@markup-carve/pandoc-carve 0.1.4 → 0.1.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.
- package/README.md +43 -22
- package/dist/ast-json.js +15 -0
- package/dist/cli.js +125 -12
- package/dist/convert.d.ts +2 -1
- package/dist/convert.js +8 -6
- package/dist/diagnostics.d.ts +13 -1
- package/dist/diagnostics.js +64 -46
- package/dist/includes.d.ts +14 -0
- package/dist/includes.js +35 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +52 -2
- package/dist/reverse.js +1 -1
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -46,6 +46,12 @@ pandoc-carve doc.crv -t latex -s -o doc.tex
|
|
|
46
46
|
# Just the Pandoc JSON AST (no pandoc needed)
|
|
47
47
|
pandoc-carve doc.crv -t json
|
|
48
48
|
|
|
49
|
+
# Expand contained includes; named files default to their own directory
|
|
50
|
+
pandoc-carve book/main.crv -t json --include-root "$PWD/book"
|
|
51
|
+
|
|
52
|
+
# Keep directives literal for a named file
|
|
53
|
+
pandoc-carve book/main.crv -t json --no-includes
|
|
54
|
+
|
|
49
55
|
# Read from stdin, pass extra args through to pandoc after --
|
|
50
56
|
cat doc.crv | pandoc-carve - -t latex -- --toc
|
|
51
57
|
|
|
@@ -62,25 +68,51 @@ carve doc.crv --to-json | pandoc-carve - -f carve-json -t latex
|
|
|
62
68
|
```
|
|
63
69
|
|
|
64
70
|
Anything Carve cannot map faithfully is reported on stderr as a
|
|
65
|
-
`pandoc-carve: degraded ...` warning - nothing degrades silently.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
`pandoc-carve: degraded ...` warning - nothing degrades silently. Unresolved or
|
|
72
|
+
refused includes are reported as dropped, while safe include renames and heading
|
|
73
|
+
clamps are normalized. For migration
|
|
74
|
+
automation, `--diagnostics report.json` writes the same findings in a versioned
|
|
75
|
+
`{ schemaVersion, sourceFormat, diagnostics }` JSON envelope without mixing
|
|
76
|
+
them into document output. `--fail-on-loss` exits with code 3 for degraded or
|
|
77
|
+
dropped findings, while preserved and normalized findings do not fail CI.
|
|
78
|
+
Imports through a Pandoc reader other than `-f json` also exit
|
|
79
|
+
3 because fidelity before the Pandoc JSON boundary cannot be verified; stderr
|
|
80
|
+
names that worst-case gate explicitly. Use `--diagnostics -` for JSON on stderr.
|
|
70
81
|
|
|
71
82
|
## API
|
|
72
83
|
|
|
73
84
|
```js
|
|
74
85
|
import { carveToPandoc, carveToPandocJson } from '@markup-carve/pandoc-carve';
|
|
75
86
|
|
|
76
|
-
const { doc, warnings, diagnostics } = carveToPandoc('Hello /world/!');
|
|
87
|
+
const { doc, warnings, diagnostics, report } = carveToPandoc('Hello /world/!');
|
|
77
88
|
// doc = { 'pandoc-api-version': [1, 23, 1], meta: {...}, blocks: [...] }
|
|
78
89
|
// warnings = ['degraded: ...'] for lossy constructs
|
|
79
|
-
// diagnostics = [{ code, direction, severity,
|
|
90
|
+
// diagnostics = [{ code, direction, class, severity, fidelity, confidence, message, ... }]
|
|
91
|
+
// report = { schemaVersion: 2, sourceFormat: 'carve', diagnostics }
|
|
80
92
|
|
|
81
93
|
const json = carveToPandocJson('Hello /world/!'); // stringified doc
|
|
82
94
|
```
|
|
83
95
|
|
|
96
|
+
String conversion leaves `{{ path }}` directives literal. File-backed hosts can
|
|
97
|
+
opt into contained expansion and receive warnings and dependency identities:
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
import { readFileSync } from 'node:fs';
|
|
101
|
+
import { carveToPandocWithIncludes } from '@markup-carve/pandoc-carve/node';
|
|
102
|
+
|
|
103
|
+
const sourcePath = '/srv/book/main.crv';
|
|
104
|
+
const result = carveToPandocWithIncludes(readFileSync(sourcePath, 'utf8'), {
|
|
105
|
+
includeRoot: '/srv/book',
|
|
106
|
+
sourcePath,
|
|
107
|
+
});
|
|
108
|
+
// includeWarnings and suppressedIncludeWarnings describe expansion diagnostics;
|
|
109
|
+
// dependencies contains resolved and unresolved include identities.
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Both API paths must be absolute, and `sourcePath` must stay inside
|
|
113
|
+
`includeRoot`. The CLI accepts ordinary relative input paths but requires an
|
|
114
|
+
absolute `--include-root`. Stdin stays literal unless that option is supplied.
|
|
115
|
+
|
|
84
116
|
What the converter reads is the **serialized AST of the Carve spec's PART 12**
|
|
85
117
|
(the shape `resources/ast-schema.json` pins), not any implementation's runtime
|
|
86
118
|
tree - so a document another engine already parsed converts the same way,
|
|
@@ -103,7 +135,7 @@ serializer), so output formatting carries fmt's guarantees:
|
|
|
103
135
|
```js
|
|
104
136
|
import { pandocToCarve, pandocToCarveAst } from '@markup-carve/pandoc-carve';
|
|
105
137
|
|
|
106
|
-
const { carve, warnings, diagnostics } = pandocToCarve(pandocJsonString);
|
|
138
|
+
const { carve, warnings, diagnostics, report } = pandocToCarve(pandocJsonString);
|
|
107
139
|
|
|
108
140
|
// Preserve structured fields that Carve 0.1 source cannot spell, including
|
|
109
141
|
// Pandoc's optional short figure/table caption.
|
|
@@ -173,7 +205,8 @@ every target that emits a resolvable URL. Pandoc's targets are not HTML, but
|
|
|
173
205
|
`pandoc -f json -t html` is one command away - passing the scheme through here
|
|
174
206
|
would not be a narrower policy, it would be the same sink one step removed.
|
|
175
207
|
|
|
176
|
-
The diagnostic is `lossy`, so `--fail-on-loss` stops on
|
|
208
|
+
The diagnostic class is `lossy`, so `--fail-on-loss` stops on its dropped
|
|
209
|
+
fidelity and a caller reading
|
|
177
210
|
`diagnostics` can act on it. The scheme list and the scheme probe are mirrored
|
|
178
211
|
from the engine rather than invented, and a test drives the engine's own writer
|
|
179
212
|
over both to keep the mirror honest - the probe strips control characters and
|
|
@@ -356,16 +389,4 @@ a processor with neither extension enabled would render.
|
|
|
356
389
|
|
|
357
390
|
## Development
|
|
358
391
|
|
|
359
|
-
|
|
360
|
-
submodule:
|
|
361
|
-
|
|
362
|
-
```sh
|
|
363
|
-
git clone --recurse-submodules https://github.com/markup-carve/pandoc-carve
|
|
364
|
-
# or, in an existing clone:
|
|
365
|
-
git submodule update --init
|
|
366
|
-
npm ci && npm test
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
Without the submodule the corpus tests FAIL rather than skip - a skipped corpus
|
|
370
|
-
reads like a converted one, which is how a stack overflow reachable from a
|
|
371
|
-
two-word document stayed live on the published package.
|
|
392
|
+
Contributor setup, testing, and maintenance notes are in the [development guide](docs/development.md).
|
package/dist/ast-json.js
CHANGED
|
@@ -47,6 +47,8 @@ const CHILD_FIELDS = [
|
|
|
47
47
|
'shortCaption',
|
|
48
48
|
'title',
|
|
49
49
|
'target',
|
|
50
|
+
'old',
|
|
51
|
+
'new',
|
|
50
52
|
];
|
|
51
53
|
function isObject(value) {
|
|
52
54
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -107,6 +109,19 @@ function normalize(node) {
|
|
|
107
109
|
// reads the wire has no reason to know the other spelling existed.
|
|
108
110
|
set('type', 'critic_comment');
|
|
109
111
|
break;
|
|
112
|
+
case 'substitution': {
|
|
113
|
+
if (!Array.isArray(current['old']) && typeof current['oldText'] === 'string') {
|
|
114
|
+
set('old', [{ type: 'text', value: current['oldText'] }]);
|
|
115
|
+
}
|
|
116
|
+
if (!Array.isArray(current['new']) && typeof current['newText'] === 'string') {
|
|
117
|
+
set('new', [{ type: 'text', value: current['newText'] }]);
|
|
118
|
+
}
|
|
119
|
+
if (current['oldText'] !== undefined || current['newText'] !== undefined) {
|
|
120
|
+
const { oldText: _oldText, newText: _newText, ...rest } = out ?? { ...node };
|
|
121
|
+
out = rest;
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
110
125
|
case 'footnote': {
|
|
111
126
|
// The pre-split inline node (carve#405), whose name COLLIDES with
|
|
112
127
|
// the wire `footnote` - which is the DEFINITION block, carrying
|
package/dist/cli.js
CHANGED
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Everything after `--` is passed through to pandoc verbatim.
|
|
10
10
|
*/
|
|
11
|
-
import { readFileSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
12
13
|
import { carveAstToPandoc, carveToPandoc } from './index.js';
|
|
13
|
-
import {
|
|
14
|
+
import { carveToPandocWithIncludes } from './includes.js';
|
|
15
|
+
import { hasLoss, migrationReport } from './diagnostics.js';
|
|
14
16
|
function usage(exitCode) {
|
|
15
17
|
const text = `Usage: pandoc-carve <input | -> [options] [-- pandoc-args...]
|
|
16
18
|
|
|
@@ -36,6 +38,8 @@ Common options:
|
|
|
36
38
|
human warnings are suppressed)
|
|
37
39
|
--fail-on-loss exit 3 when lossy or unsupported diagnostics are present
|
|
38
40
|
--symbols FILE JSON map resolving :name: symbols to text (export)
|
|
41
|
+
--include-root DIR containment root for includes (defaults to input folder)
|
|
42
|
+
--no-includes leave include directives literal
|
|
39
43
|
--pandoc PATH pandoc executable (default: $PANDOC or "pandoc")
|
|
40
44
|
-h, --help show this help
|
|
41
45
|
`;
|
|
@@ -50,6 +54,7 @@ function parseArgs(argv) {
|
|
|
50
54
|
roundtrip: false,
|
|
51
55
|
listTable: true,
|
|
52
56
|
citations: true,
|
|
57
|
+
includes: true,
|
|
53
58
|
failOnLoss: false,
|
|
54
59
|
pandocPath: process.env.PANDOC ?? 'pandoc',
|
|
55
60
|
passthrough: [],
|
|
@@ -88,6 +93,12 @@ function parseArgs(argv) {
|
|
|
88
93
|
else if (a === '--symbols') {
|
|
89
94
|
args.symbolsFile = argv[++i] ?? usage(1);
|
|
90
95
|
}
|
|
96
|
+
else if (a === '--include-root') {
|
|
97
|
+
args.includeRoot = argv[++i] ?? usage(1);
|
|
98
|
+
}
|
|
99
|
+
else if (a === '--no-includes') {
|
|
100
|
+
args.includes = false;
|
|
101
|
+
}
|
|
91
102
|
else if (a === '--diagnostics') {
|
|
92
103
|
args.diagnosticsFile = argv[++i] ?? usage(1);
|
|
93
104
|
}
|
|
@@ -112,6 +123,9 @@ function parseArgs(argv) {
|
|
|
112
123
|
}
|
|
113
124
|
async function main() {
|
|
114
125
|
const args = parseArgs(process.argv.slice(2));
|
|
126
|
+
if (args.from && args.from !== 'carve' && (args.includeRoot || !args.includes)) {
|
|
127
|
+
throw new Error('--include-root and --no-includes only apply to Carve source input');
|
|
128
|
+
}
|
|
115
129
|
// `carve` and `carve-json` are the two EXPORT inputs - Carve source, and the
|
|
116
130
|
// serialized AST any engine writes with --to-json. Every other `-f` names a
|
|
117
131
|
// pandoc reader, which is the import direction.
|
|
@@ -128,17 +142,38 @@ async function main() {
|
|
|
128
142
|
citations: args.citations,
|
|
129
143
|
symbols,
|
|
130
144
|
};
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
145
|
+
if (args.includeRoot && !path.isAbsolute(args.includeRoot)) {
|
|
146
|
+
throw new Error('--include-root must be an absolute path');
|
|
147
|
+
}
|
|
148
|
+
let includeResult;
|
|
149
|
+
let converted;
|
|
150
|
+
if (args.from === 'carve-json') {
|
|
151
|
+
converted = carveAstToPandoc(source, options);
|
|
152
|
+
}
|
|
153
|
+
else if (args.includes && (args.input !== '-' || args.includeRoot)) {
|
|
154
|
+
includeResult = carveToPandocWithIncludes(source, {
|
|
155
|
+
includeRoot: path.resolve(args.includeRoot ?? path.dirname(args.input)),
|
|
156
|
+
...(args.input === '-' ? {} : { sourcePath: path.resolve(args.input) }),
|
|
157
|
+
}, options);
|
|
158
|
+
converted = includeResult;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
converted = carveToPandoc(source, options);
|
|
162
|
+
}
|
|
163
|
+
const includeWarnings = includeResult?.includeWarnings ?? [];
|
|
164
|
+
const suppressedIncludeWarnings = includeResult?.suppressedIncludeWarnings ?? 0;
|
|
165
|
+
const { doc, warnings, diagnostics } = converted;
|
|
166
|
+
const root = args.includeRoot ?? (args.input === '-' ? undefined : path.dirname(path.resolve(args.input)));
|
|
167
|
+
const includeDiagnostics = diagnosticsForIncludes(includeWarnings, suppressedIncludeWarnings, root);
|
|
168
|
+
const allDiagnostics = [...diagnostics, ...includeDiagnostics];
|
|
169
|
+
report(args, warnings, allDiagnostics, 'carve', includeWarnings, suppressedIncludeWarnings, root);
|
|
135
170
|
const json = JSON.stringify(doc);
|
|
136
171
|
if (args.to === 'json') {
|
|
137
172
|
if (args.output)
|
|
138
173
|
writeFileSync(args.output, json + '\n');
|
|
139
174
|
else
|
|
140
175
|
process.stdout.write(json + '\n');
|
|
141
|
-
if (args.failOnLoss && hasLoss(
|
|
176
|
+
if (args.failOnLoss && hasLoss(allDiagnostics))
|
|
142
177
|
process.exitCode = 3;
|
|
143
178
|
return;
|
|
144
179
|
}
|
|
@@ -172,7 +207,7 @@ async function main() {
|
|
|
172
207
|
}
|
|
173
208
|
throw result.error;
|
|
174
209
|
}
|
|
175
|
-
process.exit(result.status || (args.failOnLoss && hasLoss(
|
|
210
|
+
process.exit(result.status || (args.failOnLoss && hasLoss(allDiagnostics) ? 3 : 0));
|
|
176
211
|
}
|
|
177
212
|
/** Reverse direction: pandoc-readable input -> Carve source. */
|
|
178
213
|
async function importToCarve(args) {
|
|
@@ -201,17 +236,29 @@ async function importToCarve(args) {
|
|
|
201
236
|
}
|
|
202
237
|
const { pandocToCarve } = await import('./index.js');
|
|
203
238
|
const { carve, warnings, diagnostics } = pandocToCarve(json);
|
|
204
|
-
|
|
239
|
+
const reportDiagnostics = args.from === 'json' ? diagnostics : [
|
|
240
|
+
...diagnostics,
|
|
241
|
+
{
|
|
242
|
+
code: 'fidelity-unverified',
|
|
243
|
+
direction: 'pandoc-to-carve',
|
|
244
|
+
class: 'unsupported',
|
|
245
|
+
severity: 'error',
|
|
246
|
+
fidelity: 'dropped',
|
|
247
|
+
confidence: 'fallback',
|
|
248
|
+
message: 'Fidelity before the Pandoc JSON boundary was not reported; dropped is a conservative worst-case release-gate classification',
|
|
249
|
+
},
|
|
250
|
+
];
|
|
251
|
+
report(args, warnings, reportDiagnostics, 'pandoc-json');
|
|
205
252
|
if (args.output)
|
|
206
253
|
writeFileSync(args.output, carve);
|
|
207
254
|
else
|
|
208
255
|
process.stdout.write(carve);
|
|
209
|
-
if (args.failOnLoss && hasLoss(
|
|
256
|
+
if (args.failOnLoss && hasLoss(reportDiagnostics))
|
|
210
257
|
process.exitCode = 3;
|
|
211
258
|
}
|
|
212
|
-
function report(args, warnings, diagnostics) {
|
|
259
|
+
function report(args, warnings, diagnostics, sourceFormat, includeWarnings = [], suppressedIncludeWarnings = 0, includeRoot) {
|
|
213
260
|
if (args.diagnosticsFile !== undefined) {
|
|
214
|
-
const json = JSON.stringify(diagnostics, null, 2) + '\n';
|
|
261
|
+
const json = JSON.stringify(migrationReport(diagnostics, sourceFormat), null, 2) + '\n';
|
|
215
262
|
if (args.diagnosticsFile === '-')
|
|
216
263
|
process.stderr.write(json);
|
|
217
264
|
else
|
|
@@ -220,6 +267,72 @@ function report(args, warnings, diagnostics) {
|
|
|
220
267
|
}
|
|
221
268
|
for (const warning of warnings)
|
|
222
269
|
process.stderr.write(`pandoc-carve: degraded: ${warning}\n`);
|
|
270
|
+
for (const warning of includeWarnings) {
|
|
271
|
+
const file = displayIncludeFile(warning.file, includeRoot, args.input);
|
|
272
|
+
process.stderr.write(`pandoc-carve: ${file}:${warning.line}:${warning.column} ${warning.rule} - ${warning.message}\n`);
|
|
273
|
+
}
|
|
274
|
+
if (suppressedIncludeWarnings) {
|
|
275
|
+
process.stderr.write(`pandoc-carve: ${suppressedIncludeWarnings} additional include warning(s) suppressed\n`);
|
|
276
|
+
}
|
|
277
|
+
for (const diagnostic of diagnostics) {
|
|
278
|
+
if (diagnostic.code === 'fidelity-unverified') {
|
|
279
|
+
process.stderr.write(`pandoc-carve: dropped: ${diagnostic.message}\n`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function diagnosticsForIncludes(warnings, suppressed, root) {
|
|
284
|
+
const diagnostics = warnings.map((warning) => {
|
|
285
|
+
const normalized = NORMALIZED_INCLUDE_RULES.has(warning.rule);
|
|
286
|
+
const file = warning.file ? displayIncludeFile(warning.file, root, '') : undefined;
|
|
287
|
+
return {
|
|
288
|
+
code: warning.rule,
|
|
289
|
+
direction: 'carve-to-pandoc',
|
|
290
|
+
class: normalized ? 'normalized' : 'lossy',
|
|
291
|
+
severity: normalized ? 'info' : 'error',
|
|
292
|
+
fidelity: normalized ? 'normalized' : 'dropped',
|
|
293
|
+
confidence: 'exact',
|
|
294
|
+
message: warning.message,
|
|
295
|
+
details: {
|
|
296
|
+
...(file ? { file } : {}),
|
|
297
|
+
line: warning.line,
|
|
298
|
+
column: warning.column,
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
});
|
|
302
|
+
if (suppressed)
|
|
303
|
+
diagnostics.push({
|
|
304
|
+
code: 'include-warnings-suppressed',
|
|
305
|
+
direction: 'carve-to-pandoc',
|
|
306
|
+
class: 'degraded',
|
|
307
|
+
severity: 'warning',
|
|
308
|
+
fidelity: 'degraded',
|
|
309
|
+
confidence: 'exact',
|
|
310
|
+
message: `${suppressed} additional include warning(s) suppressed`,
|
|
311
|
+
details: { count: suppressed },
|
|
312
|
+
});
|
|
313
|
+
return diagnostics;
|
|
314
|
+
}
|
|
315
|
+
const NORMALIZED_INCLUDE_RULES = new Set([
|
|
316
|
+
'include-footnote-rename',
|
|
317
|
+
'include-heading-clamp',
|
|
318
|
+
'include-heading-id-rename',
|
|
319
|
+
]);
|
|
320
|
+
function displayIncludeFile(file, root, fallback) {
|
|
321
|
+
if (!file || !root)
|
|
322
|
+
return fallback;
|
|
323
|
+
const canonicalRoot = realpathSync(root);
|
|
324
|
+
let canonicalFile = file;
|
|
325
|
+
try {
|
|
326
|
+
canonicalFile = realpathSync(file);
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
// A resolver may report an unresolved identity. Keep it contained below.
|
|
330
|
+
}
|
|
331
|
+
const relative = path.relative(canonicalRoot, canonicalFile);
|
|
332
|
+
if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
333
|
+
return '[outside-include-root]';
|
|
334
|
+
}
|
|
335
|
+
return `[include-root]/${relative || '.'}`;
|
|
223
336
|
}
|
|
224
337
|
main().catch((err) => {
|
|
225
338
|
process.stderr.write(`pandoc-carve: ${err instanceof Error ? err.message : String(err)}\n`);
|
package/dist/convert.d.ts
CHANGED
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import type { CarveAstDocument } from './ast-json.js';
|
|
13
13
|
import * as P from './pandoc.js';
|
|
14
|
-
import { type ConversionDiagnostic } from './diagnostics.js';
|
|
14
|
+
import { type ConversionDiagnostic, type MigrationReport } from './diagnostics.js';
|
|
15
15
|
export interface ConvertResult {
|
|
16
16
|
doc: P.PandocDoc;
|
|
17
17
|
warnings: string[];
|
|
18
18
|
diagnostics: ConversionDiagnostic[];
|
|
19
|
+
report: MigrationReport;
|
|
19
20
|
}
|
|
20
21
|
export interface ConvertOptions {
|
|
21
22
|
/**
|
package/dist/convert.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import * as P from './pandoc.js';
|
|
13
13
|
import { readRowGroups } from './row-groups.js';
|
|
14
|
-
import { diagnostic } from './diagnostics.js';
|
|
14
|
+
import { diagnostic, migrationReport } from './diagnostics.js';
|
|
15
15
|
import { provenanceAttr } from './provenance.js';
|
|
16
16
|
import { blankDeniedDestination, probeScheme } from './url-scheme.js';
|
|
17
17
|
import { isDangerousAttrName, renderedAttrValue } from './attribute-sanitize.js';
|
|
@@ -710,13 +710,13 @@ function inline(ctx, n) {
|
|
|
710
710
|
case 'delete':
|
|
711
711
|
return [P.Span(P.attr(undefined, ['deletion']), kids(ctx, n))];
|
|
712
712
|
case 'substitution': {
|
|
713
|
-
const
|
|
714
|
-
const
|
|
713
|
+
const oldContent = Array.isArray(n.old) ? inlines(ctx, n.old) : [];
|
|
714
|
+
const newContent = Array.isArray(n.new) ? inlines(ctx, n.new) : [];
|
|
715
715
|
return [
|
|
716
716
|
P.Span(P.attr(undefined, ['substitution']), [
|
|
717
|
-
P.Span(P.attr(undefined, ['deletion']),
|
|
717
|
+
P.Span(P.attr(undefined, ['deletion']), oldContent),
|
|
718
718
|
P.Str('→'),
|
|
719
|
-
P.Span(P.attr(undefined, ['insertion']),
|
|
719
|
+
P.Span(P.attr(undefined, ['insertion']), newContent),
|
|
720
720
|
]),
|
|
721
721
|
];
|
|
722
722
|
}
|
|
@@ -2038,6 +2038,7 @@ export function convert(ast, options = {}) {
|
|
|
2038
2038
|
// sits relative to its target.
|
|
2039
2039
|
collectCrossrefTargets(ctx, body, new Map());
|
|
2040
2040
|
const meta = parseMeta(ctx, frontmatter);
|
|
2041
|
+
const diagnostics = ctx.diagnostics;
|
|
2041
2042
|
return {
|
|
2042
2043
|
doc: {
|
|
2043
2044
|
'pandoc-api-version': [...P.PANDOC_API_VERSION],
|
|
@@ -2045,7 +2046,8 @@ export function convert(ast, options = {}) {
|
|
|
2045
2046
|
blocks: blocks(ctx, body),
|
|
2046
2047
|
},
|
|
2047
2048
|
warnings: ctx.warnings,
|
|
2048
|
-
diagnostics
|
|
2049
|
+
diagnostics,
|
|
2050
|
+
report: migrationReport(diagnostics, 'carve'),
|
|
2049
2051
|
};
|
|
2050
2052
|
}
|
|
2051
2053
|
/**
|
package/dist/diagnostics.d.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
export type DiagnosticDirection = 'carve-to-pandoc' | 'pandoc-to-carve';
|
|
2
|
-
export type
|
|
2
|
+
export type DiagnosticClass = 'lossy' | 'degraded' | 'normalized' | 'unsupported';
|
|
3
|
+
export type DiagnosticSeverity = 'info' | 'warning' | 'error';
|
|
4
|
+
export type MigrationFidelity = 'preserved' | 'normalized' | 'degraded' | 'dropped';
|
|
5
|
+
export type MigrationConfidence = 'exact' | 'inferred' | 'fallback';
|
|
3
6
|
export interface ConversionDiagnostic {
|
|
4
7
|
/** Stable, machine-readable identifier. */
|
|
5
8
|
code: string;
|
|
6
9
|
direction: DiagnosticDirection;
|
|
10
|
+
class: DiagnosticClass;
|
|
7
11
|
severity: DiagnosticSeverity;
|
|
12
|
+
fidelity: MigrationFidelity;
|
|
13
|
+
confidence: MigrationConfidence;
|
|
8
14
|
message: string;
|
|
9
15
|
/** Construct-specific values useful to migration tooling. */
|
|
10
16
|
details?: Record<string, unknown>;
|
|
@@ -14,4 +20,10 @@ export interface ConversionDiagnostic {
|
|
|
14
20
|
sourceLocation?: unknown;
|
|
15
21
|
}
|
|
16
22
|
export declare function diagnostic(direction: DiagnosticDirection, message: string, details?: Record<string, unknown>, sourceLocation?: unknown): ConversionDiagnostic;
|
|
23
|
+
export interface MigrationReport {
|
|
24
|
+
schemaVersion: 2;
|
|
25
|
+
sourceFormat: string;
|
|
26
|
+
diagnostics: ConversionDiagnostic[];
|
|
27
|
+
}
|
|
28
|
+
export declare function migrationReport(diagnostics: ConversionDiagnostic[], sourceFormat?: string): MigrationReport;
|
|
17
29
|
export declare function hasLoss(diagnostics: ConversionDiagnostic[]): boolean;
|
package/dist/diagnostics.js
CHANGED
|
@@ -1,49 +1,57 @@
|
|
|
1
1
|
// Ordered from specific to general. Codes are API: add rules, never rename them.
|
|
2
2
|
const RULES = [
|
|
3
|
-
{ test: /^comment:/, code: 'comment-dropped',
|
|
4
|
-
{ test: /^inline: unknown node type/, code: 'unknown-carve-inline',
|
|
5
|
-
{ test: /^block: unknown node type/, code: 'unknown-carve-block',
|
|
6
|
-
{ test: /^inline: pandoc node/, code: 'unsupported-pandoc-inline',
|
|
7
|
-
{ test: /^block: pandoc node/, code: 'unsupported-pandoc-block',
|
|
8
|
-
{ test: /^citation: .*typed locator/, code: 'citation-locator-flattened',
|
|
9
|
-
{ test: /^citation: .*suppresses its author/, code: 'citation-mode-normalized',
|
|
10
|
-
{ test: /^Cite mixes /, code: 'citation-mode-normalized',
|
|
11
|
-
{ test: /^Cite mapped /, code: 'citation-bibliography-not-emitted',
|
|
12
|
-
{ test: /^SmallCaps /, code: 'smallcaps-degraded',
|
|
13
|
-
{ test: /^short caption:/, code: 'short-caption-source-unavailable',
|
|
14
|
-
{ test: /^figure:/, code: 'figure-unwrapped',
|
|
15
|
-
{ test: /^figure group: short caption/, code: 'figure-group-short-caption-dropped',
|
|
16
|
-
{ test: /^frontmatter: format/, code: 'frontmatter-format-unsupported',
|
|
17
|
-
{ test: /^frontmatter:
|
|
18
|
-
{ test: /^frontmatter:
|
|
19
|
-
{ test: /^
|
|
20
|
-
{ test: /^meta
|
|
21
|
-
{ test: /^
|
|
22
|
-
{ test: /^definition list
|
|
23
|
-
{ test: /^
|
|
24
|
-
{ test: /^list
|
|
25
|
-
{ test: /^
|
|
26
|
-
{ test: /^
|
|
27
|
-
{ test: /^
|
|
28
|
-
{ test: /^
|
|
29
|
-
{ test: /^
|
|
30
|
-
{ test: /^attribute: unsafe
|
|
31
|
-
{ test: /^
|
|
32
|
-
{ test:
|
|
33
|
-
{ test: /^
|
|
34
|
-
{ test: /^
|
|
35
|
-
{ test: /^list-table:
|
|
36
|
-
{ test: /^list-table:
|
|
37
|
-
{ test: /^list-table:
|
|
38
|
-
{ test: /^list-table: the body groups
|
|
39
|
-
{ test: /^list-table:
|
|
40
|
-
{ test: /^table:
|
|
41
|
-
{ test: /^table:
|
|
42
|
-
{ test: /^table:
|
|
43
|
-
{ test: /^table:
|
|
44
|
-
{ test: /^table:
|
|
45
|
-
{ test: /^table: rowspan
|
|
46
|
-
{ test: /^table
|
|
3
|
+
{ test: /^comment:/, code: 'comment-dropped', class: 'lossy' },
|
|
4
|
+
{ test: /^inline: unknown node type/, code: 'unknown-carve-inline', class: 'degraded' },
|
|
5
|
+
{ test: /^block: unknown node type/, code: 'unknown-carve-block', class: 'degraded' },
|
|
6
|
+
{ test: /^inline: pandoc node/, code: 'unsupported-pandoc-inline', class: 'unsupported' },
|
|
7
|
+
{ test: /^block: pandoc node/, code: 'unsupported-pandoc-block', class: 'unsupported' },
|
|
8
|
+
{ test: /^citation: .*typed locator/, code: 'citation-locator-flattened', class: 'normalized' },
|
|
9
|
+
{ test: /^citation: .*suppresses its author/, code: 'citation-mode-normalized', class: 'normalized' },
|
|
10
|
+
{ test: /^Cite mixes /, code: 'citation-mode-normalized', class: 'normalized' },
|
|
11
|
+
{ test: /^Cite mapped /, code: 'citation-bibliography-not-emitted', class: 'degraded' },
|
|
12
|
+
{ test: /^SmallCaps /, code: 'smallcaps-degraded', class: 'degraded' },
|
|
13
|
+
{ test: /^short caption:/, code: 'short-caption-source-unavailable', class: 'unsupported' },
|
|
14
|
+
{ test: /^figure:/, code: 'figure-unwrapped', class: 'degraded' },
|
|
15
|
+
{ test: /^figure group: short caption/, code: 'figure-group-short-caption-dropped', class: 'lossy' },
|
|
16
|
+
{ test: /^frontmatter: format/, code: 'frontmatter-format-unsupported', class: 'unsupported' },
|
|
17
|
+
{ test: /^frontmatter: block content/, code: 'frontmatter-block-content-dropped', class: 'lossy' },
|
|
18
|
+
{ test: /^frontmatter: line/, code: 'frontmatter-line-skipped', class: 'lossy' },
|
|
19
|
+
{ test: /^frontmatter: value/, code: 'frontmatter-value-skipped', class: 'lossy' },
|
|
20
|
+
{ test: /^meta: .*empty/, code: 'metadata-empty-blocks-skipped', class: 'lossy' },
|
|
21
|
+
{ test: /^meta:/, code: 'metadata-value-skipped', class: 'lossy' },
|
|
22
|
+
{ test: /^definition list: looseness/, code: 'definition-list-looseness-widened', class: 'normalized' },
|
|
23
|
+
{ test: /^definition list:/, code: 'definition-entry-skipped', class: 'lossy' },
|
|
24
|
+
{ test: /^ordered list:/, code: 'ordered-list-marker-normalized', class: 'normalized' },
|
|
25
|
+
{ test: /^list: an empty item/, code: 'empty-list-item-spelled', class: 'normalized' },
|
|
26
|
+
{ test: /^task state:/, code: 'task-state-dropped', class: 'lossy' },
|
|
27
|
+
{ test: /^math: attributes/, code: 'math-attributes-dropped', class: 'lossy' },
|
|
28
|
+
{ test: /^symbol:/, code: 'symbol-unresolved', class: 'degraded' },
|
|
29
|
+
{ test: /^url: a denied scheme/, code: 'unsafe-url-scheme', class: 'lossy' },
|
|
30
|
+
{ test: /^attribute: unsafe name/, code: 'unsafe-attribute-name', class: 'lossy' },
|
|
31
|
+
{ test: /^attribute: unsafe value/, code: 'unsafe-attribute-value', class: 'lossy' },
|
|
32
|
+
{ test: /^extension:/, code: 'inline-extension-degraded', class: 'degraded' },
|
|
33
|
+
{ test: /^(?:link|image|footnote): missing definition/, code: 'reference-unresolved', class: 'degraded' },
|
|
34
|
+
{ test: /^crossref:/, code: 'crossref-unresolved', class: 'degraded' },
|
|
35
|
+
{ test: /^list-table: structure/, code: 'list-table-structure-degraded', class: 'degraded' },
|
|
36
|
+
{ test: /^list-table: the short caption/, code: 'list-table-short-caption-dropped', class: 'lossy' },
|
|
37
|
+
{ test: /^list-table: a body group's attributes/, code: 'list-table-body-attributes-dropped', class: 'lossy' },
|
|
38
|
+
{ test: /^list-table: the table's .* body groups/, code: 'list-table-body-groups-merged', class: 'normalized' },
|
|
39
|
+
{ test: /^list-table: the body groups disagree/, code: 'list-table-row-heads-normalized', class: 'normalized' },
|
|
40
|
+
{ test: /^list-table: rowspan/, code: 'list-table-rowspan-clipped', class: 'lossy' },
|
|
41
|
+
{ test: /^table: a row header outside/, code: 'table-row-head-outside-leading-run', class: 'lossy' },
|
|
42
|
+
{ test: /^table: block-level/, code: 'table-cell-blocks-flattened', class: 'lossy' },
|
|
43
|
+
{ test: /^table: attributes on/, code: 'table-continuation-attributes-dropped', class: 'lossy' },
|
|
44
|
+
{ test: /^table: colspan continuation/, code: 'table-colspan-origin-missing', class: 'degraded' },
|
|
45
|
+
{ test: /^table: rowspan crossing/, code: 'table-rowspan-clipped', class: 'lossy' },
|
|
46
|
+
{ test: /^table: rowspan continuation/, code: 'table-rowspan-origin-missing', class: 'degraded' },
|
|
47
|
+
{ test: /^table: .*padded with empty cells/, code: 'table-groups-normalized', class: 'normalized' },
|
|
48
|
+
{ test: /^table: a cell holds block content/, code: 'table-groups-normalized', class: 'normalized' },
|
|
49
|
+
{ test: /^table: .*converted with the implicit head\/body split/, code: 'table-row-groups-invalid', class: 'degraded' },
|
|
50
|
+
{ test: /^table: .*preserved in the Carve AST as `rowGroups`/, code: 'table-groups-flattened', class: 'degraded' },
|
|
51
|
+
{ test: /^table: a foot row's row header is dropped/, code: 'table-foot-row-header-dropped', class: 'lossy' },
|
|
52
|
+
{ test: /^table: the body rows disagree on how many leading cells/, code: 'table-row-heads-degraded', class: 'degraded' },
|
|
53
|
+
{ test: /^table: the rows of a declared body group disagree/, code: 'table-row-heads-degraded', class: 'degraded' },
|
|
54
|
+
{ test: /^table:/, code: 'table-unclassified-loss', class: 'unsupported' },
|
|
47
55
|
];
|
|
48
56
|
export function diagnostic(direction, message, details, sourceLocation) {
|
|
49
57
|
const rule = RULES.find((candidate) => candidate.test.test(message));
|
|
@@ -53,12 +61,22 @@ export function diagnostic(direction, message, details, sourceLocation) {
|
|
|
53
61
|
return {
|
|
54
62
|
code: rule.code,
|
|
55
63
|
direction,
|
|
56
|
-
|
|
64
|
+
class: rule.class,
|
|
65
|
+
severity: rule.class === 'normalized' ? 'info' : rule.class === 'degraded' ? 'warning' : 'error',
|
|
66
|
+
fidelity: rule.class === 'normalized'
|
|
67
|
+
? 'normalized'
|
|
68
|
+
: rule.class === 'degraded'
|
|
69
|
+
? 'degraded'
|
|
70
|
+
: 'dropped',
|
|
71
|
+
confidence: 'inferred',
|
|
57
72
|
message,
|
|
58
73
|
...(Object.keys(inferred).length ? { details: inferred } : {}),
|
|
59
74
|
...(sourceLocation !== undefined ? { sourceLocation } : {}),
|
|
60
75
|
};
|
|
61
76
|
}
|
|
77
|
+
export function migrationReport(diagnostics, sourceFormat = 'pandoc-json') {
|
|
78
|
+
return { schemaVersion: 2, sourceFormat, diagnostics };
|
|
79
|
+
}
|
|
62
80
|
function inferDetails(message) {
|
|
63
81
|
const details = {};
|
|
64
82
|
const nodeType = /node(?: type)? "([^"]+)"/.exec(message)?.[1];
|
|
@@ -79,5 +97,5 @@ function inferDetails(message) {
|
|
|
79
97
|
return details;
|
|
80
98
|
}
|
|
81
99
|
export function hasLoss(diagnostics) {
|
|
82
|
-
return diagnostics.some((item) => item.
|
|
100
|
+
return diagnostics.some((item) => item.fidelity === 'degraded' || item.fidelity === 'dropped');
|
|
83
101
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as carve from '@markup-carve/carve';
|
|
2
|
+
import { type ParseOptions } from './parse-options.js';
|
|
3
|
+
import type { ConvertOptions, ConvertResult } from './convert.js';
|
|
4
|
+
export interface IncludeConversionOptions {
|
|
5
|
+
includeRoot: string;
|
|
6
|
+
sourcePath?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface IncludeConversionResult extends ConvertResult {
|
|
9
|
+
includeWarnings: carve.IncludeWarning[];
|
|
10
|
+
suppressedIncludeWarnings: number;
|
|
11
|
+
dependencies: carve.IncludeDependency[];
|
|
12
|
+
}
|
|
13
|
+
/** Convert file-backed Carve source after contained include expansion. */
|
|
14
|
+
export declare function carveToPandocWithIncludes(source: string, include: IncludeConversionOptions, options?: ConvertOptions & ParseOptions): IncludeConversionResult;
|
package/dist/includes.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as carve from '@markup-carve/carve';
|
|
2
|
+
import { fileSystemResolver } from '@markup-carve/carve/node';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { toCarveAst } from './ast-json.js';
|
|
5
|
+
import { carveAstToPandoc } from './index.js';
|
|
6
|
+
import { parseExtensions } from './parse-options.js';
|
|
7
|
+
/** Convert file-backed Carve source after contained include expansion. */
|
|
8
|
+
export function carveToPandocWithIncludes(source, include, options) {
|
|
9
|
+
if (!path.isAbsolute(include.includeRoot)) {
|
|
10
|
+
throw new Error('includeRoot must be an absolute path');
|
|
11
|
+
}
|
|
12
|
+
if (include.sourcePath && !path.isAbsolute(include.sourcePath)) {
|
|
13
|
+
throw new Error('sourcePath must be an absolute path');
|
|
14
|
+
}
|
|
15
|
+
if (include.sourcePath) {
|
|
16
|
+
const relative = path.relative(include.includeRoot, include.sourcePath);
|
|
17
|
+
if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
18
|
+
throw new Error('input file must be inside --include-root');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const extensions = parseExtensions(options);
|
|
22
|
+
const expanded = carve.expandIncludes(carve.parse(source, { extensions, positions: true }), source, {
|
|
23
|
+
resolve: fileSystemResolver(include.includeRoot),
|
|
24
|
+
...(include.sourcePath ? { sourcePath: include.sourcePath } : {}),
|
|
25
|
+
extensions,
|
|
26
|
+
});
|
|
27
|
+
const serializer = carve.toAstJson;
|
|
28
|
+
const converted = carveAstToPandoc(toCarveAst(expanded.doc, serializer), options);
|
|
29
|
+
return {
|
|
30
|
+
...converted,
|
|
31
|
+
includeWarnings: expanded.warnings,
|
|
32
|
+
suppressedIncludeWarnings: expanded.suppressedWarnings,
|
|
33
|
+
dependencies: expanded.dependencies,
|
|
34
|
+
};
|
|
35
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,11 +3,12 @@ import { type ConvertOptions, type ConvertResult } from './convert.js';
|
|
|
3
3
|
import { type ParseOptions } from './parse-options.js';
|
|
4
4
|
import type { PandocDoc } from './pandoc.js';
|
|
5
5
|
import type { ConversionDiagnostic } from './diagnostics.js';
|
|
6
|
+
import { type MigrationReport } from './diagnostics.js';
|
|
6
7
|
export { PANDOC_API_VERSION, type PandocDoc } from './pandoc.js';
|
|
7
8
|
export type { ConvertOptions, ConvertResult } from './convert.js';
|
|
8
9
|
export type { ParseOptions } from './parse-options.js';
|
|
9
10
|
export type { ReverseResult } from './reverse.js';
|
|
10
|
-
export type { ConversionDiagnostic, DiagnosticDirection, DiagnosticSeverity } from './diagnostics.js';
|
|
11
|
+
export type { ConversionDiagnostic, DiagnosticClass, DiagnosticDirection, DiagnosticSeverity, MigrationConfidence, MigrationFidelity, MigrationReport } from './diagnostics.js';
|
|
11
12
|
export type { CarveAstDocument, CarveAstNode } from './ast-json.js';
|
|
12
13
|
/**
|
|
13
14
|
* Parse Carve source to the serialized AST of PART 12 - the shape
|
|
@@ -46,6 +47,7 @@ export declare function pandocToCarve(doc: PandocDoc | string): {
|
|
|
46
47
|
carve: string;
|
|
47
48
|
warnings: string[];
|
|
48
49
|
diagnostics: ConversionDiagnostic[];
|
|
50
|
+
report: MigrationReport;
|
|
49
51
|
};
|
|
50
52
|
/**
|
|
51
53
|
* Convert Pandoc to the canonical Carve exchange AST without forcing it through
|
|
@@ -56,4 +58,5 @@ export declare function pandocToCarveAst(doc: PandocDoc | string): {
|
|
|
56
58
|
ast: CarveAstDocument;
|
|
57
59
|
warnings: string[];
|
|
58
60
|
diagnostics: ConversionDiagnostic[];
|
|
61
|
+
report: MigrationReport;
|
|
59
62
|
};
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { normalizeCarveAst, parseCarveAst, toCarveAst } from './ast-json.js';
|
|
|
3
3
|
import { convert } from './convert.js';
|
|
4
4
|
import { parseExtensions } from './parse-options.js';
|
|
5
5
|
import { pandocToCarve as reverse } from './reverse.js';
|
|
6
|
+
import { migrationReport } from './diagnostics.js';
|
|
6
7
|
export { PANDOC_API_VERSION } from './pandoc.js';
|
|
7
8
|
/**
|
|
8
9
|
* The engine's own serializer, when the installed engine has one.
|
|
@@ -16,6 +17,54 @@ export { PANDOC_API_VERSION } from './pandoc.js';
|
|
|
16
17
|
* version does not have fails at link time, before any check could run.
|
|
17
18
|
*/
|
|
18
19
|
const engineSerializer = carve.toAstJson;
|
|
20
|
+
let rendererReadsInlineSubstitutions;
|
|
21
|
+
function engineRenderTree(ast) {
|
|
22
|
+
rendererReadsInlineSubstitutions ??= (() => {
|
|
23
|
+
const parsed = carve.parse('{~/old/~>/new/~}');
|
|
24
|
+
const probe = (engineSerializer ? engineSerializer(parsed) : parsed);
|
|
25
|
+
const children = Array.isArray(probe['children']) ? probe['children'] : [];
|
|
26
|
+
const paragraph = children[0];
|
|
27
|
+
const substitution = Array.isArray(paragraph?.['children'])
|
|
28
|
+
? paragraph['children'].find((node) => node?.type === 'substitution')
|
|
29
|
+
: undefined;
|
|
30
|
+
return Array.isArray(substitution?.['old']) && Array.isArray(substitution?.['new']);
|
|
31
|
+
})();
|
|
32
|
+
return rendererReadsInlineSubstitutions ? ast : legacySubstitutions(ast);
|
|
33
|
+
}
|
|
34
|
+
function legacySubstitutions(value) {
|
|
35
|
+
if (Array.isArray(value))
|
|
36
|
+
return value.map(legacySubstitutions);
|
|
37
|
+
if (typeof value !== 'object' || value === null)
|
|
38
|
+
return value;
|
|
39
|
+
const node = value;
|
|
40
|
+
if (node['type'] === 'substitution') {
|
|
41
|
+
const { old, new: replacement, ...rest } = node;
|
|
42
|
+
const legacyRest = Object.fromEntries(Object.entries(rest).map(([key, child]) => [key, legacySubstitutions(child)]));
|
|
43
|
+
return {
|
|
44
|
+
...legacyRest,
|
|
45
|
+
// The published renderer cannot express inline structure in these
|
|
46
|
+
// fields. This lossy view exists only at that legacy API boundary;
|
|
47
|
+
// the returned and converted ASTs retain the arrays.
|
|
48
|
+
oldText: inlineText(old),
|
|
49
|
+
newText: inlineText(replacement),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return Object.fromEntries(Object.entries(node).map(([key, child]) => [key, legacySubstitutions(child)]));
|
|
53
|
+
}
|
|
54
|
+
function inlineText(value) {
|
|
55
|
+
if (!Array.isArray(value))
|
|
56
|
+
return '';
|
|
57
|
+
return value.map((node) => {
|
|
58
|
+
if (typeof node !== 'object' || node === null)
|
|
59
|
+
return '';
|
|
60
|
+
const inline = node;
|
|
61
|
+
if (inline['type'] === 'text')
|
|
62
|
+
return String(inline['value'] ?? '');
|
|
63
|
+
if (inline['type'] === 'soft_break' || inline['type'] === 'hard_break')
|
|
64
|
+
return '\n';
|
|
65
|
+
return inlineText(inline['children']);
|
|
66
|
+
}).join('');
|
|
67
|
+
}
|
|
19
68
|
/**
|
|
20
69
|
* Parse Carve source to the serialized AST of PART 12 - the shape
|
|
21
70
|
* `resources/ast-schema.json` pins, and the shape every engine's `--to-json`
|
|
@@ -77,9 +126,10 @@ export function pandocToCarve(doc) {
|
|
|
77
126
|
const parsed = typeof doc === 'string' ? JSON.parse(doc) : doc;
|
|
78
127
|
const { ast, warnings, diagnostics } = reverse(parsed);
|
|
79
128
|
return {
|
|
80
|
-
carve: carve.renderCarve(ast),
|
|
129
|
+
carve: carve.renderCarve(engineRenderTree(ast)),
|
|
81
130
|
warnings,
|
|
82
131
|
diagnostics,
|
|
132
|
+
report: migrationReport(diagnostics),
|
|
83
133
|
};
|
|
84
134
|
}
|
|
85
135
|
/**
|
|
@@ -90,5 +140,5 @@ export function pandocToCarve(doc) {
|
|
|
90
140
|
export function pandocToCarveAst(doc) {
|
|
91
141
|
const parsed = typeof doc === 'string' ? JSON.parse(doc) : doc;
|
|
92
142
|
const { ast, warnings, diagnostics } = reverse(parsed, 'ast');
|
|
93
|
-
return { ast: toCarveAst(ast, engineSerializer), warnings, diagnostics };
|
|
143
|
+
return { ast: toCarveAst(ast, engineSerializer), warnings, diagnostics, report: migrationReport(diagnostics) };
|
|
94
144
|
}
|
package/dist/reverse.js
CHANGED
|
@@ -346,7 +346,7 @@ function span(ctx, c) {
|
|
|
346
346
|
if (parts.length === 2) {
|
|
347
347
|
const [oldC, newC] = parts.map((p) => p.c[1]);
|
|
348
348
|
return [
|
|
349
|
-
{ type: 'substitution',
|
|
349
|
+
{ type: 'substitution', old: inlines(ctx, oldC), new: inlines(ctx, newC) },
|
|
350
350
|
];
|
|
351
351
|
}
|
|
352
352
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markup-carve/pandoc-carve",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Carve to Pandoc bridge: convert Carve markup to Pandoc's JSON AST and reach every pandoc output format (LaTeX, Typst, DOCX, PDF, ...)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"carve",
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"types": "./dist/index.d.ts",
|
|
22
22
|
"import": "./dist/index.js"
|
|
23
23
|
},
|
|
24
|
+
"./node": {
|
|
25
|
+
"types": "./dist/includes.d.ts",
|
|
26
|
+
"import": "./dist/includes.js"
|
|
27
|
+
},
|
|
24
28
|
"./package.json": "./package.json"
|
|
25
29
|
},
|
|
26
30
|
"bin": {
|
|
@@ -38,7 +42,8 @@
|
|
|
38
42
|
"scripts": {
|
|
39
43
|
"build": "tsc",
|
|
40
44
|
"examples:build": "npm run build && node scripts/build-examples.mjs",
|
|
41
|
-
"
|
|
45
|
+
"check:importer-fixtures": "node scripts/check-importer-fixtures.mjs",
|
|
46
|
+
"test": "npm run check:importer-fixtures && npm run build && node --test test/*.test.mjs",
|
|
42
47
|
"prepublishOnly": "npm run build"
|
|
43
48
|
},
|
|
44
49
|
"repository": {
|
|
@@ -50,7 +55,7 @@
|
|
|
50
55
|
"source": "https://github.com/markup-carve/pandoc-carve"
|
|
51
56
|
},
|
|
52
57
|
"dependencies": {
|
|
53
|
-
"@markup-carve/carve": "^0.1.
|
|
58
|
+
"@markup-carve/carve": "^0.1.7"
|
|
54
59
|
},
|
|
55
60
|
"devDependencies": {
|
|
56
61
|
"@types/node": "^24.0.0",
|