docrev 0.8.1 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/PLAN-tables-and-postprocess.md +850 -0
  3. package/README.md +33 -0
  4. package/bin/rev.js +12 -131
  5. package/bin/rev.ts +145 -0
  6. package/dist/bin/rev.d.ts +9 -0
  7. package/dist/bin/rev.d.ts.map +1 -0
  8. package/dist/bin/rev.js +118 -0
  9. package/dist/bin/rev.js.map +1 -0
  10. package/dist/lib/annotations.d.ts +91 -0
  11. package/dist/lib/annotations.d.ts.map +1 -0
  12. package/dist/lib/annotations.js +554 -0
  13. package/dist/lib/annotations.js.map +1 -0
  14. package/dist/lib/build.d.ts +171 -0
  15. package/dist/lib/build.d.ts.map +1 -0
  16. package/dist/lib/build.js +755 -0
  17. package/dist/lib/build.js.map +1 -0
  18. package/dist/lib/citations.d.ts +34 -0
  19. package/dist/lib/citations.d.ts.map +1 -0
  20. package/dist/lib/citations.js +140 -0
  21. package/dist/lib/citations.js.map +1 -0
  22. package/dist/lib/commands/build.d.ts +13 -0
  23. package/dist/lib/commands/build.d.ts.map +1 -0
  24. package/dist/lib/commands/build.js +678 -0
  25. package/dist/lib/commands/build.js.map +1 -0
  26. package/dist/lib/commands/citations.d.ts +11 -0
  27. package/dist/lib/commands/citations.d.ts.map +1 -0
  28. package/dist/lib/commands/citations.js +428 -0
  29. package/dist/lib/commands/citations.js.map +1 -0
  30. package/dist/lib/commands/comments.d.ts +11 -0
  31. package/dist/lib/commands/comments.d.ts.map +1 -0
  32. package/dist/lib/commands/comments.js +883 -0
  33. package/dist/lib/commands/comments.js.map +1 -0
  34. package/dist/lib/commands/context.d.ts +35 -0
  35. package/dist/lib/commands/context.d.ts.map +1 -0
  36. package/dist/lib/commands/context.js +59 -0
  37. package/dist/lib/commands/context.js.map +1 -0
  38. package/dist/lib/commands/core.d.ts +11 -0
  39. package/dist/lib/commands/core.d.ts.map +1 -0
  40. package/dist/lib/commands/core.js +246 -0
  41. package/dist/lib/commands/core.js.map +1 -0
  42. package/dist/lib/commands/doi.d.ts +11 -0
  43. package/dist/lib/commands/doi.d.ts.map +1 -0
  44. package/dist/lib/commands/doi.js +373 -0
  45. package/dist/lib/commands/doi.js.map +1 -0
  46. package/dist/lib/commands/history.d.ts +11 -0
  47. package/dist/lib/commands/history.d.ts.map +1 -0
  48. package/dist/lib/commands/history.js +245 -0
  49. package/dist/lib/commands/history.js.map +1 -0
  50. package/dist/lib/commands/index.d.ts +28 -0
  51. package/dist/lib/commands/index.d.ts.map +1 -0
  52. package/dist/lib/commands/index.js +35 -0
  53. package/dist/lib/commands/index.js.map +1 -0
  54. package/dist/lib/commands/init.d.ts +11 -0
  55. package/dist/lib/commands/init.d.ts.map +1 -0
  56. package/dist/lib/commands/init.js +209 -0
  57. package/dist/lib/commands/init.js.map +1 -0
  58. package/dist/lib/commands/response.d.ts +11 -0
  59. package/dist/lib/commands/response.d.ts.map +1 -0
  60. package/dist/lib/commands/response.js +317 -0
  61. package/dist/lib/commands/response.js.map +1 -0
  62. package/dist/lib/commands/sections.d.ts +11 -0
  63. package/dist/lib/commands/sections.d.ts.map +1 -0
  64. package/dist/lib/commands/sections.js +1071 -0
  65. package/dist/lib/commands/sections.js.map +1 -0
  66. package/dist/lib/commands/utilities.d.ts +19 -0
  67. package/dist/lib/commands/utilities.d.ts.map +1 -0
  68. package/dist/lib/commands/utilities.js +2009 -0
  69. package/dist/lib/commands/utilities.js.map +1 -0
  70. package/dist/lib/comment-realign.d.ts +50 -0
  71. package/dist/lib/comment-realign.d.ts.map +1 -0
  72. package/dist/lib/comment-realign.js +372 -0
  73. package/dist/lib/comment-realign.js.map +1 -0
  74. package/dist/lib/config.d.ts +41 -0
  75. package/dist/lib/config.d.ts.map +1 -0
  76. package/dist/lib/config.js +76 -0
  77. package/dist/lib/config.js.map +1 -0
  78. package/dist/lib/crossref.d.ts +108 -0
  79. package/dist/lib/crossref.d.ts.map +1 -0
  80. package/dist/lib/crossref.js +597 -0
  81. package/dist/lib/crossref.js.map +1 -0
  82. package/dist/lib/dependencies.d.ts +30 -0
  83. package/dist/lib/dependencies.d.ts.map +1 -0
  84. package/dist/lib/dependencies.js +95 -0
  85. package/dist/lib/dependencies.js.map +1 -0
  86. package/dist/lib/doi-cache.d.ts +29 -0
  87. package/dist/lib/doi-cache.d.ts.map +1 -0
  88. package/dist/lib/doi-cache.js +104 -0
  89. package/dist/lib/doi-cache.js.map +1 -0
  90. package/dist/lib/doi.d.ts +65 -0
  91. package/dist/lib/doi.d.ts.map +1 -0
  92. package/dist/lib/doi.js +710 -0
  93. package/dist/lib/doi.js.map +1 -0
  94. package/dist/lib/equations.d.ts +61 -0
  95. package/dist/lib/equations.d.ts.map +1 -0
  96. package/dist/lib/equations.js +445 -0
  97. package/dist/lib/equations.js.map +1 -0
  98. package/dist/lib/errors.d.ts +60 -0
  99. package/dist/lib/errors.d.ts.map +1 -0
  100. package/dist/lib/errors.js +303 -0
  101. package/dist/lib/errors.js.map +1 -0
  102. package/dist/lib/format.d.ts +104 -0
  103. package/dist/lib/format.d.ts.map +1 -0
  104. package/dist/lib/format.js +416 -0
  105. package/dist/lib/format.js.map +1 -0
  106. package/dist/lib/git.d.ts +88 -0
  107. package/dist/lib/git.d.ts.map +1 -0
  108. package/dist/lib/git.js +304 -0
  109. package/dist/lib/git.js.map +1 -0
  110. package/dist/lib/grammar.d.ts +62 -0
  111. package/dist/lib/grammar.d.ts.map +1 -0
  112. package/dist/lib/grammar.js +244 -0
  113. package/dist/lib/grammar.js.map +1 -0
  114. package/dist/lib/image-registry.d.ts +68 -0
  115. package/dist/lib/image-registry.d.ts.map +1 -0
  116. package/dist/lib/image-registry.js +112 -0
  117. package/dist/lib/image-registry.js.map +1 -0
  118. package/dist/lib/import.d.ts +184 -0
  119. package/dist/lib/import.d.ts.map +1 -0
  120. package/dist/lib/import.js +1581 -0
  121. package/dist/lib/import.js.map +1 -0
  122. package/dist/lib/journals.d.ts +55 -0
  123. package/dist/lib/journals.d.ts.map +1 -0
  124. package/dist/lib/journals.js +417 -0
  125. package/dist/lib/journals.js.map +1 -0
  126. package/dist/lib/merge.d.ts +138 -0
  127. package/dist/lib/merge.d.ts.map +1 -0
  128. package/dist/lib/merge.js +603 -0
  129. package/dist/lib/merge.js.map +1 -0
  130. package/dist/lib/orcid.d.ts +36 -0
  131. package/dist/lib/orcid.d.ts.map +1 -0
  132. package/dist/lib/orcid.js +117 -0
  133. package/dist/lib/orcid.js.map +1 -0
  134. package/dist/lib/pdf-comments.d.ts +95 -0
  135. package/dist/lib/pdf-comments.d.ts.map +1 -0
  136. package/dist/lib/pdf-comments.js +192 -0
  137. package/dist/lib/pdf-comments.js.map +1 -0
  138. package/dist/lib/pdf-import.d.ts +118 -0
  139. package/dist/lib/pdf-import.d.ts.map +1 -0
  140. package/dist/lib/pdf-import.js +397 -0
  141. package/dist/lib/pdf-import.js.map +1 -0
  142. package/dist/lib/plugins.d.ts +76 -0
  143. package/dist/lib/plugins.d.ts.map +1 -0
  144. package/dist/lib/plugins.js +235 -0
  145. package/dist/lib/plugins.js.map +1 -0
  146. package/dist/lib/postprocess.d.ts +42 -0
  147. package/dist/lib/postprocess.d.ts.map +1 -0
  148. package/dist/lib/postprocess.js +138 -0
  149. package/dist/lib/postprocess.js.map +1 -0
  150. package/dist/lib/pptx-template.d.ts +59 -0
  151. package/dist/lib/pptx-template.d.ts.map +1 -0
  152. package/dist/lib/pptx-template.js +613 -0
  153. package/dist/lib/pptx-template.js.map +1 -0
  154. package/dist/lib/pptx-themes.d.ts +80 -0
  155. package/dist/lib/pptx-themes.d.ts.map +1 -0
  156. package/dist/lib/pptx-themes.js +818 -0
  157. package/dist/lib/pptx-themes.js.map +1 -0
  158. package/dist/lib/protect-restore.d.ts +137 -0
  159. package/dist/lib/protect-restore.d.ts.map +1 -0
  160. package/dist/lib/protect-restore.js +394 -0
  161. package/dist/lib/protect-restore.js.map +1 -0
  162. package/dist/lib/rate-limiter.d.ts +27 -0
  163. package/dist/lib/rate-limiter.d.ts.map +1 -0
  164. package/dist/lib/rate-limiter.js +79 -0
  165. package/dist/lib/rate-limiter.js.map +1 -0
  166. package/dist/lib/response.d.ts +41 -0
  167. package/dist/lib/response.d.ts.map +1 -0
  168. package/dist/lib/response.js +150 -0
  169. package/dist/lib/response.js.map +1 -0
  170. package/dist/lib/review.d.ts +35 -0
  171. package/dist/lib/review.d.ts.map +1 -0
  172. package/dist/lib/review.js +263 -0
  173. package/dist/lib/review.js.map +1 -0
  174. package/dist/lib/schema.d.ts +66 -0
  175. package/dist/lib/schema.d.ts.map +1 -0
  176. package/dist/lib/schema.js +339 -0
  177. package/dist/lib/schema.js.map +1 -0
  178. package/dist/lib/scientific-words.d.ts +6 -0
  179. package/dist/lib/scientific-words.d.ts.map +1 -0
  180. package/dist/lib/scientific-words.js +66 -0
  181. package/dist/lib/scientific-words.js.map +1 -0
  182. package/dist/lib/sections.d.ts +40 -0
  183. package/dist/lib/sections.d.ts.map +1 -0
  184. package/dist/lib/sections.js +288 -0
  185. package/dist/lib/sections.js.map +1 -0
  186. package/dist/lib/slides.d.ts +86 -0
  187. package/dist/lib/slides.d.ts.map +1 -0
  188. package/dist/lib/slides.js +676 -0
  189. package/dist/lib/slides.js.map +1 -0
  190. package/dist/lib/spelling.d.ts +76 -0
  191. package/dist/lib/spelling.d.ts.map +1 -0
  192. package/dist/lib/spelling.js +272 -0
  193. package/dist/lib/spelling.js.map +1 -0
  194. package/dist/lib/templates.d.ts +30 -0
  195. package/dist/lib/templates.d.ts.map +1 -0
  196. package/dist/lib/templates.js +504 -0
  197. package/dist/lib/templates.js.map +1 -0
  198. package/dist/lib/themes.d.ts +85 -0
  199. package/dist/lib/themes.d.ts.map +1 -0
  200. package/dist/lib/themes.js +652 -0
  201. package/dist/lib/themes.js.map +1 -0
  202. package/dist/lib/trackchanges.d.ts +51 -0
  203. package/dist/lib/trackchanges.d.ts.map +1 -0
  204. package/dist/lib/trackchanges.js +202 -0
  205. package/dist/lib/trackchanges.js.map +1 -0
  206. package/dist/lib/tui.d.ts +76 -0
  207. package/dist/lib/tui.d.ts.map +1 -0
  208. package/dist/lib/tui.js +377 -0
  209. package/dist/lib/tui.js.map +1 -0
  210. package/dist/lib/types.d.ts +447 -0
  211. package/dist/lib/types.d.ts.map +1 -0
  212. package/dist/lib/types.js +6 -0
  213. package/dist/lib/types.js.map +1 -0
  214. package/dist/lib/undo.d.ts +57 -0
  215. package/dist/lib/undo.d.ts.map +1 -0
  216. package/dist/lib/undo.js +185 -0
  217. package/dist/lib/undo.js.map +1 -0
  218. package/dist/lib/utils.d.ts +16 -0
  219. package/dist/lib/utils.d.ts.map +1 -0
  220. package/dist/lib/utils.js +40 -0
  221. package/dist/lib/utils.js.map +1 -0
  222. package/dist/lib/variables.d.ts +42 -0
  223. package/dist/lib/variables.d.ts.map +1 -0
  224. package/dist/lib/variables.js +141 -0
  225. package/dist/lib/variables.js.map +1 -0
  226. package/dist/lib/word.d.ts +80 -0
  227. package/dist/lib/word.d.ts.map +1 -0
  228. package/dist/lib/word.js +360 -0
  229. package/dist/lib/word.js.map +1 -0
  230. package/dist/lib/wordcomments.d.ts +51 -0
  231. package/dist/lib/wordcomments.d.ts.map +1 -0
  232. package/dist/lib/wordcomments.js +587 -0
  233. package/dist/lib/wordcomments.js.map +1 -0
  234. package/eslint.config.js +27 -0
  235. package/lib/annotations.ts +622 -0
  236. package/lib/apply-buildup-colors.py +88 -0
  237. package/lib/build.ts +1013 -0
  238. package/lib/{citations.js → citations.ts} +38 -27
  239. package/lib/commands/{build.js → build.ts} +80 -27
  240. package/lib/commands/{citations.js → citations.ts} +36 -18
  241. package/lib/commands/{comments.js → comments.ts} +187 -54
  242. package/lib/commands/{context.js → context.ts} +18 -8
  243. package/lib/commands/{core.js → core.ts} +34 -20
  244. package/lib/commands/{doi.js → doi.ts} +32 -16
  245. package/lib/commands/{history.js → history.ts} +25 -12
  246. package/lib/commands/{index.js → index.ts} +9 -5
  247. package/lib/commands/{init.js → init.ts} +20 -8
  248. package/lib/commands/{response.js → response.ts} +47 -20
  249. package/lib/commands/{sections.js → sections.ts} +273 -68
  250. package/lib/commands/{utilities.js → utilities.ts} +338 -158
  251. package/lib/{comment-realign.js → comment-realign.ts} +117 -45
  252. package/lib/config.ts +84 -0
  253. package/lib/{crossref.js → crossref.ts} +213 -138
  254. package/lib/dependencies.ts +106 -0
  255. package/lib/doi-cache.ts +115 -0
  256. package/lib/{doi.js → doi.ts} +115 -281
  257. package/lib/{equations.js → equations.ts} +60 -64
  258. package/lib/{errors.js → errors.ts} +56 -48
  259. package/lib/{format.js → format.ts} +137 -63
  260. package/lib/{git.js → git.ts} +66 -63
  261. package/lib/{grammar.js → grammar.ts} +45 -32
  262. package/lib/image-registry.ts +180 -0
  263. package/lib/import.ts +2060 -0
  264. package/lib/journals.ts +505 -0
  265. package/lib/{merge.js → merge.ts} +185 -135
  266. package/lib/{orcid.js → orcid.ts} +17 -22
  267. package/lib/{pdf-comments.js → pdf-comments.ts} +76 -18
  268. package/lib/{pdf-import.js → pdf-import.ts} +148 -70
  269. package/lib/{plugins.js → plugins.ts} +82 -39
  270. package/lib/postprocess.ts +188 -0
  271. package/lib/pptx-color-filter.lua +37 -0
  272. package/lib/pptx-template.ts +625 -0
  273. package/lib/pptx-themes/academic.pptx +0 -0
  274. package/lib/pptx-themes/corporate.pptx +0 -0
  275. package/lib/pptx-themes/dark.pptx +0 -0
  276. package/lib/pptx-themes/default.pptx +0 -0
  277. package/lib/pptx-themes/minimal.pptx +0 -0
  278. package/lib/pptx-themes/plant.pptx +0 -0
  279. package/lib/pptx-themes.ts +896 -0
  280. package/lib/protect-restore.ts +516 -0
  281. package/lib/rate-limiter.ts +94 -0
  282. package/lib/{response.js → response.ts} +36 -21
  283. package/lib/{review.js → review.ts} +53 -43
  284. package/lib/{schema.js → schema.ts} +70 -25
  285. package/lib/{sections.js → sections.ts} +71 -76
  286. package/lib/slides.ts +793 -0
  287. package/lib/{spelling.js → spelling.ts} +43 -59
  288. package/lib/{templates.js → templates.ts} +20 -17
  289. package/lib/themes.ts +742 -0
  290. package/lib/{trackchanges.js → trackchanges.ts} +52 -23
  291. package/lib/types.ts +509 -0
  292. package/lib/{undo.js → undo.ts} +75 -52
  293. package/lib/utils.ts +41 -0
  294. package/lib/{variables.js → variables.ts} +60 -54
  295. package/lib/word.ts +428 -0
  296. package/lib/{wordcomments.js → wordcomments.ts} +94 -40
  297. package/package.json +15 -5
  298. package/skill/REFERENCE.md +67 -0
  299. package/tsconfig.json +26 -0
  300. package/lib/annotations.js +0 -414
  301. package/lib/build.js +0 -639
  302. package/lib/config.js +0 -79
  303. package/lib/import.js +0 -1145
  304. package/lib/journals.js +0 -629
  305. package/lib/word.js +0 -225
  306. /package/lib/{scientific-words.js → scientific-words.ts} +0 -0
package/lib/build.js DELETED
@@ -1,639 +0,0 @@
1
- /**
2
- * Build system - combines sections → paper.md → PDF/DOCX/TEX
3
- *
4
- * Features:
5
- * - Reads rev.yaml config
6
- * - Combines section files into paper.md (persisted)
7
- * - Strips annotations appropriately per output format
8
- * - Runs pandoc with crossref filter
9
- */
10
-
11
- import * as fs from 'fs';
12
- import * as path from 'path';
13
- import { execSync, spawn } from 'child_process';
14
- import YAML from 'yaml';
15
- import { stripAnnotations } from './annotations.js';
16
- import { buildRegistry, labelToDisplay, detectDynamicRefs, resolveForwardRefs } from './crossref.js';
17
- import { processVariables, hasVariables } from './variables.js';
18
-
19
- /**
20
- * Default rev.yaml configuration
21
- */
22
- export const DEFAULT_CONFIG = {
23
- title: 'Untitled Document',
24
- authors: [],
25
- sections: [],
26
- bibliography: null,
27
- csl: null,
28
- crossref: {
29
- figureTitle: 'Figure',
30
- tableTitle: 'Table',
31
- figPrefix: ['Fig.', 'Figs.'],
32
- tblPrefix: ['Table', 'Tables'],
33
- secPrefix: ['Section', 'Sections'],
34
- linkReferences: true,
35
- },
36
- pdf: {
37
- template: null,
38
- documentclass: 'article',
39
- fontsize: '12pt',
40
- geometry: 'margin=1in',
41
- linestretch: 1.5,
42
- numbersections: false,
43
- toc: false,
44
- },
45
- docx: {
46
- reference: null,
47
- keepComments: true,
48
- toc: false,
49
- },
50
- tex: {
51
- standalone: true,
52
- },
53
- };
54
-
55
- /**
56
- * Load rev.yaml config from directory
57
- * @param {string} directory
58
- * @returns {object} merged config with defaults
59
- */
60
- export function loadConfig(directory) {
61
- const configPath = path.join(directory, 'rev.yaml');
62
-
63
- if (!fs.existsSync(configPath)) {
64
- return { ...DEFAULT_CONFIG, _configPath: null };
65
- }
66
-
67
- try {
68
- const content = fs.readFileSync(configPath, 'utf-8');
69
- const userConfig = YAML.parse(content) || {};
70
-
71
- // Deep merge with defaults
72
- const config = {
73
- ...DEFAULT_CONFIG,
74
- ...userConfig,
75
- crossref: { ...DEFAULT_CONFIG.crossref, ...userConfig.crossref },
76
- pdf: { ...DEFAULT_CONFIG.pdf, ...userConfig.pdf },
77
- docx: { ...DEFAULT_CONFIG.docx, ...userConfig.docx },
78
- tex: { ...DEFAULT_CONFIG.tex, ...userConfig.tex },
79
- _configPath: configPath,
80
- };
81
-
82
- return config;
83
- } catch (err) {
84
- throw new Error(`Failed to parse rev.yaml: ${err.message}`);
85
- }
86
- }
87
-
88
- /**
89
- * Find section files in directory
90
- * @param {string} directory
91
- * @param {string[]} configSections - sections from rev.yaml (optional)
92
- * @returns {string[]} ordered list of section files
93
- */
94
- export function findSections(directory, configSections = []) {
95
- // If sections specified in config, use that order
96
- if (configSections.length > 0) {
97
- const sections = [];
98
- for (const section of configSections) {
99
- const filePath = path.join(directory, section);
100
- if (fs.existsSync(filePath)) {
101
- sections.push(section);
102
- } else {
103
- console.warn(`Warning: Section file not found: ${section}`);
104
- }
105
- }
106
- return sections;
107
- }
108
-
109
- // Try sections.yaml
110
- const sectionsYamlPath = path.join(directory, 'sections.yaml');
111
- if (fs.existsSync(sectionsYamlPath)) {
112
- try {
113
- const sectionsConfig = YAML.parse(fs.readFileSync(sectionsYamlPath, 'utf-8'));
114
- if (sectionsConfig.sections) {
115
- return Object.entries(sectionsConfig.sections)
116
- .sort((a, b) => (a[1].order ?? 999) - (b[1].order ?? 999))
117
- .map(([file]) => file)
118
- .filter((f) => fs.existsSync(path.join(directory, f)));
119
- }
120
- } catch {
121
- // Ignore yaml errors
122
- }
123
- }
124
-
125
- // Default: find all .md files except special ones
126
- const exclude = ['paper.md', 'readme.md', 'claude.md'];
127
- const files = fs.readdirSync(directory).filter((f) => {
128
- if (!f.endsWith('.md')) return false;
129
- if (exclude.includes(f.toLowerCase())) return false;
130
- return true;
131
- });
132
-
133
- // Sort alphabetically as fallback
134
- return files.sort();
135
- }
136
-
137
- /**
138
- * Combine section files into paper.md
139
- * @param {string} directory
140
- * @param {object} config
141
- * @param {object} options
142
- * @returns {string} path to paper.md
143
- */
144
- export function combineSections(directory, config, options = {}) {
145
- const sections = findSections(directory, config.sections);
146
-
147
- if (sections.length === 0) {
148
- throw new Error('No section files found. Create .md files or specify sections in rev.yaml');
149
- }
150
-
151
- const parts = [];
152
-
153
- // Add YAML frontmatter
154
- const frontmatter = buildFrontmatter(config);
155
- parts.push('---');
156
- parts.push(YAML.stringify(frontmatter).trim());
157
- parts.push('---');
158
- parts.push('');
159
-
160
- // Read all section contents for variable processing
161
- const sectionContents = [];
162
-
163
- // Check if we need to auto-inject references before supplementary
164
- // Pandoc places refs at the end by default, which breaks when supplementary follows
165
- const hasRefsSection = sections.some(s =>
166
- s.toLowerCase().includes('reference') || s.toLowerCase().includes('refs')
167
- );
168
- const suppIndex = sections.findIndex(s =>
169
- s.toLowerCase().includes('supp') || s.toLowerCase().includes('appendix')
170
- );
171
- const hasBibliography = config.bibliography && fs.existsSync(path.join(directory, config.bibliography));
172
-
173
- // Track if we find an explicit refs div in any section
174
- let hasExplicitRefsDiv = false;
175
-
176
- // Combine sections
177
- for (let i = 0; i < sections.length; i++) {
178
- const section = sections[i];
179
- const filePath = path.join(directory, section);
180
- let content = fs.readFileSync(filePath, 'utf-8');
181
-
182
- // Remove any existing frontmatter from section files
183
- content = stripFrontmatter(content);
184
- sectionContents.push(content);
185
-
186
- // Check if this section has an explicit refs div
187
- if (content.includes('::: {#refs}') || content.includes('::: {#refs}')) {
188
- hasExplicitRefsDiv = true;
189
- }
190
-
191
- // Auto-inject references before supplementary if needed
192
- if (i === suppIndex && hasBibliography && !hasRefsSection && !hasExplicitRefsDiv) {
193
- parts.push('# References\n');
194
- parts.push('::: {#refs}');
195
- parts.push(':::');
196
- parts.push('');
197
- parts.push('');
198
- options._refsAutoInjected = true;
199
- }
200
-
201
- parts.push(content.trim());
202
- parts.push('');
203
- parts.push(''); // Double newline between sections
204
- }
205
-
206
- let paperContent = parts.join('\n');
207
-
208
- // Process template variables if any exist
209
- if (hasVariables(paperContent)) {
210
- paperContent = processVariables(paperContent, config, { sectionContents });
211
- }
212
-
213
- // Resolve forward references (refs that appear before their anchor definition)
214
- // This fixes pandoc-crossref limitation with multi-file documents
215
- if (hasPandocCrossref()) {
216
- const registry = buildRegistry(directory, sections);
217
- const { text, resolved } = resolveForwardRefs(paperContent, registry);
218
- if (resolved.length > 0) {
219
- paperContent = text;
220
- // Store resolved count for optional reporting
221
- options._forwardRefsResolved = resolved.length;
222
- }
223
- }
224
-
225
- const paperPath = path.join(directory, 'paper.md');
226
-
227
- fs.writeFileSync(paperPath, paperContent, 'utf-8');
228
-
229
- return paperPath;
230
- }
231
-
232
- /**
233
- * Build YAML frontmatter from config
234
- * @param {object} config
235
- * @returns {object}
236
- */
237
- function buildFrontmatter(config) {
238
- const fm = {};
239
-
240
- if (config.title) fm.title = config.title;
241
-
242
- if (config.authors && config.authors.length > 0) {
243
- fm.author = config.authors;
244
- }
245
-
246
- if (config.bibliography) {
247
- fm.bibliography = config.bibliography;
248
- }
249
-
250
- if (config.csl) {
251
- fm.csl = config.csl;
252
- }
253
-
254
- return fm;
255
- }
256
-
257
- /**
258
- * Strip YAML frontmatter from content
259
- * @param {string} content
260
- * @returns {string}
261
- */
262
- function stripFrontmatter(content) {
263
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
264
- if (match) {
265
- return content.slice(match[0].length);
266
- }
267
- return content;
268
- }
269
-
270
- /**
271
- * Prepare paper.md for specific output format
272
- * @param {string} paperPath
273
- * @param {string} format - 'pdf', 'docx', 'tex'
274
- * @param {object} config
275
- * @param {object} options
276
- * @returns {string} path to prepared file
277
- */
278
- export function prepareForFormat(paperPath, format, config, options = {}) {
279
- const directory = path.dirname(paperPath);
280
- let content = fs.readFileSync(paperPath, 'utf-8');
281
-
282
- // Build crossref registry for reference conversion
283
- // Pass sections from config to ensure correct file ordering
284
- const registry = buildRegistry(directory, config.sections);
285
-
286
- if (format === 'pdf' || format === 'tex') {
287
- // Strip all annotations for clean output
288
- content = stripAnnotations(content);
289
- } else if (format === 'docx') {
290
- // Strip track changes, optionally keep comments
291
- content = stripAnnotations(content, { keepComments: config.docx.keepComments });
292
-
293
- // Convert @fig:label to "Figure 1" for Word readers
294
- content = convertDynamicRefsToDisplay(content, registry);
295
- }
296
-
297
- // Write to temporary file
298
- const preparedPath = path.join(directory, `.paper-${format}.md`);
299
- fs.writeFileSync(preparedPath, content, 'utf-8');
300
-
301
- return preparedPath;
302
- }
303
-
304
- /**
305
- * Convert @fig:label references to display format (Figure 1)
306
- * @param {string} text
307
- * @param {object} registry
308
- * @returns {string}
309
- */
310
- function convertDynamicRefsToDisplay(text, registry) {
311
- const refs = detectDynamicRefs(text);
312
-
313
- // Process in reverse order to preserve positions
314
- let result = text;
315
- for (let i = refs.length - 1; i >= 0; i--) {
316
- const ref = refs[i];
317
- const display = labelToDisplay(ref.type, ref.label, registry);
318
-
319
- if (display) {
320
- result = result.slice(0, ref.position) + display + result.slice(ref.position + ref.match.length);
321
- }
322
- }
323
-
324
- return result;
325
- }
326
-
327
- /**
328
- * Build pandoc arguments for format
329
- * @param {string} format
330
- * @param {object} config
331
- * @param {string} outputPath
332
- * @returns {string[]}
333
- */
334
- export function buildPandocArgs(format, config, outputPath) {
335
- const args = [];
336
-
337
- // Output format
338
- if (format === 'tex') {
339
- args.push('-t', 'latex');
340
- if (config.tex.standalone) {
341
- args.push('-s');
342
- }
343
- } else if (format === 'pdf') {
344
- args.push('-t', 'pdf');
345
- } else if (format === 'docx') {
346
- args.push('-t', 'docx');
347
- }
348
-
349
- args.push('-o', outputPath);
350
-
351
- // Crossref filter (if available)
352
- if (hasPandocCrossref()) {
353
- args.push('--filter', 'pandoc-crossref');
354
- }
355
-
356
- // Bibliography
357
- if (config.bibliography) {
358
- args.push('--citeproc');
359
- }
360
-
361
- // Format-specific options
362
- if (format === 'pdf') {
363
- if (config.pdf.template) {
364
- args.push('--template', config.pdf.template);
365
- }
366
- args.push('-V', `documentclass=${config.pdf.documentclass}`);
367
- args.push('-V', `fontsize=${config.pdf.fontsize}`);
368
- args.push('-V', `geometry:${config.pdf.geometry}`);
369
- if (config.pdf.linestretch !== 1) {
370
- args.push('-V', `linestretch=${config.pdf.linestretch}`);
371
- }
372
- if (config.pdf.numbersections) {
373
- args.push('--number-sections');
374
- }
375
- if (config.pdf.toc) {
376
- args.push('--toc');
377
- }
378
- } else if (format === 'docx') {
379
- if (config.docx.reference) {
380
- args.push('--reference-doc', config.docx.reference);
381
- }
382
- if (config.docx.toc) {
383
- args.push('--toc');
384
- }
385
- }
386
-
387
- return args;
388
- }
389
-
390
- /**
391
- * Check if pandoc-crossref is available
392
- * @returns {boolean}
393
- */
394
- export function hasPandocCrossref() {
395
- try {
396
- execSync('pandoc-crossref --version', { stdio: 'ignore' });
397
- return true;
398
- } catch {
399
- return false;
400
- }
401
- }
402
-
403
- /**
404
- * Check if pandoc is available
405
- * @returns {boolean}
406
- */
407
- export function hasPandoc() {
408
- try {
409
- execSync('pandoc --version', { stdio: 'ignore' });
410
- return true;
411
- } catch {
412
- return false;
413
- }
414
- }
415
-
416
- /**
417
- * Check if LaTeX is available (for PDF generation)
418
- * @returns {boolean}
419
- */
420
- export function hasLatex() {
421
- try {
422
- execSync('pdflatex --version', { stdio: 'ignore' });
423
- return true;
424
- } catch {
425
- try {
426
- execSync('xelatex --version', { stdio: 'ignore' });
427
- return true;
428
- } catch {
429
- return false;
430
- }
431
- }
432
- }
433
-
434
- /**
435
- * Get installation instructions for missing dependencies
436
- * @param {string} dependency - 'pandoc', 'latex', 'pandoc-crossref'
437
- * @returns {string}
438
- */
439
- export function getInstallInstructions(dependency) {
440
- const platform = process.platform;
441
- const instructions = {
442
- pandoc: {
443
- darwin: 'brew install pandoc',
444
- win32: 'winget install JohnMacFarlane.Pandoc',
445
- linux: 'sudo apt install pandoc',
446
- },
447
- latex: {
448
- darwin: 'brew install --cask mactex-no-gui',
449
- win32: 'Install MiKTeX from https://miktex.org/download',
450
- linux: 'sudo apt install texlive-latex-base texlive-fonts-recommended',
451
- },
452
- 'pandoc-crossref': {
453
- darwin: 'brew install pandoc-crossref',
454
- win32: 'Download from https://github.com/lierdakil/pandoc-crossref/releases',
455
- linux: 'Download from https://github.com/lierdakil/pandoc-crossref/releases',
456
- },
457
- };
458
-
459
- const platformInstructions = instructions[dependency];
460
- if (!platformInstructions) return '';
461
-
462
- return platformInstructions[platform] || platformInstructions.linux;
463
- }
464
-
465
- /**
466
- * Check dependencies and return status
467
- * @returns {{ pandoc: boolean, latex: boolean, crossref: boolean, messages: string[] }}
468
- */
469
- export function checkDependencies() {
470
- const status = {
471
- pandoc: hasPandoc(),
472
- latex: hasLatex(),
473
- crossref: hasPandocCrossref(),
474
- messages: [],
475
- };
476
-
477
- if (!status.pandoc) {
478
- status.messages.push(`Pandoc not found. Install with: ${getInstallInstructions('pandoc')}`);
479
- }
480
- if (!status.latex) {
481
- status.messages.push(`LaTeX not found (required for PDF). Install with: ${getInstallInstructions('latex')}`);
482
- }
483
- if (!status.crossref) {
484
- status.messages.push(`pandoc-crossref not found (optional, for figure/table refs). Install with: ${getInstallInstructions('pandoc-crossref')}`);
485
- }
486
-
487
- return status;
488
- }
489
-
490
- /**
491
- * Write crossref.yaml if needed
492
- * @param {string} directory
493
- * @param {object} config
494
- */
495
- function ensureCrossrefConfig(directory, config) {
496
- const crossrefPath = path.join(directory, 'crossref.yaml');
497
-
498
- if (!fs.existsSync(crossrefPath) && hasPandocCrossref()) {
499
- fs.writeFileSync(crossrefPath, YAML.stringify(config.crossref), 'utf-8');
500
- }
501
- }
502
-
503
- /**
504
- * Run pandoc build
505
- * @param {string} inputPath
506
- * @param {string} format
507
- * @param {object} config
508
- * @param {object} options
509
- * @returns {Promise<{outputPath: string, success: boolean, error?: string}>}
510
- */
511
- export async function runPandoc(inputPath, format, config, options = {}) {
512
- const directory = path.dirname(inputPath);
513
- const baseName = config.title
514
- ? config.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 50)
515
- : 'paper';
516
-
517
- const ext = format === 'tex' ? '.tex' : format === 'pdf' ? '.pdf' : '.docx';
518
- // Allow custom output path via options
519
- const outputPath = options.outputPath || path.join(directory, `${baseName}${ext}`);
520
-
521
- // Ensure crossref.yaml exists
522
- ensureCrossrefConfig(directory, config);
523
-
524
- const args = buildPandocArgs(format, config, outputPath);
525
-
526
- // Add crossref metadata file if exists
527
- const crossrefPath = path.join(directory, 'crossref.yaml');
528
- if (fs.existsSync(crossrefPath) && hasPandocCrossref()) {
529
- args.push('--metadata-file', crossrefPath);
530
- }
531
-
532
- // Input file
533
- args.push(inputPath);
534
-
535
- return new Promise((resolve) => {
536
- const pandoc = spawn('pandoc', args, {
537
- cwd: directory,
538
- stdio: ['ignore', 'pipe', 'pipe'],
539
- });
540
-
541
- let stderr = '';
542
- pandoc.stderr.on('data', (data) => {
543
- stderr += data.toString();
544
- });
545
-
546
- pandoc.on('close', (code) => {
547
- if (code === 0) {
548
- resolve({ outputPath, success: true });
549
- } else {
550
- resolve({ outputPath, success: false, error: stderr || `Exit code ${code}` });
551
- }
552
- });
553
-
554
- pandoc.on('error', (err) => {
555
- resolve({ outputPath, success: false, error: err.message });
556
- });
557
- });
558
- }
559
-
560
- /**
561
- * Full build pipeline
562
- * @param {string} directory
563
- * @param {string[]} formats - ['pdf', 'docx', 'tex'] or ['all']
564
- * @param {object} options
565
- * @returns {Promise<{results: object[], paperPath: string, warnings: string[], forwardRefsResolved: number}>}
566
- */
567
- export async function build(directory, formats = ['pdf', 'docx'], options = {}) {
568
- const warnings = [];
569
- let forwardRefsResolved = 0;
570
-
571
- // Check pandoc
572
- if (!hasPandoc()) {
573
- const instruction = getInstallInstructions('pandoc');
574
- throw new Error(`Pandoc not found. Install with: ${instruction}\nOr run: rev doctor`);
575
- }
576
-
577
- // Check LaTeX if PDF is requested
578
- if ((formats.includes('pdf') || formats.includes('all')) && !hasLatex()) {
579
- warnings.push(`LaTeX not found - PDF generation may fail. Install with: ${getInstallInstructions('latex')}`);
580
- }
581
-
582
- // Check pandoc-crossref
583
- if (!hasPandocCrossref()) {
584
- warnings.push('pandoc-crossref not found - figure/table numbering will not work');
585
- }
586
-
587
- // Load config (use passed config if provided, otherwise load from file)
588
- const config = options.config || loadConfig(directory);
589
-
590
- // Combine sections → paper.md
591
- const buildOptions = { ...options };
592
- const paperPath = combineSections(directory, config, buildOptions);
593
- forwardRefsResolved = buildOptions._forwardRefsResolved || 0;
594
- const refsAutoInjected = buildOptions._refsAutoInjected || false;
595
-
596
- // Expand 'all' to all formats
597
- if (formats.includes('all')) {
598
- formats = ['pdf', 'docx', 'tex'];
599
- }
600
-
601
- const results = [];
602
-
603
- for (const format of formats) {
604
- // Prepare format-specific version
605
- const preparedPath = prepareForFormat(paperPath, format, config, options);
606
-
607
- // Run pandoc
608
- const result = await runPandoc(preparedPath, format, config, options);
609
- results.push({ format, ...result });
610
-
611
- // Clean up temp file
612
- try {
613
- fs.unlinkSync(preparedPath);
614
- } catch {
615
- // Ignore cleanup errors
616
- }
617
- }
618
-
619
- return { results, paperPath, warnings, forwardRefsResolved, refsAutoInjected };
620
- }
621
-
622
- /**
623
- * Get build status summary
624
- * @param {object[]} results
625
- * @returns {string}
626
- */
627
- export function formatBuildResults(results) {
628
- const lines = [];
629
-
630
- for (const r of results) {
631
- if (r.success) {
632
- lines.push(` ${r.format.toUpperCase()}: ${path.basename(r.outputPath)}`);
633
- } else {
634
- lines.push(` ${r.format.toUpperCase()}: FAILED - ${r.error}`);
635
- }
636
- }
637
-
638
- return lines.join('\n');
639
- }
package/lib/config.js DELETED
@@ -1,79 +0,0 @@
1
- /**
2
- * User configuration management
3
- * Stores user preferences in ~/.revrc
4
- */
5
-
6
- import * as fs from 'fs';
7
- import * as path from 'path';
8
- import * as os from 'os';
9
-
10
- const CONFIG_PATH = path.join(os.homedir(), '.revrc');
11
-
12
- /**
13
- * Load user config
14
- * @returns {object}
15
- */
16
- export function loadUserConfig() {
17
- try {
18
- if (fs.existsSync(CONFIG_PATH)) {
19
- return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
20
- }
21
- } catch {
22
- // Ignore parse errors
23
- }
24
- return {};
25
- }
26
-
27
- /**
28
- * Save user config
29
- * @param {object} config
30
- */
31
- export function saveUserConfig(config) {
32
- fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
33
- }
34
-
35
- /**
36
- * Get user name
37
- * @returns {string|null}
38
- */
39
- export function getUserName() {
40
- const config = loadUserConfig();
41
- return config.userName || null;
42
- }
43
-
44
- /**
45
- * Set user name
46
- * @param {string} name
47
- */
48
- export function setUserName(name) {
49
- const config = loadUserConfig();
50
- config.userName = name;
51
- saveUserConfig(config);
52
- }
53
-
54
- /**
55
- * Get config file path
56
- * @returns {string}
57
- */
58
- export function getConfigPath() {
59
- return CONFIG_PATH;
60
- }
61
-
62
- /**
63
- * Get default sections for new projects
64
- * @returns {string[]|null}
65
- */
66
- export function getDefaultSections() {
67
- const config = loadUserConfig();
68
- return config.defaultSections || null;
69
- }
70
-
71
- /**
72
- * Set default sections for new projects
73
- * @param {string[]} sections - Array of section names (without .md extension)
74
- */
75
- export function setDefaultSections(sections) {
76
- const config = loadUserConfig();
77
- config.defaultSections = sections;
78
- saveUserConfig(config);
79
- }