Files
overleaf-cep/services/web/app/src/Features/Metadata/MetaHandler.mjs
T
Alf Eaton 27a617eb16 Parse labels from environment options (#24189)
GitOrigin-RevId: e51eed7521f6e32e614f8b38092a0b0219f7f186
2025-03-11 09:05:31 +00:00

133 lines
3.2 KiB
JavaScript

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<string, Record<string, any>>,
* packageNames: string[],
* }} DocMeta
*/
/**
* @param {string[]} lines
* @return {Promise<DocMeta>}
*/
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<string>}
*/
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),
}