docrev 0.6.7 → 0.7.6
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.
- package/CHANGELOG.md +32 -0
- package/README.md +230 -95
- package/bin/rev.js +113 -5059
- package/completions/rev.ps1 +210 -0
- package/lib/annotations.js +41 -11
- package/lib/build.js +95 -8
- package/lib/commands/build.js +708 -0
- package/lib/commands/citations.js +497 -0
- package/lib/commands/comments.js +922 -0
- package/lib/commands/context.js +165 -0
- package/lib/commands/core.js +295 -0
- package/lib/commands/doi.js +419 -0
- package/lib/commands/history.js +307 -0
- package/lib/commands/index.js +56 -0
- package/lib/commands/init.js +247 -0
- package/lib/commands/response.js +374 -0
- package/lib/commands/sections.js +862 -0
- package/lib/commands/utilities.js +2272 -0
- package/lib/config.js +19 -0
- package/lib/crossref.js +17 -2
- package/lib/doi.js +279 -43
- package/lib/errors.js +338 -0
- package/lib/format.js +53 -6
- package/lib/git.js +92 -0
- package/lib/import.js +41 -9
- package/lib/journals.js +28 -4
- package/lib/orcid.js +149 -0
- package/lib/pdf-comments.js +217 -0
- package/lib/pdf-import.js +446 -0
- package/lib/plugins.js +285 -0
- package/lib/review.js +109 -0
- package/lib/schema.js +368 -0
- package/lib/sections.js +3 -8
- package/lib/templates.js +218 -0
- package/lib/tui.js +437 -0
- package/lib/undo.js +236 -0
- package/lib/wordcomments.js +86 -39
- package/package.json +5 -3
- package/skill/REFERENCE.md +76 -18
- package/skill/SKILL.md +122 -27
- package/.rev-dictionary +0 -4
package/lib/schema.js
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema validation for rev.yaml configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* JSON Schema for rev.yaml
|
|
7
|
+
*/
|
|
8
|
+
export const revYamlSchema = {
|
|
9
|
+
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
10
|
+
title: 'rev.yaml configuration',
|
|
11
|
+
description: 'Configuration file for docrev document workflow',
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
title: {
|
|
15
|
+
type: 'string',
|
|
16
|
+
description: 'Document title',
|
|
17
|
+
},
|
|
18
|
+
version: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: 'Document version',
|
|
21
|
+
},
|
|
22
|
+
authors: {
|
|
23
|
+
type: 'array',
|
|
24
|
+
description: 'List of authors',
|
|
25
|
+
items: {
|
|
26
|
+
oneOf: [
|
|
27
|
+
{ type: 'string' },
|
|
28
|
+
{
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
name: { type: 'string' },
|
|
32
|
+
affiliation: { type: 'string' },
|
|
33
|
+
email: { type: 'string', format: 'email' },
|
|
34
|
+
orcid: { type: 'string', pattern: '^\\d{4}-\\d{4}-\\d{4}-\\d{3}[0-9X]$' },
|
|
35
|
+
},
|
|
36
|
+
required: ['name'],
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
sections: {
|
|
42
|
+
type: 'array',
|
|
43
|
+
description: 'Ordered list of section files to include',
|
|
44
|
+
items: { type: 'string', pattern: '.*\\.md$' },
|
|
45
|
+
},
|
|
46
|
+
bibliography: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
description: 'Path to bibliography file (.bib)',
|
|
49
|
+
pattern: '.*\\.bib$',
|
|
50
|
+
},
|
|
51
|
+
csl: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'Path to CSL citation style file',
|
|
54
|
+
},
|
|
55
|
+
crossref: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
description: 'pandoc-crossref settings',
|
|
58
|
+
properties: {
|
|
59
|
+
figureTitle: { type: 'string', default: 'Figure' },
|
|
60
|
+
tableTitle: { type: 'string', default: 'Table' },
|
|
61
|
+
figPrefix: {
|
|
62
|
+
oneOf: [
|
|
63
|
+
{ type: 'string' },
|
|
64
|
+
{ type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 2 },
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
tblPrefix: {
|
|
68
|
+
oneOf: [
|
|
69
|
+
{ type: 'string' },
|
|
70
|
+
{ type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 2 },
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
eqnPrefix: {
|
|
74
|
+
oneOf: [
|
|
75
|
+
{ type: 'string' },
|
|
76
|
+
{ type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 2 },
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
secPrefix: {
|
|
80
|
+
oneOf: [
|
|
81
|
+
{ type: 'string' },
|
|
82
|
+
{ type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 2 },
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
linkReferences: { type: 'boolean', default: true },
|
|
86
|
+
},
|
|
87
|
+
additionalProperties: true,
|
|
88
|
+
},
|
|
89
|
+
pdf: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
description: 'PDF output settings',
|
|
92
|
+
properties: {
|
|
93
|
+
template: { type: 'string' },
|
|
94
|
+
documentclass: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
enum: ['article', 'report', 'book', 'memoir', 'scrartcl', 'scrreprt', 'scrbook'],
|
|
97
|
+
default: 'article',
|
|
98
|
+
},
|
|
99
|
+
fontsize: {
|
|
100
|
+
type: 'string',
|
|
101
|
+
pattern: '^\\d{1,2}pt$',
|
|
102
|
+
default: '12pt',
|
|
103
|
+
},
|
|
104
|
+
geometry: { type: 'string', default: 'margin=1in' },
|
|
105
|
+
linestretch: { type: 'number', minimum: 1, maximum: 3, default: 1.5 },
|
|
106
|
+
numbersections: { type: 'boolean', default: false },
|
|
107
|
+
toc: { type: 'boolean', default: false },
|
|
108
|
+
header: { type: 'string' },
|
|
109
|
+
footer: { type: 'string' },
|
|
110
|
+
},
|
|
111
|
+
additionalProperties: true,
|
|
112
|
+
},
|
|
113
|
+
docx: {
|
|
114
|
+
type: 'object',
|
|
115
|
+
description: 'Word output settings',
|
|
116
|
+
properties: {
|
|
117
|
+
reference: { type: 'string', description: 'Reference document for styling' },
|
|
118
|
+
keepComments: { type: 'boolean', default: true },
|
|
119
|
+
toc: { type: 'boolean', default: false },
|
|
120
|
+
},
|
|
121
|
+
additionalProperties: true,
|
|
122
|
+
},
|
|
123
|
+
tex: {
|
|
124
|
+
type: 'object',
|
|
125
|
+
description: 'LaTeX output settings',
|
|
126
|
+
properties: {
|
|
127
|
+
standalone: { type: 'boolean', default: true },
|
|
128
|
+
},
|
|
129
|
+
additionalProperties: true,
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
additionalProperties: true,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Validate a value against a simple schema
|
|
137
|
+
* @param {*} value - Value to validate
|
|
138
|
+
* @param {object} schema - JSON Schema
|
|
139
|
+
* @param {string} path - Current path for error messages
|
|
140
|
+
* @returns {object[]} Array of validation errors
|
|
141
|
+
*/
|
|
142
|
+
function validateValue(value, schema, path = '') {
|
|
143
|
+
const errors = [];
|
|
144
|
+
|
|
145
|
+
// Handle oneOf
|
|
146
|
+
if (schema.oneOf) {
|
|
147
|
+
const validForAny = schema.oneOf.some((subSchema) => {
|
|
148
|
+
const subErrors = validateValue(value, subSchema, path);
|
|
149
|
+
return subErrors.length === 0;
|
|
150
|
+
});
|
|
151
|
+
if (!validForAny) {
|
|
152
|
+
errors.push({
|
|
153
|
+
path,
|
|
154
|
+
message: `Value does not match any allowed type`,
|
|
155
|
+
value,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return errors;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Type check
|
|
162
|
+
if (schema.type) {
|
|
163
|
+
const actualType = Array.isArray(value) ? 'array' : typeof value;
|
|
164
|
+
if (actualType !== schema.type) {
|
|
165
|
+
errors.push({
|
|
166
|
+
path,
|
|
167
|
+
message: `Expected ${schema.type}, got ${actualType}`,
|
|
168
|
+
value,
|
|
169
|
+
});
|
|
170
|
+
return errors; // Stop further validation if type is wrong
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// String validation
|
|
175
|
+
if (schema.type === 'string' && typeof value === 'string') {
|
|
176
|
+
if (schema.pattern) {
|
|
177
|
+
const regex = new RegExp(schema.pattern);
|
|
178
|
+
if (!regex.test(value)) {
|
|
179
|
+
errors.push({
|
|
180
|
+
path,
|
|
181
|
+
message: `Value "${value}" does not match pattern ${schema.pattern}`,
|
|
182
|
+
value,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (schema.enum && !schema.enum.includes(value)) {
|
|
187
|
+
errors.push({
|
|
188
|
+
path,
|
|
189
|
+
message: `Value "${value}" must be one of: ${schema.enum.join(', ')}`,
|
|
190
|
+
value,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Number validation
|
|
196
|
+
if (schema.type === 'number' && typeof value === 'number') {
|
|
197
|
+
if (schema.minimum !== undefined && value < schema.minimum) {
|
|
198
|
+
errors.push({
|
|
199
|
+
path,
|
|
200
|
+
message: `Value ${value} is less than minimum ${schema.minimum}`,
|
|
201
|
+
value,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
if (schema.maximum !== undefined && value > schema.maximum) {
|
|
205
|
+
errors.push({
|
|
206
|
+
path,
|
|
207
|
+
message: `Value ${value} is greater than maximum ${schema.maximum}`,
|
|
208
|
+
value,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Array validation
|
|
214
|
+
if (schema.type === 'array' && Array.isArray(value)) {
|
|
215
|
+
if (schema.minItems !== undefined && value.length < schema.minItems) {
|
|
216
|
+
errors.push({
|
|
217
|
+
path,
|
|
218
|
+
message: `Array must have at least ${schema.minItems} items`,
|
|
219
|
+
value,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
if (schema.maxItems !== undefined && value.length > schema.maxItems) {
|
|
223
|
+
errors.push({
|
|
224
|
+
path,
|
|
225
|
+
message: `Array must have at most ${schema.maxItems} items`,
|
|
226
|
+
value,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
if (schema.items) {
|
|
230
|
+
value.forEach((item, index) => {
|
|
231
|
+
errors.push(...validateValue(item, schema.items, `${path}[${index}]`));
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Object validation
|
|
237
|
+
if (schema.type === 'object' && typeof value === 'object' && value !== null) {
|
|
238
|
+
if (schema.properties) {
|
|
239
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
240
|
+
if (value[key] !== undefined) {
|
|
241
|
+
errors.push(...validateValue(value[key], propSchema, path ? `${path}.${key}` : key));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (schema.required) {
|
|
246
|
+
for (const key of schema.required) {
|
|
247
|
+
if (value[key] === undefined) {
|
|
248
|
+
errors.push({
|
|
249
|
+
path: path ? `${path}.${key}` : key,
|
|
250
|
+
message: `Required property "${key}" is missing`,
|
|
251
|
+
value: undefined,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return errors;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Validate rev.yaml configuration
|
|
263
|
+
* @param {object} config - Parsed configuration object
|
|
264
|
+
* @returns {{ valid: boolean, errors: object[], warnings: object[] }}
|
|
265
|
+
*/
|
|
266
|
+
export function validateConfig(config) {
|
|
267
|
+
const errors = validateValue(config, revYamlSchema);
|
|
268
|
+
const warnings = [];
|
|
269
|
+
|
|
270
|
+
// Additional semantic validations
|
|
271
|
+
if (config.sections && config.sections.length === 0) {
|
|
272
|
+
warnings.push({
|
|
273
|
+
path: 'sections',
|
|
274
|
+
message: 'No sections specified - build will auto-detect .md files',
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (config.bibliography && !config.bibliography.endsWith('.bib')) {
|
|
279
|
+
warnings.push({
|
|
280
|
+
path: 'bibliography',
|
|
281
|
+
message: 'Bibliography file should have .bib extension',
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (config.pdf?.linestretch && (config.pdf.linestretch < 1 || config.pdf.linestretch > 3)) {
|
|
286
|
+
warnings.push({
|
|
287
|
+
path: 'pdf.linestretch',
|
|
288
|
+
message: 'Line stretch values outside 1-3 range may produce unexpected results',
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Check for common typos
|
|
293
|
+
const knownKeys = Object.keys(revYamlSchema.properties);
|
|
294
|
+
for (const key of Object.keys(config)) {
|
|
295
|
+
if (key.startsWith('_')) continue; // Internal keys
|
|
296
|
+
if (!knownKeys.includes(key)) {
|
|
297
|
+
// Check for similar keys (possible typos)
|
|
298
|
+
const similar = knownKeys.find(
|
|
299
|
+
(k) => levenshtein(key.toLowerCase(), k.toLowerCase()) <= 2
|
|
300
|
+
);
|
|
301
|
+
if (similar) {
|
|
302
|
+
warnings.push({
|
|
303
|
+
path: key,
|
|
304
|
+
message: `Unknown property "${key}" - did you mean "${similar}"?`,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
valid: errors.length === 0,
|
|
312
|
+
errors,
|
|
313
|
+
warnings,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Format validation results for display
|
|
319
|
+
* @param {{ valid: boolean, errors: object[], warnings: object[] }} result
|
|
320
|
+
* @param {object} chalk - Chalk instance for coloring
|
|
321
|
+
* @returns {string}
|
|
322
|
+
*/
|
|
323
|
+
export function formatValidationResult(result, chalk) {
|
|
324
|
+
const lines = [];
|
|
325
|
+
|
|
326
|
+
if (result.errors.length > 0) {
|
|
327
|
+
lines.push(chalk.red('Configuration errors:'));
|
|
328
|
+
for (const error of result.errors) {
|
|
329
|
+
lines.push(chalk.red(` ✗ ${error.path}: ${error.message}`));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (result.warnings.length > 0) {
|
|
334
|
+
if (lines.length > 0) lines.push('');
|
|
335
|
+
lines.push(chalk.yellow('Warnings:'));
|
|
336
|
+
for (const warning of result.warnings) {
|
|
337
|
+
lines.push(chalk.yellow(` ! ${warning.path}: ${warning.message}`));
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (result.valid && result.warnings.length === 0) {
|
|
342
|
+
lines.push(chalk.green('✓ Configuration is valid'));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return lines.join('\n');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Levenshtein distance for typo detection
|
|
350
|
+
*/
|
|
351
|
+
function levenshtein(a, b) {
|
|
352
|
+
const matrix = Array(b.length + 1)
|
|
353
|
+
.fill(null)
|
|
354
|
+
.map(() => Array(a.length + 1).fill(null));
|
|
355
|
+
for (let i = 0; i <= a.length; i++) matrix[0][i] = i;
|
|
356
|
+
for (let j = 0; j <= b.length; j++) matrix[j][0] = j;
|
|
357
|
+
for (let j = 1; j <= b.length; j++) {
|
|
358
|
+
for (let i = 1; i <= a.length; i++) {
|
|
359
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
360
|
+
matrix[j][i] = Math.min(
|
|
361
|
+
matrix[j][i - 1] + 1,
|
|
362
|
+
matrix[j - 1][i] + 1,
|
|
363
|
+
matrix[j - 1][i - 1] + cost
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return matrix[b.length][a.length];
|
|
368
|
+
}
|
package/lib/sections.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
|
-
import
|
|
7
|
+
import YAML from 'yaml';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @typedef {Object} SectionConfig
|
|
@@ -124,7 +124,7 @@ function titleCase(str) {
|
|
|
124
124
|
*/
|
|
125
125
|
export function loadConfig(configPath) {
|
|
126
126
|
const content = fs.readFileSync(configPath, 'utf-8');
|
|
127
|
-
const config =
|
|
127
|
+
const config = YAML.parse(content);
|
|
128
128
|
|
|
129
129
|
// Normalize: convert string values to full config objects
|
|
130
130
|
const normalized = { ...config };
|
|
@@ -154,12 +154,7 @@ export function loadConfig(configPath) {
|
|
|
154
154
|
* @param {object} config
|
|
155
155
|
*/
|
|
156
156
|
export function saveConfig(configPath, config) {
|
|
157
|
-
const yamlStr =
|
|
158
|
-
indent: 2,
|
|
159
|
-
lineWidth: 100,
|
|
160
|
-
quotingType: '"',
|
|
161
|
-
forceQuotes: false,
|
|
162
|
-
});
|
|
157
|
+
const yamlStr = YAML.stringify(config, { indent: 2, lineWidth: 100 });
|
|
163
158
|
fs.writeFileSync(configPath, yamlStr, 'utf-8');
|
|
164
159
|
}
|
|
165
160
|
|
package/lib/templates.js
CHANGED
|
@@ -212,6 +212,135 @@ paper.md
|
|
|
212
212
|
directories: ['figures', 'tables'],
|
|
213
213
|
},
|
|
214
214
|
|
|
215
|
+
/**
|
|
216
|
+
* LaTeX-focused project with direct .tex output
|
|
217
|
+
*/
|
|
218
|
+
latex: {
|
|
219
|
+
name: 'LaTeX Project',
|
|
220
|
+
description: 'LaTeX-native with journal template support',
|
|
221
|
+
files: {
|
|
222
|
+
'rev.yaml': `# LaTeX Paper Configuration
|
|
223
|
+
title: "Paper Title"
|
|
224
|
+
authors:
|
|
225
|
+
- name: First Author
|
|
226
|
+
affiliation: University
|
|
227
|
+
email: author@example.edu
|
|
228
|
+
orcid: 0000-0000-0000-0000
|
|
229
|
+
|
|
230
|
+
sections:
|
|
231
|
+
- introduction.md
|
|
232
|
+
- methods.md
|
|
233
|
+
- results.md
|
|
234
|
+
- discussion.md
|
|
235
|
+
|
|
236
|
+
bibliography: references.bib
|
|
237
|
+
csl: null
|
|
238
|
+
|
|
239
|
+
# LaTeX-specific settings
|
|
240
|
+
pdf:
|
|
241
|
+
documentclass: article
|
|
242
|
+
classoption: [11pt, a4paper]
|
|
243
|
+
fontsize: 11pt
|
|
244
|
+
geometry: "margin=2.5cm"
|
|
245
|
+
linestretch: 1.5
|
|
246
|
+
numbersections: true
|
|
247
|
+
header-includes: |
|
|
248
|
+
\\usepackage{amsmath}
|
|
249
|
+
\\usepackage{graphicx}
|
|
250
|
+
\\usepackage{booktabs}
|
|
251
|
+
\\usepackage{hyperref}
|
|
252
|
+
\\usepackage{natbib}
|
|
253
|
+
|
|
254
|
+
# TEX output settings
|
|
255
|
+
tex:
|
|
256
|
+
standalone: true
|
|
257
|
+
keep-tex: true
|
|
258
|
+
`,
|
|
259
|
+
'introduction.md': `# Introduction
|
|
260
|
+
|
|
261
|
+
Background and motivation.
|
|
262
|
+
|
|
263
|
+
## Objectives
|
|
264
|
+
|
|
265
|
+
State your research questions.
|
|
266
|
+
|
|
267
|
+
`,
|
|
268
|
+
'methods.md': `# Materials and Methods
|
|
269
|
+
|
|
270
|
+
## Study Area
|
|
271
|
+
|
|
272
|
+
Describe the study area or data sources.
|
|
273
|
+
|
|
274
|
+
## Statistical Analysis
|
|
275
|
+
|
|
276
|
+
All analyses were performed in R [@R2024].
|
|
277
|
+
|
|
278
|
+
`,
|
|
279
|
+
'results.md': `# Results
|
|
280
|
+
|
|
281
|
+
Main findings presented here.
|
|
282
|
+
|
|
283
|
+
{#fig:main width=100%}
|
|
284
|
+
|
|
285
|
+
See @fig:main for the main results.
|
|
286
|
+
|
|
287
|
+
| Variable | Value | SE |
|
|
288
|
+
|----------|-------|------|
|
|
289
|
+
| A | 1.23 | 0.05 |
|
|
290
|
+
| B | 4.56 | 0.12 |
|
|
291
|
+
|
|
292
|
+
: Summary statistics {#tbl:summary}
|
|
293
|
+
|
|
294
|
+
`,
|
|
295
|
+
'discussion.md': `# Discussion
|
|
296
|
+
|
|
297
|
+
Interpretation of findings.
|
|
298
|
+
|
|
299
|
+
## Limitations
|
|
300
|
+
|
|
301
|
+
Study limitations.
|
|
302
|
+
|
|
303
|
+
## Conclusions
|
|
304
|
+
|
|
305
|
+
Key takeaways.
|
|
306
|
+
|
|
307
|
+
`,
|
|
308
|
+
'references.bib': `@Manual{R2024,
|
|
309
|
+
title = {R: A Language and Environment for Statistical Computing},
|
|
310
|
+
author = {{R Core Team}},
|
|
311
|
+
organization = {R Foundation for Statistical Computing},
|
|
312
|
+
address = {Vienna, Austria},
|
|
313
|
+
year = {2024},
|
|
314
|
+
url = {https://www.R-project.org/}
|
|
315
|
+
}
|
|
316
|
+
`,
|
|
317
|
+
'.gitignore': `# Build outputs
|
|
318
|
+
*.pdf
|
|
319
|
+
*.docx
|
|
320
|
+
paper.md
|
|
321
|
+
.paper-*.md
|
|
322
|
+
|
|
323
|
+
# Keep .tex for version control
|
|
324
|
+
# *.tex
|
|
325
|
+
|
|
326
|
+
# LaTeX auxiliary files
|
|
327
|
+
*.aux
|
|
328
|
+
*.bbl
|
|
329
|
+
*.blg
|
|
330
|
+
*.log
|
|
331
|
+
*.out
|
|
332
|
+
*.toc
|
|
333
|
+
*.fdb_latexmk
|
|
334
|
+
*.fls
|
|
335
|
+
*.synctex.gz
|
|
336
|
+
|
|
337
|
+
# System
|
|
338
|
+
.DS_Store
|
|
339
|
+
`,
|
|
340
|
+
},
|
|
341
|
+
directories: ['figures', 'tables'],
|
|
342
|
+
},
|
|
343
|
+
|
|
215
344
|
/**
|
|
216
345
|
* Review article structure
|
|
217
346
|
*/
|
|
@@ -303,3 +432,92 @@ export function listTemplates() {
|
|
|
303
432
|
description: template.description,
|
|
304
433
|
}));
|
|
305
434
|
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Convert string to title case for headers
|
|
438
|
+
* @param {string} str
|
|
439
|
+
* @returns {string}
|
|
440
|
+
*/
|
|
441
|
+
function titleCase(str) {
|
|
442
|
+
return str
|
|
443
|
+
.split(/[-_\s]+/)
|
|
444
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
445
|
+
.join(' ');
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Generate a custom template with specified sections
|
|
450
|
+
* @param {string[]} sections - Array of section names (without .md extension)
|
|
451
|
+
* @param {object} baseTemplate - Base template to extend (default: paper)
|
|
452
|
+
* @returns {object}
|
|
453
|
+
*/
|
|
454
|
+
export function generateCustomTemplate(sections, baseTemplate = TEMPLATES.paper) {
|
|
455
|
+
const files = {};
|
|
456
|
+
|
|
457
|
+
// Generate rev.yaml with custom sections
|
|
458
|
+
const sectionsList = sections.map((s) => ` - ${s}.md`).join('\n');
|
|
459
|
+
files['rev.yaml'] = `# Paper configuration
|
|
460
|
+
title: "Your Paper Title"
|
|
461
|
+
authors:
|
|
462
|
+
- name: First Author
|
|
463
|
+
affiliation: Institution
|
|
464
|
+
email: author@example.com
|
|
465
|
+
|
|
466
|
+
# Section files in order
|
|
467
|
+
sections:
|
|
468
|
+
${sectionsList}
|
|
469
|
+
|
|
470
|
+
# Bibliography (optional)
|
|
471
|
+
bibliography: references.bib
|
|
472
|
+
csl: null # uses default CSL
|
|
473
|
+
|
|
474
|
+
# Cross-reference settings
|
|
475
|
+
crossref:
|
|
476
|
+
figureTitle: Figure
|
|
477
|
+
tableTitle: Table
|
|
478
|
+
figPrefix: [Fig., Figs.]
|
|
479
|
+
tblPrefix: [Table, Tables]
|
|
480
|
+
linkReferences: true
|
|
481
|
+
|
|
482
|
+
# PDF output settings
|
|
483
|
+
pdf:
|
|
484
|
+
documentclass: article
|
|
485
|
+
fontsize: 12pt
|
|
486
|
+
geometry: margin=1in
|
|
487
|
+
linestretch: 1.5
|
|
488
|
+
numbersections: false
|
|
489
|
+
|
|
490
|
+
# Word output settings
|
|
491
|
+
docx:
|
|
492
|
+
reference: null # path to reference.docx template
|
|
493
|
+
keepComments: true
|
|
494
|
+
`;
|
|
495
|
+
|
|
496
|
+
// Generate section files
|
|
497
|
+
for (const section of sections) {
|
|
498
|
+
const header = titleCase(section);
|
|
499
|
+
files[`${section}.md`] = `# ${header}
|
|
500
|
+
|
|
501
|
+
`;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Add common files
|
|
505
|
+
files['references.bib'] = baseTemplate.files['references.bib'] || '';
|
|
506
|
+
files['.gitignore'] = baseTemplate.files['.gitignore'] || `# Build outputs
|
|
507
|
+
*.pdf
|
|
508
|
+
*.docx
|
|
509
|
+
*.tex
|
|
510
|
+
paper.md
|
|
511
|
+
.paper-*.md
|
|
512
|
+
|
|
513
|
+
# System
|
|
514
|
+
.DS_Store
|
|
515
|
+
`;
|
|
516
|
+
|
|
517
|
+
return {
|
|
518
|
+
name: 'Custom',
|
|
519
|
+
description: 'Custom sections',
|
|
520
|
+
files,
|
|
521
|
+
directories: baseTemplate.directories || ['figures'],
|
|
522
|
+
};
|
|
523
|
+
}
|