mmdfmt 0.0.0 → 0.2.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/LICENSE +21 -0
- package/README.ja.md +784 -0
- package/README.md +677 -0
- package/lib/cli.d.mts +1 -0
- package/lib/cli.mjs +433 -0
- package/lib/format-file-DSbEElSV.mjs +545 -0
- package/lib/index.d.mts +72 -0
- package/lib/index.mjs +22 -0
- package/package.json +34 -6
package/lib/cli.mjs
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { a as kindOf, i as formatFile, n as endingsInWords } from "./format-file-DSbEElSV.mjs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { getFileInfo } from "prettier";
|
|
5
|
+
import { text } from "node:stream/consumers";
|
|
6
|
+
import { readFile, stat, writeFile } from "node:fs/promises";
|
|
7
|
+
import { styleText } from "node:util";
|
|
8
|
+
import { cli, define, isArgsValidationError } from "gunshi";
|
|
9
|
+
import { convertPathToPattern, glob } from "tinyglobby";
|
|
10
|
+
//#region src/broken-pipe.ts
|
|
11
|
+
/**
|
|
12
|
+
* The listener of the errors of standard output. When the reader of standard
|
|
13
|
+
* output goes away, as `head` does once it has its lines, writing fails with
|
|
14
|
+
* `EPIPE`. Other command-line tools then end without a word; so does this
|
|
15
|
+
* tool, and it finishes the run, so that `--write` still rewrites every file
|
|
16
|
+
* and the exit code still tells the result (README, Usage). Any other error is
|
|
17
|
+
* thrown, as Node.js throws it when the stream has no listener.
|
|
18
|
+
*/
|
|
19
|
+
function ignoreBrokenPipe(error) {
|
|
20
|
+
if (error.code !== "EPIPE") throw error;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region package.json
|
|
24
|
+
var version = "0.2.0";
|
|
25
|
+
var description = "Formatter for Mermaid diagrams in .mmd files and in Markdown code blocks, which changes nothing else, as a command and as a function";
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/patterns.ts
|
|
28
|
+
/**
|
|
29
|
+
* The directories whose files are skipped, those that Prettier's CLI skips
|
|
30
|
+
* (README, Excluding files).
|
|
31
|
+
*/
|
|
32
|
+
const skippedDirectories = [
|
|
33
|
+
"node_modules",
|
|
34
|
+
".git",
|
|
35
|
+
".hg",
|
|
36
|
+
".jj",
|
|
37
|
+
".sl",
|
|
38
|
+
".svn"
|
|
39
|
+
];
|
|
40
|
+
/** The globs of the files under the skipped directories. */
|
|
41
|
+
const skippedDirectoryGlobs = skippedDirectories.map((directory) => `**/${directory}/**`);
|
|
42
|
+
/**
|
|
43
|
+
* Selects the files that the patterns name, of those the tool formats, as
|
|
44
|
+
* Prettier's CLI reads the patterns (README, Patterns). A pattern that names a
|
|
45
|
+
* path in a skipped directory, a file, a directory, or a glob, is skipped. A
|
|
46
|
+
* pattern is then first the path of a file or of a directory. Otherwise, a
|
|
47
|
+
* pattern that starts with `!` excludes the files that the glob after it
|
|
48
|
+
* matches from what every other pattern selects, and any other pattern is a
|
|
49
|
+
* glob.
|
|
50
|
+
*/
|
|
51
|
+
async function selectFiles(patterns, cwd) {
|
|
52
|
+
const selecting = [];
|
|
53
|
+
const excluding = [];
|
|
54
|
+
const skipped = [];
|
|
55
|
+
for (const pattern of patterns) {
|
|
56
|
+
const target = path.resolve(cwd, pattern);
|
|
57
|
+
if (passesSkippedDirectory(target, cwd)) {
|
|
58
|
+
skipped.push(pattern);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const stats = await stat(target).catch(() => void 0);
|
|
62
|
+
if (stats?.isFile()) selecting.push({
|
|
63
|
+
kind: "file",
|
|
64
|
+
text: pattern,
|
|
65
|
+
path: target
|
|
66
|
+
});
|
|
67
|
+
else if (stats?.isDirectory()) selecting.push({
|
|
68
|
+
kind: "directory",
|
|
69
|
+
text: pattern,
|
|
70
|
+
path: target
|
|
71
|
+
});
|
|
72
|
+
else if (pattern.startsWith("!")) excluding.push(globOf(pattern.slice(1)));
|
|
73
|
+
else selecting.push({
|
|
74
|
+
kind: "glob",
|
|
75
|
+
text: pattern
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
const files = /* @__PURE__ */ new Map();
|
|
79
|
+
const unmatched = [];
|
|
80
|
+
const excluded = [];
|
|
81
|
+
for (const pattern of selecting) {
|
|
82
|
+
if (pattern.kind === "file" && await isExcluded(pattern.path, cwd, excluding)) {
|
|
83
|
+
excluded.push(pattern.text);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const selected = [];
|
|
87
|
+
for (const file of (await filesOf(pattern, cwd, excluding)).sort()) {
|
|
88
|
+
const kind = kindOf(file);
|
|
89
|
+
if (kind !== void 0) selected.push({
|
|
90
|
+
path: file,
|
|
91
|
+
kind
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
if (selected.length === 0) unmatched.push(pattern.text);
|
|
95
|
+
for (const file of selected) if (!files.has(file.path)) files.set(file.path, file);
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
files: [...files.values()],
|
|
99
|
+
noSelectingPattern: selecting.length === 0,
|
|
100
|
+
skipped,
|
|
101
|
+
unmatched,
|
|
102
|
+
excluded
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* A pattern as a glob. On Windows, Prettier's CLI reads a `\` of a glob as
|
|
107
|
+
* `/`, the separator of globs; so does this (README, Patterns).
|
|
108
|
+
*/
|
|
109
|
+
function globOf(pattern) {
|
|
110
|
+
return process.platform === "win32" ? pattern.replaceAll("\\", "/") : pattern;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Whether the globs of the excluding patterns match a file, given by its
|
|
114
|
+
* absolute path. As in Prettier's CLI, they are matched against its path from
|
|
115
|
+
* `cwd`.
|
|
116
|
+
*/
|
|
117
|
+
async function isExcluded(file, cwd, excluding) {
|
|
118
|
+
return (await globFiles(convertPathToPattern(path.relative(cwd, file)), cwd, excluding)).length === 0;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The files that a pattern names, as absolute paths, apart from those that the
|
|
122
|
+
* globs of the excluding patterns match. The path of a file names the file
|
|
123
|
+
* wherever it is; a directory or a glob names no file under a skipped
|
|
124
|
+
* directory, as Prettier's CLI does not look into one.
|
|
125
|
+
*/
|
|
126
|
+
async function filesOf(pattern, cwd, excluding) {
|
|
127
|
+
if (pattern.kind === "file") return [pattern.path];
|
|
128
|
+
const ignore = [...skippedDirectoryGlobs, ...excluding];
|
|
129
|
+
return (pattern.kind === "directory" ? await globFiles(`${convertPathToPattern(path.relative(cwd, pattern.path) || ".")}/**/*`, cwd, ignore) : await globFiles(globOf(pattern.text), cwd, ignore)).filter((file) => !isUnderSkippedDirectory(file, cwd));
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The files that a glob matches from `cwd`, as absolute paths, apart from
|
|
133
|
+
* those that the globs of `ignore` match. Like Prettier's CLI, it matches names
|
|
134
|
+
* that start with a dot and follows no symbolic link.
|
|
135
|
+
*/
|
|
136
|
+
async function globFiles(pattern, cwd, ignore) {
|
|
137
|
+
return (await glob(pattern, {
|
|
138
|
+
cwd,
|
|
139
|
+
absolute: true,
|
|
140
|
+
dot: true,
|
|
141
|
+
expandDirectories: false,
|
|
142
|
+
followSymbolicLinks: false,
|
|
143
|
+
ignore: [...ignore]
|
|
144
|
+
})).map((file) => path.resolve(file));
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Whether a path, taken from `cwd`, is one of the skipped directories or passes
|
|
148
|
+
* through one, as Prettier's CLI decides it for a pattern.
|
|
149
|
+
*/
|
|
150
|
+
function passesSkippedDirectory(target, cwd) {
|
|
151
|
+
return path.relative(cwd, target).split(path.sep).some((name) => skippedDirectories.includes(name));
|
|
152
|
+
}
|
|
153
|
+
/** Whether a file is under one of the skipped directories of `cwd`. */
|
|
154
|
+
function isUnderSkippedDirectory(file, cwd) {
|
|
155
|
+
return passesSkippedDirectory(path.dirname(file), cwd);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Whether a file is skipped: it is under one of the skipped directories, or
|
|
159
|
+
* the `.gitignore` of the current directory lists it (README, Excluding files).
|
|
160
|
+
*
|
|
161
|
+
* Unlike Prettier's CLI, the tool does not read `.prettierignore`. It is used
|
|
162
|
+
* where Prettier does not format the Markdown, and the `.prettierignore` there
|
|
163
|
+
* often lists the Markdown files: reading it would leave their diagrams
|
|
164
|
+
* unformatted without a word. If users ask for a way to keep files from this
|
|
165
|
+
* tool alone, other than a pattern that starts with `!`, read a file of its
|
|
166
|
+
* own, such as `.mmdfmtignore`, not `.prettierignore`.
|
|
167
|
+
*/
|
|
168
|
+
async function isSkipped(file, cwd) {
|
|
169
|
+
if (isUnderSkippedDirectory(file, cwd)) return true;
|
|
170
|
+
const { ignored } = await getFileInfo(file, {
|
|
171
|
+
ignorePath: path.join(cwd, ".gitignore"),
|
|
172
|
+
resolveConfig: false
|
|
173
|
+
});
|
|
174
|
+
return ignored;
|
|
175
|
+
}
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/main.ts
|
|
178
|
+
/** The exit codes (README, Errors and exit codes). */
|
|
179
|
+
const exitCodes = {
|
|
180
|
+
ok: 0,
|
|
181
|
+
changed: 1,
|
|
182
|
+
error: 2
|
|
183
|
+
};
|
|
184
|
+
/** The examples in the usage, one on a line (README, Usage). */
|
|
185
|
+
const examples = `mmdfmt --write "**/*.{md,mmd,mermaid}"
|
|
186
|
+
mmdfmt --check "**/*.{md,mmd,mermaid}"
|
|
187
|
+
mmdfmt --stdin-filepath diagram.mmd < diagram.mmd`;
|
|
188
|
+
/** Runs the tool with its arguments, and returns the exit code (README, Usage). */
|
|
189
|
+
async function main(args, io) {
|
|
190
|
+
let code = exitCodes.ok;
|
|
191
|
+
const command = define({
|
|
192
|
+
toKebab: true,
|
|
193
|
+
args: {
|
|
194
|
+
write: {
|
|
195
|
+
type: "boolean",
|
|
196
|
+
short: "w",
|
|
197
|
+
negatable: true,
|
|
198
|
+
description: "Rewrite the files that change, and list each file with its time"
|
|
199
|
+
},
|
|
200
|
+
check: {
|
|
201
|
+
type: "boolean",
|
|
202
|
+
short: "c",
|
|
203
|
+
negatable: true,
|
|
204
|
+
description: "Check whether the files are formatted, and print a summary and the files that are not",
|
|
205
|
+
conflicts: ["listDifferent"]
|
|
206
|
+
},
|
|
207
|
+
listDifferent: {
|
|
208
|
+
type: "boolean",
|
|
209
|
+
short: "l",
|
|
210
|
+
negatable: true,
|
|
211
|
+
description: "List the files that --write would change",
|
|
212
|
+
conflicts: ["check"]
|
|
213
|
+
},
|
|
214
|
+
stdinFilepath: {
|
|
215
|
+
type: "string",
|
|
216
|
+
description: "Format standard input as the file at this path, and write the result to standard output"
|
|
217
|
+
},
|
|
218
|
+
patterns: {
|
|
219
|
+
type: "positional",
|
|
220
|
+
multiple: true,
|
|
221
|
+
required: false,
|
|
222
|
+
description: "The files, directories, or globs of the files to format"
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
examples,
|
|
226
|
+
/** Runs the mode that the arguments select, and keeps its exit code. */
|
|
227
|
+
async run(ctx) {
|
|
228
|
+
if (args.length === 0) {
|
|
229
|
+
const usage = await ctx.env.renderUsage?.(ctx);
|
|
230
|
+
io.stderr(usage ?? "");
|
|
231
|
+
code = exitCodes.error;
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const { write, check, listDifferent, stdinFilepath } = ctx.values;
|
|
235
|
+
const patterns = [...ctx.values.patterns ?? [], ...ctx.rest];
|
|
236
|
+
if (stdinFilepath !== void 0) code = patterns.length > 0 ? argumentError(io, ["--stdin-filepath takes no patterns"]) : await formatStandardInput(stdinFilepath, {
|
|
237
|
+
write: Boolean(write),
|
|
238
|
+
listsDifference: Boolean(check) || Boolean(listDifferent)
|
|
239
|
+
}, io);
|
|
240
|
+
else if (ctx.explicit.stdinFilepath) code = argumentError(io, ["--stdin-filepath needs a path"]);
|
|
241
|
+
else if (patterns.length === 0) code = argumentError(io, ["no patterns of the files to format"]);
|
|
242
|
+
else if (!write && !check && !listDifferent) code = argumentError(io, ["give --write, --check, or --list-different with the patterns"]);
|
|
243
|
+
else code = await formatFiles(patterns, {
|
|
244
|
+
write: Boolean(write),
|
|
245
|
+
check: Boolean(check),
|
|
246
|
+
listDifferent: Boolean(listDifferent)
|
|
247
|
+
}, io);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
try {
|
|
251
|
+
const printed = await cli([...args], command, {
|
|
252
|
+
name: "mmdfmt",
|
|
253
|
+
version,
|
|
254
|
+
description,
|
|
255
|
+
strict: true,
|
|
256
|
+
usageSilent: true
|
|
257
|
+
});
|
|
258
|
+
if (printed !== void 0) io.stdout(printed.endsWith("\n") ? printed : `${printed}\n`);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
if (error instanceof AggregateError && error.errors.every(isArgsValidationError)) return argumentError(io, error.errors.map(messageOf));
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
return code;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Formats the files that the patterns select, following Prettier's counts
|
|
267
|
+
* and reports for `write`, `check`, and `listDifferent`.
|
|
268
|
+
*/
|
|
269
|
+
async function formatFiles(patterns, mode, io) {
|
|
270
|
+
if (mode.check) io.stdout("Checking formatting...\n");
|
|
271
|
+
const { files, noSelectingPattern, skipped, unmatched, excluded } = await selectFiles(patterns, io.cwd);
|
|
272
|
+
let failed = noSelectingPattern || unmatched.length > 0 || excluded.length > 0;
|
|
273
|
+
let numberOfUnformattedFilesFound = 0;
|
|
274
|
+
let numberOfFilesWithError = 0;
|
|
275
|
+
if (noSelectingPattern) argumentError(io, [skipped.length > 0 ? "no patterns of the files to format; a pattern in node_modules or a directory of version control is skipped" : "no patterns of the files to format; a pattern that starts with ! only excludes files"]);
|
|
276
|
+
for (const pattern of unmatched) io.stderr(`mmdfmt: error: no file ending in ${endingsInWords} matches ${JSON.stringify(pattern)}\n`);
|
|
277
|
+
for (const pattern of excluded) io.stderr(`mmdfmt: error: a pattern that starts with ! excludes the file ${JSON.stringify(pattern)}\n`);
|
|
278
|
+
for (const file of files) {
|
|
279
|
+
const name = displayName(file.path, io.cwd);
|
|
280
|
+
let text, result;
|
|
281
|
+
try {
|
|
282
|
+
if (await isSkipped(file.path, io.cwd)) continue;
|
|
283
|
+
text = await readFile(file.path, "utf8");
|
|
284
|
+
} catch (error) {
|
|
285
|
+
io.stderr(`${name}: error: ${messageOf(error)}\n`);
|
|
286
|
+
failed = true;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
const start = performance.now();
|
|
290
|
+
try {
|
|
291
|
+
result = await formatFile(text, file.path, file.kind, mode.write ? "output" : "input");
|
|
292
|
+
report(io, name, result.diagnostics);
|
|
293
|
+
if (result.diagnostics.some(isError)) {
|
|
294
|
+
failed = true;
|
|
295
|
+
numberOfFilesWithError += 1;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
} catch (error) {
|
|
299
|
+
io.stderr(`${name}: error: ${messageOf(error)}\n`);
|
|
300
|
+
failed = true;
|
|
301
|
+
numberOfFilesWithError += 1;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
const listsTimes = mode.write && !mode.check && !mode.listDifferent;
|
|
305
|
+
const time = `${Math.round(performance.now() - start)}ms`;
|
|
306
|
+
if (result.text === text) {
|
|
307
|
+
if (listsTimes) io.stdout(`${style(io.stdoutStream, "gray", name)} ${time} (unchanged)\n`);
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
if (mode.write) {
|
|
311
|
+
if (listsTimes) io.stdout(`${name} ${time}\n`);
|
|
312
|
+
try {
|
|
313
|
+
await writeFile(file.path, result.text);
|
|
314
|
+
} catch (error) {
|
|
315
|
+
io.stderr(`${name}: error: ${messageOf(error)}\n`);
|
|
316
|
+
failed = true;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (mode.check) warn(io, name);
|
|
320
|
+
else if (mode.listDifferent) io.stdout(`${name}\n`);
|
|
321
|
+
numberOfUnformattedFilesFound += 1;
|
|
322
|
+
}
|
|
323
|
+
if (mode.check) {
|
|
324
|
+
if (numberOfFilesWithError > 0) {
|
|
325
|
+
const filesDescription = numberOfFilesWithError === 1 ? "the above file" : `${numberOfFilesWithError} files`;
|
|
326
|
+
io.stdout(`Error occurred when checking code style in ${filesDescription}.\n`);
|
|
327
|
+
} else if (numberOfUnformattedFilesFound === 0) io.stdout("All diagrams in the matched files use Prettier code style!\n");
|
|
328
|
+
else {
|
|
329
|
+
const filesDescription = numberOfUnformattedFilesFound === 1 ? "the above file" : `${numberOfUnformattedFilesFound} files`;
|
|
330
|
+
warn(io, mode.write ? `Code style issues fixed in ${filesDescription}.` : `Code style issues found in ${filesDescription}. Run mmdfmt with --write to fix.`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (failed) return exitCodes.error;
|
|
334
|
+
return numberOfUnformattedFilesFound > 0 && !mode.write ? exitCodes.changed : exitCodes.ok;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Formats standard input as the file at `filepath`, and writes the result to
|
|
338
|
+
* standard output. A skipped path gets its input back unchanged, and a
|
|
339
|
+
* rejected diagram file gets nothing. With `listsDifference` (`--check` or
|
|
340
|
+
* `--list-different`), the result is not written: `(stdin)` is listed when it
|
|
341
|
+
* differs from the input, unless `write` is given too, as Prettier lists it.
|
|
342
|
+
* `write` alone changes nothing: no file is written.
|
|
343
|
+
*/
|
|
344
|
+
async function formatStandardInput(filepath, mode, io) {
|
|
345
|
+
const file = path.resolve(io.cwd, filepath);
|
|
346
|
+
const kind = kindOf(file);
|
|
347
|
+
if (kind === void 0) {
|
|
348
|
+
io.stderr(`mmdfmt: error: the path of --stdin-filepath does not end in ${endingsInWords}: ${JSON.stringify(filepath)}\n`);
|
|
349
|
+
return exitCodes.error;
|
|
350
|
+
}
|
|
351
|
+
const text = await io.readStdin();
|
|
352
|
+
try {
|
|
353
|
+
if (await isSkipped(file, io.cwd)) {
|
|
354
|
+
io.stdout(text);
|
|
355
|
+
return exitCodes.ok;
|
|
356
|
+
}
|
|
357
|
+
const result = await formatFile(text, file, kind, mode.listsDifference ? "input" : "output");
|
|
358
|
+
report(io, filepath, result.diagnostics);
|
|
359
|
+
if (result.diagnostics.some(isError)) return exitCodes.error;
|
|
360
|
+
if (!mode.listsDifference) {
|
|
361
|
+
io.stdout(result.text);
|
|
362
|
+
return exitCodes.ok;
|
|
363
|
+
}
|
|
364
|
+
if (result.text === text || mode.write) return exitCodes.ok;
|
|
365
|
+
io.stdout("(stdin)\n");
|
|
366
|
+
return exitCodes.changed;
|
|
367
|
+
} catch (error) {
|
|
368
|
+
io.stderr(`${filepath}: error: ${messageOf(error)}\n`);
|
|
369
|
+
return exitCodes.error;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Writes a warning of `--check` to standard error, as Prettier's logger
|
|
374
|
+
* writes one: `[warn] ` before the text, with the word `warn` in yellow where
|
|
375
|
+
* the stream shows colors.
|
|
376
|
+
*/
|
|
377
|
+
function warn(io, text) {
|
|
378
|
+
io.stderr(`[${style(io.stderrStream, "yellow", "warn")}] ${text}\n`);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* `text` in `color` where Node's `util.styleText` colors text for `stream`,
|
|
382
|
+
* and as it is without a stream.
|
|
383
|
+
*/
|
|
384
|
+
function style(stream, color, text) {
|
|
385
|
+
return stream === void 0 ? text : styleText(color, text, { stream });
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* The name of a file in what the tool prints: its path relative to `cwd`, with
|
|
389
|
+
* `/` between its parts on every system, as Prettier's CLI writes the name of a
|
|
390
|
+
* file (README, Usage). `pathOf` is `node:path` of the system the tool runs on;
|
|
391
|
+
* a test gives `path.win32` or `path.posix`.
|
|
392
|
+
*/
|
|
393
|
+
function displayName(file, cwd, pathOf = path) {
|
|
394
|
+
return pathOf.relative(cwd, file).replaceAll(pathOf.sep, "/");
|
|
395
|
+
}
|
|
396
|
+
/** Writes the diagnostics of a file to standard error. */
|
|
397
|
+
function report(io, name, diagnostics) {
|
|
398
|
+
for (const { severity, message, position } of diagnostics) io.stderr(`${name}:${position.line}:${position.column}: ${severity}: ${message}\n`);
|
|
399
|
+
}
|
|
400
|
+
/** Whether a diagnostic makes the run fail. */
|
|
401
|
+
function isError(diagnostic) {
|
|
402
|
+
return diagnostic.severity === "error";
|
|
403
|
+
}
|
|
404
|
+
/** Reports wrong uses of the arguments, and returns 2 (README, Usage). */
|
|
405
|
+
function argumentError(io, messages) {
|
|
406
|
+
for (const message of messages) io.stderr(`mmdfmt: error: ${message}\n`);
|
|
407
|
+
return exitCodes.error;
|
|
408
|
+
}
|
|
409
|
+
/** The message of a thrown value. */
|
|
410
|
+
function messageOf(error) {
|
|
411
|
+
return error instanceof Error ? error.message : String(error);
|
|
412
|
+
}
|
|
413
|
+
//#endregion
|
|
414
|
+
//#region src/cli.ts
|
|
415
|
+
/** Reads standard input to its end, as UTF-8. */
|
|
416
|
+
async function readStdin() {
|
|
417
|
+
return text(process.stdin.setEncoding("utf8"));
|
|
418
|
+
}
|
|
419
|
+
process.stdout.on("error", ignoreBrokenPipe);
|
|
420
|
+
process.exitCode = await main(process.argv.slice(2), {
|
|
421
|
+
cwd: process.cwd(),
|
|
422
|
+
readStdin,
|
|
423
|
+
stdout: (output) => {
|
|
424
|
+
process.stdout.write(output);
|
|
425
|
+
},
|
|
426
|
+
stderr: (output) => {
|
|
427
|
+
process.stderr.write(output);
|
|
428
|
+
},
|
|
429
|
+
stdoutStream: process.stdout,
|
|
430
|
+
stderrStream: process.stderr
|
|
431
|
+
});
|
|
432
|
+
//#endregion
|
|
433
|
+
export {};
|