@aloth/olcli 0.12.0 → 0.13.0
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 +76 -0
- package/dist/cli.js +280 -6
- package/dist/cli.js.map +1 -1
- package/dist/diff.d.ts +30 -0
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +34 -0
- package/dist/diff.js.map +1 -1
- package/dist/latexdiff.d.ts +165 -0
- package/dist/latexdiff.d.ts.map +1 -0
- package/dist/latexdiff.js +269 -0
- package/dist/latexdiff.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `latexdiff` integration for `olcli diff`.
|
|
3
|
+
*
|
|
4
|
+
* `diff` already computes both sides of the comparison - the project as it is
|
|
5
|
+
* on Overleaf right now, and the working directory - and prints them as
|
|
6
|
+
* unified patches. That is the right artifact for a developer and the wrong
|
|
7
|
+
* one for a thesis advisor, who expects the revision marked up in the document
|
|
8
|
+
* itself. `latexdiff` produces that, and the only parts a user cannot do by
|
|
9
|
+
* hand are getting the remote side onto disk and compiling the result without
|
|
10
|
+
* a local TeX installation.
|
|
11
|
+
*
|
|
12
|
+
* Everything decidable from data is decided here as a pure function so it can
|
|
13
|
+
* be unit-tested with no Overleaf account and no `latexdiff` binary; the spawn
|
|
14
|
+
* and the upload/compile/delete sequence stay in `cli.ts` with the rest of the
|
|
15
|
+
* IO. Same split as `diff.ts`.
|
|
16
|
+
*
|
|
17
|
+
* Orientation matches `diff.ts` and is not negotiable: **old is the remote,
|
|
18
|
+
* new is local**. Struck-through text in the markup is content a subsequent
|
|
19
|
+
* push would overwrite, underlined text is content it would upload.
|
|
20
|
+
*/
|
|
21
|
+
/** Binary looked up on PATH. Not configurable; `latexdiff` is its only name. */
|
|
22
|
+
export declare const LATEXDIFF_BIN = "latexdiff";
|
|
23
|
+
/**
|
|
24
|
+
* Directory the marked-up files are written to, relative to the target
|
|
25
|
+
* directory.
|
|
26
|
+
*
|
|
27
|
+
* Dotted on purpose. `scanLocalFiles` skips dotted entries unconditionally,
|
|
28
|
+
* before any ignore rule is consulted, so this is the one location whose
|
|
29
|
+
* contents cannot leak into a later `push`. A plain `main-diff.tex` next to
|
|
30
|
+
* the document would be uploaded to Overleaf on the next sync.
|
|
31
|
+
*/
|
|
32
|
+
export declare const DIFF_OUTPUT_DIR = ".olcli-diff";
|
|
33
|
+
/**
|
|
34
|
+
* Name of the file `--pdf` uploads to the project for the duration of one
|
|
35
|
+
* compile.
|
|
36
|
+
*
|
|
37
|
+
* Fixed rather than randomized: a name a user can recognize and delete by hand
|
|
38
|
+
* is worth more than one that never collides, and a collision is refused
|
|
39
|
+
* rather than resolved anyway.
|
|
40
|
+
*/
|
|
41
|
+
export declare const REMOTE_SCRATCH_NAME = "olcli-latexdiff.tex";
|
|
42
|
+
/**
|
|
43
|
+
* True when the file declares a document class outside of a comment.
|
|
44
|
+
*
|
|
45
|
+
* This is what separates a root document from the chapters it inputs. A
|
|
46
|
+
* commented-out `\documentclass` is common in subfiles - people leave the
|
|
47
|
+
* standalone preamble behind when they split a document up - and counting it
|
|
48
|
+
* would report every chapter as a candidate root.
|
|
49
|
+
*/
|
|
50
|
+
export declare function declaresDocumentClass(content: Buffer): boolean;
|
|
51
|
+
export declare function isTexPath(path: string): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Every `.tex` file in the tree that declares a document class, sorted by path
|
|
54
|
+
* so the list a user is shown is stable across runs.
|
|
55
|
+
*/
|
|
56
|
+
export declare function findRootCandidates(files: Map<string, Buffer>): string[];
|
|
57
|
+
/** Raised when the root document cannot be determined; carries the advice. */
|
|
58
|
+
export declare class RootDocumentError extends Error {
|
|
59
|
+
readonly candidates: string[];
|
|
60
|
+
constructor(message: string, candidates?: string[]);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Decide which document to mark up.
|
|
64
|
+
*
|
|
65
|
+
* An explicit `--main` always wins, including for layouts this heuristic
|
|
66
|
+
* cannot read. Otherwise the document class is the signal, and ambiguity is
|
|
67
|
+
* reported rather than guessed at: a project holding both `paper.tex` and
|
|
68
|
+
* `poster.tex` has no correct default, and marking up the wrong one produces a
|
|
69
|
+
* plausible PDF describing the wrong revision - the failure that is hardest to
|
|
70
|
+
* notice.
|
|
71
|
+
*
|
|
72
|
+
* @param files The local tree, already filtered the same way `diff` filters it.
|
|
73
|
+
*/
|
|
74
|
+
export declare function resolveRootDocument(files: Map<string, Buffer>, explicit?: string): string;
|
|
75
|
+
export interface LatexdiffArgsOptions {
|
|
76
|
+
/**
|
|
77
|
+
* Inline `\input` and `\include` before comparing. On by default; see
|
|
78
|
+
* `buildLatexdiffArgs`.
|
|
79
|
+
*/
|
|
80
|
+
flatten?: boolean;
|
|
81
|
+
/** Verbatim `latexdiff` options from `--latexdiff-opt`, passed through. */
|
|
82
|
+
extra?: string[];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Build the `latexdiff` argument list.
|
|
86
|
+
*
|
|
87
|
+
* `--flatten` is the default, and the reason is the remote compile rather than
|
|
88
|
+
* taste. Without it the marked-up file still contains `\input{sections/intro}`,
|
|
89
|
+
* and compiling it on Overleaf resolves those against the files sitting in the
|
|
90
|
+
* project - the old content - so a multi-file thesis would produce a PDF that
|
|
91
|
+
* marks up the root document and silently shows every input file as unchanged.
|
|
92
|
+
* `--no-flatten` is there for anyone who wants the raw single-file markup.
|
|
93
|
+
*
|
|
94
|
+
* Extra options come before the file arguments because `latexdiff` reads the
|
|
95
|
+
* last two positional arguments as old and new.
|
|
96
|
+
*/
|
|
97
|
+
export declare function buildLatexdiffArgs(oldPath: string, newPath: string, options?: LatexdiffArgsOptions): string[];
|
|
98
|
+
/**
|
|
99
|
+
* Where a marked-up artifact is written, relative to the target directory.
|
|
100
|
+
*
|
|
101
|
+
* Flat rather than mirroring the root document's folder: the output of a
|
|
102
|
+
* flattened run is a single self-contained file, and burying it under
|
|
103
|
+
* `.olcli-diff/chapters/` would only make it harder to find.
|
|
104
|
+
*/
|
|
105
|
+
export declare function diffOutputPath(rootPath: string, extension: string): string;
|
|
106
|
+
/**
|
|
107
|
+
* Project path `--pdf` uploads the marked-up file to.
|
|
108
|
+
*
|
|
109
|
+
* Placed next to the root document rather than at the project root, so that
|
|
110
|
+
* relative `\includegraphics` and `\bibliography` paths in the flattened file
|
|
111
|
+
* resolve exactly as they do for the document it was built from.
|
|
112
|
+
*/
|
|
113
|
+
export declare function remoteScratchPath(rootPath: string): string;
|
|
114
|
+
/**
|
|
115
|
+
* Write a file tree to a directory, for use as `latexdiff`'s old side.
|
|
116
|
+
*
|
|
117
|
+
* `latexdiff --flatten` resolves `\input` relative to each side's own file, so
|
|
118
|
+
* the remote tree has to exist on disk with its folder structure intact -
|
|
119
|
+
* handing it just the root document would break every input in a multi-file
|
|
120
|
+
* project.
|
|
121
|
+
*
|
|
122
|
+
* Entry names are re-checked against the destination even though `diff` has
|
|
123
|
+
* already filtered them against the target directory: this writes files, and a
|
|
124
|
+
* path that was safe relative to one base is not automatically safe relative
|
|
125
|
+
* to another. See #44.
|
|
126
|
+
*/
|
|
127
|
+
export declare function materializeTree(destination: string, files: Map<string, Buffer>): number;
|
|
128
|
+
export interface LatexdiffResult {
|
|
129
|
+
/** The marked-up document, as printed to stdout. */
|
|
130
|
+
markup: string;
|
|
131
|
+
/** Warnings `latexdiff` printed while succeeding. */
|
|
132
|
+
stderr: string;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Raised when `latexdiff` could not be run or exited non-zero. `missing`
|
|
136
|
+
* separates "not installed" - a setup problem with a known fix - from a real
|
|
137
|
+
* failure on the documents, which needs the tool's own message.
|
|
138
|
+
*/
|
|
139
|
+
export declare class LatexdiffError extends Error {
|
|
140
|
+
readonly missing: boolean;
|
|
141
|
+
readonly stderr: string;
|
|
142
|
+
constructor(message: string, missing: boolean, stderr?: string);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Run `latexdiff`, returning its stdout.
|
|
146
|
+
*
|
|
147
|
+
* `execFile`, never a shell: the arguments include user-supplied paths and
|
|
148
|
+
* pass-through options, and an argv array cannot be talked into running
|
|
149
|
+
* something else.
|
|
150
|
+
*
|
|
151
|
+
* The default `maxBuffer` is 1 MB, which a flattened thesis passes without
|
|
152
|
+
* trying. Exceeding it truncates stdout and reports it as an error, which
|
|
153
|
+
* would read as "latexdiff failed" on exactly the documents this feature
|
|
154
|
+
* exists for.
|
|
155
|
+
*/
|
|
156
|
+
export declare function runLatexdiff(args: string[]): Promise<LatexdiffResult>;
|
|
157
|
+
/**
|
|
158
|
+
* Install advice for a missing binary, by platform.
|
|
159
|
+
*
|
|
160
|
+
* `latexdiff` ships with TeX Live and MacTeX, so on most machines the answer
|
|
161
|
+
* is that the whole distribution is missing rather than this one tool - which
|
|
162
|
+
* is also the case where `--pdf` is most useful, since it needs no local TeX.
|
|
163
|
+
*/
|
|
164
|
+
export declare function latexdiffInstallHint(platform?: string): string;
|
|
165
|
+
//# sourceMappingURL=latexdiff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"latexdiff.d.ts","sourceRoot":"","sources":["../src/latexdiff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAUH,gFAAgF;AAChF,eAAO,MAAM,aAAa,cAAc,CAAC;AAEzC;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAE7C;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,wBAAwB,CAAC;AAsBzD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAK9D;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAG/C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,CAKvE;AAED,8EAA8E;AAC9E,qBAAa,iBAAkB,SAAQ,KAAK;IACb,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE;gBAA9C,OAAO,EAAE,MAAM,EAAW,UAAU,GAAE,MAAM,EAAO;CAIhE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CA4BzF;AAED,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,MAAM,EAAE,CAMV;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAG1E;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAUvF;AAED,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACV,QAAQ,CAAC,OAAO,EAAE,OAAO;IAAE,QAAQ,CAAC,MAAM;gBAA3D,OAAO,EAAE,MAAM,EAAW,OAAO,EAAE,OAAO,EAAW,MAAM,SAAK;CAI7E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,CAAC,CAsB3E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,GAAE,MAAyB,GAAG,MAAM,CAShF"}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `latexdiff` integration for `olcli diff`.
|
|
3
|
+
*
|
|
4
|
+
* `diff` already computes both sides of the comparison - the project as it is
|
|
5
|
+
* on Overleaf right now, and the working directory - and prints them as
|
|
6
|
+
* unified patches. That is the right artifact for a developer and the wrong
|
|
7
|
+
* one for a thesis advisor, who expects the revision marked up in the document
|
|
8
|
+
* itself. `latexdiff` produces that, and the only parts a user cannot do by
|
|
9
|
+
* hand are getting the remote side onto disk and compiling the result without
|
|
10
|
+
* a local TeX installation.
|
|
11
|
+
*
|
|
12
|
+
* Everything decidable from data is decided here as a pure function so it can
|
|
13
|
+
* be unit-tested with no Overleaf account and no `latexdiff` binary; the spawn
|
|
14
|
+
* and the upload/compile/delete sequence stay in `cli.ts` with the rest of the
|
|
15
|
+
* IO. Same split as `diff.ts`.
|
|
16
|
+
*
|
|
17
|
+
* Orientation matches `diff.ts` and is not negotiable: **old is the remote,
|
|
18
|
+
* new is local**. Struck-through text in the markup is content a subsequent
|
|
19
|
+
* push would overwrite, underlined text is content it would upload.
|
|
20
|
+
*/
|
|
21
|
+
import { execFile } from 'node:child_process';
|
|
22
|
+
import { promisify } from 'node:util';
|
|
23
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
24
|
+
import { dirname } from 'node:path';
|
|
25
|
+
import { resolveWithin } from './paths.js';
|
|
26
|
+
const execFileAsync = promisify(execFile);
|
|
27
|
+
/** Binary looked up on PATH. Not configurable; `latexdiff` is its only name. */
|
|
28
|
+
export const LATEXDIFF_BIN = 'latexdiff';
|
|
29
|
+
/**
|
|
30
|
+
* Directory the marked-up files are written to, relative to the target
|
|
31
|
+
* directory.
|
|
32
|
+
*
|
|
33
|
+
* Dotted on purpose. `scanLocalFiles` skips dotted entries unconditionally,
|
|
34
|
+
* before any ignore rule is consulted, so this is the one location whose
|
|
35
|
+
* contents cannot leak into a later `push`. A plain `main-diff.tex` next to
|
|
36
|
+
* the document would be uploaded to Overleaf on the next sync.
|
|
37
|
+
*/
|
|
38
|
+
export const DIFF_OUTPUT_DIR = '.olcli-diff';
|
|
39
|
+
/**
|
|
40
|
+
* Name of the file `--pdf` uploads to the project for the duration of one
|
|
41
|
+
* compile.
|
|
42
|
+
*
|
|
43
|
+
* Fixed rather than randomized: a name a user can recognize and delete by hand
|
|
44
|
+
* is worth more than one that never collides, and a collision is refused
|
|
45
|
+
* rather than resolved anyway.
|
|
46
|
+
*/
|
|
47
|
+
export const REMOTE_SCRATCH_NAME = 'olcli-latexdiff.tex';
|
|
48
|
+
/** Extensions treated as LaTeX source when looking for the root document. */
|
|
49
|
+
const TEX_EXTENSIONS = ['.tex', '.ltx'];
|
|
50
|
+
/**
|
|
51
|
+
* Drop the comment part of a single line of TeX.
|
|
52
|
+
*
|
|
53
|
+
* A `%` starts a comment unless it is escaped, and `\\%` is an escaped
|
|
54
|
+
* backslash followed by a comment, so the run of backslashes before the `%`
|
|
55
|
+
* has to be counted rather than just the character in front of it.
|
|
56
|
+
*/
|
|
57
|
+
function stripTexComment(line) {
|
|
58
|
+
for (let i = 0; i < line.length; i++) {
|
|
59
|
+
if (line[i] !== '%')
|
|
60
|
+
continue;
|
|
61
|
+
let backslashes = 0;
|
|
62
|
+
for (let j = i - 1; j >= 0 && line[j] === '\\'; j--)
|
|
63
|
+
backslashes++;
|
|
64
|
+
if (backslashes % 2 === 0)
|
|
65
|
+
return line.slice(0, i);
|
|
66
|
+
}
|
|
67
|
+
return line;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* True when the file declares a document class outside of a comment.
|
|
71
|
+
*
|
|
72
|
+
* This is what separates a root document from the chapters it inputs. A
|
|
73
|
+
* commented-out `\documentclass` is common in subfiles - people leave the
|
|
74
|
+
* standalone preamble behind when they split a document up - and counting it
|
|
75
|
+
* would report every chapter as a candidate root.
|
|
76
|
+
*/
|
|
77
|
+
export function declaresDocumentClass(content) {
|
|
78
|
+
const text = content.toString('utf-8');
|
|
79
|
+
return text
|
|
80
|
+
.split('\n')
|
|
81
|
+
.some((line) => /\\documentclass\s*[[{]/.test(stripTexComment(line)));
|
|
82
|
+
}
|
|
83
|
+
export function isTexPath(path) {
|
|
84
|
+
const lower = path.toLowerCase();
|
|
85
|
+
return TEX_EXTENSIONS.some((ext) => lower.endsWith(ext));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Every `.tex` file in the tree that declares a document class, sorted by path
|
|
89
|
+
* so the list a user is shown is stable across runs.
|
|
90
|
+
*/
|
|
91
|
+
export function findRootCandidates(files) {
|
|
92
|
+
return [...files.keys()]
|
|
93
|
+
.filter((path) => isTexPath(path))
|
|
94
|
+
.filter((path) => declaresDocumentClass(files.get(path)))
|
|
95
|
+
.sort();
|
|
96
|
+
}
|
|
97
|
+
/** Raised when the root document cannot be determined; carries the advice. */
|
|
98
|
+
export class RootDocumentError extends Error {
|
|
99
|
+
candidates;
|
|
100
|
+
constructor(message, candidates = []) {
|
|
101
|
+
super(message);
|
|
102
|
+
this.candidates = candidates;
|
|
103
|
+
this.name = 'RootDocumentError';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Decide which document to mark up.
|
|
108
|
+
*
|
|
109
|
+
* An explicit `--main` always wins, including for layouts this heuristic
|
|
110
|
+
* cannot read. Otherwise the document class is the signal, and ambiguity is
|
|
111
|
+
* reported rather than guessed at: a project holding both `paper.tex` and
|
|
112
|
+
* `poster.tex` has no correct default, and marking up the wrong one produces a
|
|
113
|
+
* plausible PDF describing the wrong revision - the failure that is hardest to
|
|
114
|
+
* notice.
|
|
115
|
+
*
|
|
116
|
+
* @param files The local tree, already filtered the same way `diff` filters it.
|
|
117
|
+
*/
|
|
118
|
+
export function resolveRootDocument(files, explicit) {
|
|
119
|
+
if (explicit) {
|
|
120
|
+
if (!files.has(explicit)) {
|
|
121
|
+
throw new RootDocumentError(`Root document not found locally: ${explicit}\n` +
|
|
122
|
+
' It must be a file in the target directory that diff also reports ' +
|
|
123
|
+
'(an ignore rule can hide it).');
|
|
124
|
+
}
|
|
125
|
+
return explicit;
|
|
126
|
+
}
|
|
127
|
+
const candidates = findRootCandidates(files);
|
|
128
|
+
if (candidates.length === 1)
|
|
129
|
+
return candidates[0];
|
|
130
|
+
if (candidates.length === 0) {
|
|
131
|
+
throw new RootDocumentError('No .tex file declaring \\documentclass was found, so there is no root document to mark up.\n' +
|
|
132
|
+
' Pass --main <path> to name one.');
|
|
133
|
+
}
|
|
134
|
+
throw new RootDocumentError(`${candidates.length} files declare \\documentclass, so the root document is ambiguous.\n` +
|
|
135
|
+
' Pass --main <path> to choose one.', candidates);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Build the `latexdiff` argument list.
|
|
139
|
+
*
|
|
140
|
+
* `--flatten` is the default, and the reason is the remote compile rather than
|
|
141
|
+
* taste. Without it the marked-up file still contains `\input{sections/intro}`,
|
|
142
|
+
* and compiling it on Overleaf resolves those against the files sitting in the
|
|
143
|
+
* project - the old content - so a multi-file thesis would produce a PDF that
|
|
144
|
+
* marks up the root document and silently shows every input file as unchanged.
|
|
145
|
+
* `--no-flatten` is there for anyone who wants the raw single-file markup.
|
|
146
|
+
*
|
|
147
|
+
* Extra options come before the file arguments because `latexdiff` reads the
|
|
148
|
+
* last two positional arguments as old and new.
|
|
149
|
+
*/
|
|
150
|
+
export function buildLatexdiffArgs(oldPath, newPath, options = {}) {
|
|
151
|
+
const args = [];
|
|
152
|
+
if (options.flatten !== false)
|
|
153
|
+
args.push('--flatten');
|
|
154
|
+
args.push(...(options.extra ?? []));
|
|
155
|
+
args.push(oldPath, newPath);
|
|
156
|
+
return args;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Where a marked-up artifact is written, relative to the target directory.
|
|
160
|
+
*
|
|
161
|
+
* Flat rather than mirroring the root document's folder: the output of a
|
|
162
|
+
* flattened run is a single self-contained file, and burying it under
|
|
163
|
+
* `.olcli-diff/chapters/` would only make it harder to find.
|
|
164
|
+
*/
|
|
165
|
+
export function diffOutputPath(rootPath, extension) {
|
|
166
|
+
const base = rootPath.split('/').pop().replace(/\.(tex|ltx)$/i, '');
|
|
167
|
+
return `${DIFF_OUTPUT_DIR}/${base}-diff.${extension}`;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Project path `--pdf` uploads the marked-up file to.
|
|
171
|
+
*
|
|
172
|
+
* Placed next to the root document rather than at the project root, so that
|
|
173
|
+
* relative `\includegraphics` and `\bibliography` paths in the flattened file
|
|
174
|
+
* resolve exactly as they do for the document it was built from.
|
|
175
|
+
*/
|
|
176
|
+
export function remoteScratchPath(rootPath) {
|
|
177
|
+
const folder = rootPath.includes('/') ? rootPath.slice(0, rootPath.lastIndexOf('/')) : '';
|
|
178
|
+
return folder ? `${folder}/${REMOTE_SCRATCH_NAME}` : REMOTE_SCRATCH_NAME;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Write a file tree to a directory, for use as `latexdiff`'s old side.
|
|
182
|
+
*
|
|
183
|
+
* `latexdiff --flatten` resolves `\input` relative to each side's own file, so
|
|
184
|
+
* the remote tree has to exist on disk with its folder structure intact -
|
|
185
|
+
* handing it just the root document would break every input in a multi-file
|
|
186
|
+
* project.
|
|
187
|
+
*
|
|
188
|
+
* Entry names are re-checked against the destination even though `diff` has
|
|
189
|
+
* already filtered them against the target directory: this writes files, and a
|
|
190
|
+
* path that was safe relative to one base is not automatically safe relative
|
|
191
|
+
* to another. See #44.
|
|
192
|
+
*/
|
|
193
|
+
export function materializeTree(destination, files) {
|
|
194
|
+
let written = 0;
|
|
195
|
+
for (const [path, data] of files) {
|
|
196
|
+
const target = resolveWithin(destination, path);
|
|
197
|
+
if (!target)
|
|
198
|
+
continue;
|
|
199
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
200
|
+
writeFileSync(target, data);
|
|
201
|
+
written++;
|
|
202
|
+
}
|
|
203
|
+
return written;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Raised when `latexdiff` could not be run or exited non-zero. `missing`
|
|
207
|
+
* separates "not installed" - a setup problem with a known fix - from a real
|
|
208
|
+
* failure on the documents, which needs the tool's own message.
|
|
209
|
+
*/
|
|
210
|
+
export class LatexdiffError extends Error {
|
|
211
|
+
missing;
|
|
212
|
+
stderr;
|
|
213
|
+
constructor(message, missing, stderr = '') {
|
|
214
|
+
super(message);
|
|
215
|
+
this.missing = missing;
|
|
216
|
+
this.stderr = stderr;
|
|
217
|
+
this.name = 'LatexdiffError';
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Run `latexdiff`, returning its stdout.
|
|
222
|
+
*
|
|
223
|
+
* `execFile`, never a shell: the arguments include user-supplied paths and
|
|
224
|
+
* pass-through options, and an argv array cannot be talked into running
|
|
225
|
+
* something else.
|
|
226
|
+
*
|
|
227
|
+
* The default `maxBuffer` is 1 MB, which a flattened thesis passes without
|
|
228
|
+
* trying. Exceeding it truncates stdout and reports it as an error, which
|
|
229
|
+
* would read as "latexdiff failed" on exactly the documents this feature
|
|
230
|
+
* exists for.
|
|
231
|
+
*/
|
|
232
|
+
export async function runLatexdiff(args) {
|
|
233
|
+
try {
|
|
234
|
+
const { stdout, stderr } = await execFileAsync(LATEXDIFF_BIN, args, {
|
|
235
|
+
encoding: 'utf-8',
|
|
236
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
237
|
+
});
|
|
238
|
+
return { markup: stdout, stderr };
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
// execFile rejects with the spawn error for a missing binary and with the
|
|
242
|
+
// captured streams for a non-zero exit; `code` carries the errno in the
|
|
243
|
+
// first case and the exit status in the second.
|
|
244
|
+
const failure = error;
|
|
245
|
+
if (failure.code === 'ENOENT') {
|
|
246
|
+
throw new LatexdiffError(`${LATEXDIFF_BIN} was not found on PATH`, true);
|
|
247
|
+
}
|
|
248
|
+
const stderr = typeof failure.stderr === 'string' ? failure.stderr.trim() : '';
|
|
249
|
+
throw new LatexdiffError(`${LATEXDIFF_BIN} exited with status ${failure.code ?? 'unknown'}`, false, stderr);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Install advice for a missing binary, by platform.
|
|
254
|
+
*
|
|
255
|
+
* `latexdiff` ships with TeX Live and MacTeX, so on most machines the answer
|
|
256
|
+
* is that the whole distribution is missing rather than this one tool - which
|
|
257
|
+
* is also the case where `--pdf` is most useful, since it needs no local TeX.
|
|
258
|
+
*/
|
|
259
|
+
export function latexdiffInstallHint(platform = process.platform) {
|
|
260
|
+
switch (platform) {
|
|
261
|
+
case 'darwin':
|
|
262
|
+
return 'Install MacTeX (brew install --cask mactex-no-gui) or brew install latexdiff';
|
|
263
|
+
case 'win32':
|
|
264
|
+
return 'Install MiKTeX or TeX Live; both ship latexdiff';
|
|
265
|
+
default:
|
|
266
|
+
return 'Install it from your TeX distribution (apt install latexdiff, or TeX Live)';
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=latexdiff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"latexdiff.js","sourceRoot":"","sources":["../src/latexdiff.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED