MetaHandler.mjs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import ProjectEntityHandler from '../Project/ProjectEntityHandler.mjs'
  2. import DocumentUpdaterHandler from '../DocumentUpdater/DocumentUpdaterHandler.mjs'
  3. import packageMapping from './packageMapping.mjs'
  4. import { callbackify } from '@overleaf/promise-utils'
  5. /** @typedef {{
  6. * labels: string[]
  7. * packages: Record<string, Record<string, any>>,
  8. * packageNames: string[],
  9. * documentClass: string | null
  10. * }} DocMeta
  11. */
  12. const LABEL_RE = /\\label{(.{0,80}?)}/g
  13. const LABEL_OPTION_RE = /\blabel={?(.{0,80}?)[\s},\]]/g
  14. const PACKAGE_RE = /^\\usepackage(?:\[.{0,80}?])?{(.{0,80}?)}/g
  15. const REQ_PACKAGE_RE = /^\\RequirePackage(?:\[.{0,80}?])?{(.{0,80}?)}/g
  16. const DOCUMENT_CLASS_RE = /^\\documentclass(?:\[.{0,80}?])?{(.{0,80}?)}/
  17. /**
  18. * @param {string[]} lines
  19. * @return {Promise<DocMeta>}
  20. */
  21. async function extractMetaFromDoc(lines) {
  22. /** @type {DocMeta} */
  23. const docMeta = {
  24. labels: [],
  25. packages: {},
  26. packageNames: [],
  27. documentClass: null,
  28. }
  29. for (const rawLine of lines) {
  30. const line = getNonCommentedContent(rawLine)
  31. for (const label of lineMatches(LABEL_RE, line)) {
  32. docMeta.labels.push(label)
  33. }
  34. for (const label of lineMatches(LABEL_OPTION_RE, line)) {
  35. docMeta.labels.push(label)
  36. }
  37. for (const pkg of lineMatches(PACKAGE_RE, line, ',')) {
  38. docMeta.packageNames.push(pkg)
  39. }
  40. for (const pkg of lineMatches(REQ_PACKAGE_RE, line, ',')) {
  41. docMeta.packageNames.push(pkg)
  42. }
  43. if (docMeta.documentClass == null) {
  44. const match = line.match(DOCUMENT_CLASS_RE)
  45. if (match != null) {
  46. docMeta.documentClass = match[1]
  47. }
  48. }
  49. }
  50. for (const packageName of docMeta.packageNames) {
  51. if (packageMapping[packageName]) {
  52. docMeta.packages[packageName] = packageMapping[packageName]
  53. }
  54. }
  55. return docMeta
  56. }
  57. /**
  58. *
  59. * @param {RegExp} matchRe
  60. * @param {string} line
  61. * @param {string} [separator]
  62. * @return {Generator<string>}
  63. */
  64. function* lineMatches(matchRe, line, separator) {
  65. let match
  66. while ((match = matchRe.exec(line))) {
  67. const matched = match[1].trim()
  68. if (matched) {
  69. if (separator) {
  70. const items = matched
  71. .split(',')
  72. .map(item => item.trim())
  73. .filter(Boolean)
  74. for (const item of items) {
  75. yield item
  76. }
  77. } else {
  78. yield matched
  79. }
  80. }
  81. }
  82. }
  83. /**
  84. * @param {Record<{ lines: string[] }, any>} projectDocs
  85. * @return {Promise<{}>}
  86. */
  87. async function extractMetaFromProjectDocs(projectDocs) {
  88. const projectMeta = {}
  89. for (const doc of Object.values(projectDocs)) {
  90. projectMeta[doc._id] = await extractMetaFromDoc(doc.lines)
  91. }
  92. return projectMeta
  93. }
  94. /**
  95. * Trims comment content from line
  96. * @param {string} rawLine
  97. * @returns {string}
  98. */
  99. function getNonCommentedContent(rawLine) {
  100. return rawLine.replace(/(^|[^\\])%.*/, '$1')
  101. }
  102. async function getAllMetaForProject(projectId) {
  103. await DocumentUpdaterHandler.promises.flushProjectToMongo(projectId)
  104. const docs = await ProjectEntityHandler.promises.getAllDocs(projectId)
  105. return await extractMetaFromProjectDocs(docs)
  106. }
  107. async function getMetaForDoc(projectId, docId) {
  108. await DocumentUpdaterHandler.promises.flushDocToMongo(projectId, docId)
  109. const { lines } = await ProjectEntityHandler.promises.getDoc(projectId, docId)
  110. return await extractMetaFromDoc(lines)
  111. }
  112. export default {
  113. promises: {
  114. getAllMetaForProject,
  115. getMetaForDoc,
  116. },
  117. getAllMetaForProject: callbackify(getAllMetaForProject),
  118. getMetaForDoc: callbackify(getMetaForDoc),
  119. }