import ProjectEntityHandler from '../Project/ProjectEntityHandler.js' import DocumentUpdaterHandler from '../DocumentUpdater/DocumentUpdaterHandler.js' import packageMapping from './packageMapping.mjs' import { callbackify } from '@overleaf/promise-utils' /** @typedef {{ * labels: string[] * packages: Record>, * packageNames: string[], * }} DocMeta */ /** * @param {string[]} lines * @return {Promise} */ async function extractMetaFromDoc(lines) { /** @type {DocMeta} */ const docMeta = { labels: [], packages: {}, packageNames: [], } const labelRe = /\\label{(.{0,80}?)}/g const labelOptionRe = /\blabel={?(.{0,80}?)[\s},\]]/g const packageRe = /^\\usepackage(?:\[.{0,80}?])?{(.{0,80}?)}/g const reqPackageRe = /^\\RequirePackage(?:\[.{0,80}?])?{(.{0,80}?)}/g for (const rawLine of lines) { const line = getNonCommentedContent(rawLine) for (const label of lineMatches(labelRe, line)) { docMeta.labels.push(label) } for (const label of lineMatches(labelOptionRe, line)) { docMeta.labels.push(label) } for (const pkg of lineMatches(packageRe, line, ',')) { docMeta.packageNames.push(pkg) } for (const pkg of lineMatches(reqPackageRe, line, ',')) { docMeta.packageNames.push(pkg) } } for (const packageName of docMeta.packageNames) { if (packageMapping[packageName]) { docMeta.packages[packageName] = packageMapping[packageName] } } return docMeta } /** * * @param {RegExp} matchRe * @param {string} line * @param {string} [separator] * @return {Generator} */ function* lineMatches(matchRe, line, separator) { let match while ((match = matchRe.exec(line))) { const matched = match[1].trim() if (matched) { if (separator) { const items = matched .split(',') .map(item => item.trim()) .filter(Boolean) for (const item of items) { yield item } } else { yield matched } } } } /** * @param {Record<{ lines: string[] }, any>} projectDocs * @return {Promise<{}>} */ async function extractMetaFromProjectDocs(projectDocs) { const projectMeta = {} for (const doc of Object.values(projectDocs)) { projectMeta[doc._id] = await extractMetaFromDoc(doc.lines) } return projectMeta } /** * Trims comment content from line * @param {string} rawLine * @returns {string} */ function getNonCommentedContent(rawLine) { return rawLine.replace(/(^|[^\\])%.*/, '$1') } async function getAllMetaForProject(projectId) { await DocumentUpdaterHandler.promises.flushProjectToMongo(projectId) const docs = await ProjectEntityHandler.promises.getAllDocs(projectId) return await extractMetaFromProjectDocs(docs) } async function getMetaForDoc(projectId, docId) { await DocumentUpdaterHandler.promises.flushDocToMongo(projectId, docId) const { lines } = await ProjectEntityHandler.promises.getDoc(projectId, docId) return await extractMetaFromDoc(lines) } export default { promises: { getAllMetaForProject, getMetaForDoc, }, getAllMetaForProject: callbackify(getAllMetaForProject), getMetaForDoc: callbackify(getMetaForDoc), }