punctilio 3.13.2 → 4.1.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/.pre-commit-hooks.yaml +1 -1
- package/README.md +30 -11
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +190 -60
- package/dist/cli.js.map +1 -1
- package/dist/dashes.js +4 -4
- package/dist/dashes.js.map +1 -1
- package/dist/html.d.ts +5 -0
- package/dist/html.d.ts.map +1 -1
- package/dist/html.js +8 -2
- package/dist/html.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/markdown.d.ts +9 -0
- package/dist/markdown.d.ts.map +1 -1
- package/dist/markdown.js +7 -1
- package/dist/markdown.js.map +1 -1
- package/dist/prettier-plugin.d.ts.map +1 -1
- package/dist/prettier-plugin.js +13 -1
- package/dist/prettier-plugin.js.map +1 -1
- package/dist/rehype.d.ts +15 -1
- package/dist/rehype.d.ts.map +1 -1
- package/dist/rehype.js +41 -12
- package/dist/rehype.js.map +1 -1
- package/dist/remark.d.ts +5 -0
- package/dist/remark.d.ts.map +1 -1
- package/dist/remark.js +7 -6
- package/dist/remark.js.map +1 -1
- package/dist/symbols.d.ts.map +1 -1
- package/dist/symbols.js +2 -3
- package/dist/symbols.js.map +1 -1
- package/dist/utils.d.ts +18 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +40 -3
- package/dist/utils.js.map +1 -1
- package/package.json +2 -3
- package/src/cli.ts +200 -59
- package/src/dashes.ts +4 -4
- package/src/html.ts +11 -2
- package/src/index.ts +15 -4
- package/src/markdown.ts +15 -1
- package/src/prettier-plugin.ts +13 -1
- package/src/rehype.ts +60 -11
- package/src/remark.ts +13 -6
- package/src/symbols.ts +2 -3
- package/src/utils.ts +48 -3
package/dist/utils.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Formats a string for error messages, truncating above the threshold.
|
|
3
|
-
*
|
|
3
|
+
* When the PUNCTILIO_DEBUG environment variable is set to a non-empty
|
|
4
|
+
* value, also writes the full content to stderr in Node.js so build logs
|
|
5
|
+
* capture it.
|
|
4
6
|
*/
|
|
5
7
|
export declare function formatErrorString(content: string, label: string): string;
|
|
6
8
|
/** Throws if any text node contains the separator (would corrupt split/join). */
|
|
@@ -18,11 +20,26 @@ interface TextNode {
|
|
|
18
20
|
export declare function transformTextNodes(textNodes: TextNode[], transformFn: (input: string) => string, separator: string): void;
|
|
19
21
|
/** Throws if a transformation changed the separator count. */
|
|
20
22
|
export declare function assertSeparatorCountPreserved(original: string, transformed: string, separator?: string, transformName?: string): void;
|
|
23
|
+
/** Throws if `options` contains a key not present in `validKeys`. @internal */
|
|
24
|
+
export declare function assertKnownOptionKeys(options: object, validKeys: readonly string[], context: string): void;
|
|
25
|
+
/** Returns a copy of `obj` without the listed keys. @internal */
|
|
26
|
+
export declare function omitKeys<T extends object>(obj: T, keys: readonly string[]): Partial<T>;
|
|
21
27
|
/** Returns a copy of `obj` with `undefined`-valued keys removed. @internal */
|
|
22
28
|
export declare function filterUndefined<T extends object>(obj: T): Partial<T>;
|
|
23
29
|
/** Deterministic JSON string for an options object, suitable as cache key. @internal */
|
|
24
30
|
export declare function stableStringify(obj: object): string;
|
|
25
31
|
/** Extracts named groups from a `.replace()` callback's arguments. @internal */
|
|
26
32
|
export declare function namedGroups<G>(args: unknown[]): G;
|
|
33
|
+
/**
|
|
34
|
+
* Extracts the match offset and input string from a `.replace()` callback's
|
|
35
|
+
* arguments, independent of capture-group count. The trailing arguments are
|
|
36
|
+
* `..., offset, input[, namedGroups]`: the input is the last string (the
|
|
37
|
+
* named-groups object, when present, follows it) and the offset is the
|
|
38
|
+
* number immediately before the input. @internal
|
|
39
|
+
*/
|
|
40
|
+
export declare function replaceCallbackContext(args: unknown[]): {
|
|
41
|
+
offset: number;
|
|
42
|
+
input: string;
|
|
43
|
+
};
|
|
27
44
|
export {};
|
|
28
45
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAIA
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAkBxE;AAED,iFAAiF;AACjF,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAanF;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,MAA0B,GAAG,MAAM,CAS3F;AAED,UAAU,QAAQ;IAChB,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,QAAQ,EAAE,EACrB,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EACtC,SAAS,EAAE,MAAM,GAChB,IAAI,CAmBN;AAED,8DAA8D;AAC9D,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,SAAS,GAAE,MAA0B,EACrC,aAAa,GAAE,MAAoB,GAClC,IAAI,CAUN;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,OAAO,EAAE,MAAM,GACd,IAAI,CAQN;AAED,iEAAiE;AACjE,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAMtF;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAMpE;AAED,wFAAwF;AACxF,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMnD;AAED,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,CAEjD;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAUzF"}
|
package/dist/utils.js
CHANGED
|
@@ -2,15 +2,17 @@ import { DEFAULT_SEPARATOR, ISSUES_URL } from "./constants.js";
|
|
|
2
2
|
const ERROR_STRING_THRESHOLD = 2000;
|
|
3
3
|
/**
|
|
4
4
|
* Formats a string for error messages, truncating above the threshold.
|
|
5
|
-
*
|
|
5
|
+
* When the PUNCTILIO_DEBUG environment variable is set to a non-empty
|
|
6
|
+
* value, also writes the full content to stderr in Node.js so build logs
|
|
7
|
+
* capture it.
|
|
6
8
|
*/
|
|
7
9
|
export function formatErrorString(content, label) {
|
|
8
10
|
if (content.length <= ERROR_STRING_THRESHOLD) {
|
|
9
11
|
return JSON.stringify(content);
|
|
10
12
|
}
|
|
11
|
-
// In Node.js, write full content to stderr for debugging
|
|
13
|
+
// In Node.js, write full content to stderr for debugging when opted in
|
|
12
14
|
try {
|
|
13
|
-
if (typeof globalThis.process?.stderr?.write === "function") {
|
|
15
|
+
if (globalThis.process?.env?.PUNCTILIO_DEBUG && typeof globalThis.process?.stderr?.write === "function") {
|
|
14
16
|
globalThis.process.stderr.write(`\n[punctilio ${label} full content (${content.length} chars)]:\n${content}\n\n`);
|
|
15
17
|
}
|
|
16
18
|
}
|
|
@@ -75,6 +77,23 @@ export function assertSeparatorCountPreserved(original, transformed, separator =
|
|
|
75
77
|
`Include the input text that caused this error.`);
|
|
76
78
|
}
|
|
77
79
|
}
|
|
80
|
+
/** Throws if `options` contains a key not present in `validKeys`. @internal */
|
|
81
|
+
export function assertKnownOptionKeys(options, validKeys, context) {
|
|
82
|
+
for (const key of Object.keys(options)) {
|
|
83
|
+
if (!validKeys.includes(key)) {
|
|
84
|
+
throw new Error(`Unknown option "${key}" for ${context}. Valid options: ${[...validKeys].sort().join(", ")}.`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Returns a copy of `obj` without the listed keys. @internal */
|
|
89
|
+
export function omitKeys(obj, keys) {
|
|
90
|
+
const result = {};
|
|
91
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
92
|
+
if (!keys.includes(k))
|
|
93
|
+
result[k] = v;
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
78
97
|
/** Returns a copy of `obj` with `undefined`-valued keys removed. @internal */
|
|
79
98
|
export function filterUndefined(obj) {
|
|
80
99
|
const result = {};
|
|
@@ -96,4 +115,22 @@ export function stableStringify(obj) {
|
|
|
96
115
|
export function namedGroups(args) {
|
|
97
116
|
return args[args.length - 1];
|
|
98
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Extracts the match offset and input string from a `.replace()` callback's
|
|
120
|
+
* arguments, independent of capture-group count. The trailing arguments are
|
|
121
|
+
* `..., offset, input[, namedGroups]`: the input is the last string (the
|
|
122
|
+
* named-groups object, when present, follows it) and the offset is the
|
|
123
|
+
* number immediately before the input. @internal
|
|
124
|
+
*/
|
|
125
|
+
export function replaceCallbackContext(args) {
|
|
126
|
+
let inputIndex = args.length - 1;
|
|
127
|
+
while (inputIndex >= 0 && typeof args[inputIndex] !== "string") {
|
|
128
|
+
inputIndex--;
|
|
129
|
+
}
|
|
130
|
+
const offset = args[inputIndex - 1];
|
|
131
|
+
if (inputIndex < 1 || typeof offset !== "number") {
|
|
132
|
+
throw new Error("Could not locate offset and input in replace-callback arguments.");
|
|
133
|
+
}
|
|
134
|
+
return { offset, input: args[inputIndex] };
|
|
135
|
+
}
|
|
99
136
|
//# sourceMappingURL=utils.js.map
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAE9D,MAAM,sBAAsB,GAAG,IAAI,CAAA;AAEnC
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "punctilio",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Smart typography transformations: curly quotes, em-dashes, en-dashes, and more",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
},
|
|
77
77
|
"homepage": "https://github.com/alexander-turner/punctilio#readme",
|
|
78
78
|
"engines": {
|
|
79
|
-
"node": ">=
|
|
79
|
+
"node": ">=20.0.0"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
82
|
"@eslint/js": "9.39.4",
|
|
@@ -145,7 +145,6 @@
|
|
|
145
145
|
}
|
|
146
146
|
},
|
|
147
147
|
"dependencies": {
|
|
148
|
-
"commander": "14.0.3",
|
|
149
148
|
"cosmiconfig": "9.0.1",
|
|
150
149
|
"escape-string-regexp": "5.0.0",
|
|
151
150
|
"ignore": "7.0.5",
|
package/src/cli.ts
CHANGED
|
@@ -5,17 +5,17 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
|
5
5
|
import { readFile, writeFile } from "node:fs/promises"
|
|
6
6
|
import { dirname, extname, isAbsolute, join, relative, resolve } from "node:path"
|
|
7
7
|
import { fileURLToPath } from "node:url"
|
|
8
|
+
import { parseArgs } from "node:util"
|
|
8
9
|
|
|
9
|
-
import { Command, CommanderError, Option } from "commander"
|
|
10
10
|
import { cosmiconfig } from "cosmiconfig"
|
|
11
11
|
import ignore, { type Ignore } from "ignore"
|
|
12
12
|
import { glob } from "tinyglobby"
|
|
13
13
|
|
|
14
|
-
import { type MarkdownOptions, transformMarkdown } from "./markdown.js"
|
|
15
|
-
import { type HtmlOptions, transformHtml } from "./html.js"
|
|
14
|
+
import { MARKDOWN_ONLY_OPTION_KEYS, MARKDOWN_OPTION_KEYS, type MarkdownOptions, transformMarkdown } from "./markdown.js"
|
|
15
|
+
import { HTML_ONLY_OPTION_KEYS, HTML_OPTION_KEYS, type HtmlOptions, transformHtml } from "./html.js"
|
|
16
16
|
import { PUNCTUATION_STYLES, type PunctuationStyle } from "./quotes.js"
|
|
17
17
|
import { DASH_STYLES, type DashStyle } from "./dashes.js"
|
|
18
|
-
import { stableStringify } from "./utils.js"
|
|
18
|
+
import { assertKnownOptionKeys, omitKeys, stableStringify } from "./utils.js"
|
|
19
19
|
|
|
20
20
|
type CliOptions = MarkdownOptions & HtmlOptions
|
|
21
21
|
type FileType = "md" | "html"
|
|
@@ -36,8 +36,9 @@ const RULE_MARKERS = ["-", "*", "_"] as const
|
|
|
36
36
|
const MARKDOWN_EXTENSIONS = new Set([".md", ".markdown"])
|
|
37
37
|
const HTML_EXTENSIONS = new Set([".html", ".htm"])
|
|
38
38
|
|
|
39
|
-
//
|
|
39
|
+
// camelCased flag values; buildFlags validates choice literals at runtime.
|
|
40
40
|
interface ParsedFlags {
|
|
41
|
+
write?: boolean
|
|
41
42
|
check?: boolean
|
|
42
43
|
cache?: boolean
|
|
43
44
|
cacheLocation?: string
|
|
@@ -64,42 +65,158 @@ interface ParsedFlags {
|
|
|
64
65
|
fragment?: boolean
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
68
|
+
const PARSE_ARGS_OPTIONS = {
|
|
69
|
+
"bullet-marker": { type: "string" },
|
|
70
|
+
"cache-location": { type: "string" },
|
|
71
|
+
check: { type: "boolean" },
|
|
72
|
+
config: { type: "string" },
|
|
73
|
+
"dash-style": { type: "string" },
|
|
74
|
+
degrees: { type: "boolean" },
|
|
75
|
+
"emphasis-marker": { type: "string" },
|
|
76
|
+
fractions: { type: "boolean" },
|
|
77
|
+
help: { short: "h", type: "boolean" },
|
|
78
|
+
"ignore-path": { type: "string" },
|
|
79
|
+
ligatures: { type: "boolean" },
|
|
80
|
+
nbsp: { type: "boolean" },
|
|
81
|
+
"no-arrows": { type: "boolean" },
|
|
82
|
+
"no-cache": { type: "boolean" },
|
|
83
|
+
"no-collapse-spaces": { type: "boolean" },
|
|
84
|
+
"no-config": { type: "boolean" },
|
|
85
|
+
"no-fragment": { type: "boolean" },
|
|
86
|
+
"no-nbsp": { type: "boolean" },
|
|
87
|
+
"no-symbols": { type: "boolean" },
|
|
88
|
+
"punctuation-style": { type: "string" },
|
|
89
|
+
"rule-marker": { type: "string" },
|
|
90
|
+
"skip-class": { multiple: true, type: "string" },
|
|
91
|
+
"skip-tag": { multiple: true, type: "string" },
|
|
92
|
+
"stdin-filepath": { type: "string" },
|
|
93
|
+
"strong-marker": { type: "string" },
|
|
94
|
+
superscript: { type: "boolean" },
|
|
95
|
+
type: { type: "string" },
|
|
96
|
+
version: { short: "V", type: "boolean" },
|
|
97
|
+
write: { type: "boolean" },
|
|
98
|
+
} as const
|
|
99
|
+
|
|
100
|
+
const HELP_TEXT = `Usage: punctilio [options] [files...]
|
|
101
|
+
|
|
102
|
+
Apply typographic improvements to Markdown and HTML files.
|
|
103
|
+
|
|
104
|
+
Arguments:
|
|
105
|
+
files files to format (pass '-' to read from stdin)
|
|
106
|
+
|
|
107
|
+
Options:
|
|
108
|
+
-V, --version show version
|
|
109
|
+
--write rewrite files in place (default prints formatted output to stdout)
|
|
110
|
+
--check exit 1 if any file would change; write nothing
|
|
111
|
+
--config <path> path to a punctilio config file (otherwise auto-discovered)
|
|
112
|
+
--no-config ignore any auto-discovered config file
|
|
113
|
+
--ignore-path <path> path to an ignore file (otherwise .punctilioignore in cwd)
|
|
114
|
+
--no-cache disable the incremental cache (default location node_modules/.cache/punctilio/cache.json)
|
|
115
|
+
--cache-location <path> override the cache file location
|
|
116
|
+
--stdin-filepath <path> treat stdin as if it were this filename (infers type from extension)
|
|
117
|
+
--type <type> force file type (otherwise inferred from extension) (choices: ${FILE_TYPES.join(", ")})
|
|
118
|
+
--punctuation-style <style> quote and punctuation style (choices: ${PUNCTUATION_STYLES.join(", ")})
|
|
119
|
+
--dash-style <style> dash style (choices: ${DASH_STYLES.join(", ")})
|
|
120
|
+
--no-symbols disable symbol transforms (ellipsis, ×, ©, etc.)
|
|
121
|
+
--nbsp enable non-breaking space insertion (default: on for HTML, off for Markdown)
|
|
122
|
+
--no-nbsp disable non-breaking space insertion
|
|
123
|
+
--no-collapse-spaces preserve runs of consecutive spaces
|
|
124
|
+
--no-arrows disable -> → arrow transforms
|
|
125
|
+
--fractions enable 1/2 → ½
|
|
126
|
+
--degrees enable 20 C → 20 °C
|
|
127
|
+
--superscript enable 1st → 1ˢᵗ
|
|
128
|
+
--ligatures enable !? → ⁉
|
|
129
|
+
--emphasis-marker <char> markdown emphasis character (choices: ${EMPHASIS_MARKERS.join(", ")})
|
|
130
|
+
--strong-marker <char> markdown strong emphasis character (choices: ${EMPHASIS_MARKERS.join(", ")})
|
|
131
|
+
--bullet-marker <char> markdown list bullet character (choices: ${BULLET_MARKERS.join(", ")})
|
|
132
|
+
--rule-marker <char> markdown thematic break character (choices: ${RULE_MARKERS.join(", ")})
|
|
133
|
+
--skip-tag <tag> HTML tag whose contents are left alone (repeatable)
|
|
134
|
+
--skip-class <class> HTML class name whose elements are skipped (repeatable)
|
|
135
|
+
--no-fragment parse input as a full HTML document
|
|
136
|
+
-h, --help display help for command
|
|
137
|
+
`
|
|
138
|
+
|
|
139
|
+
function parseRawArgs(args: string[]) {
|
|
140
|
+
try {
|
|
141
|
+
return parseArgs({
|
|
142
|
+
args,
|
|
143
|
+
allowPositionals: true,
|
|
144
|
+
options: PARSE_ARGS_OPTIONS,
|
|
145
|
+
strict: true,
|
|
146
|
+
tokens: true,
|
|
102
147
|
})
|
|
148
|
+
} catch (err) {
|
|
149
|
+
// parseArgs throws on unknown options and missing option values.
|
|
150
|
+
throw new UsageError((err as Error).message)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
type ParsedValues = ReturnType<typeof parseRawArgs>["values"]
|
|
155
|
+
type ParsedTokens = ReturnType<typeof parseRawArgs>["tokens"]
|
|
156
|
+
|
|
157
|
+
function parseChoice<T extends string>(
|
|
158
|
+
flag: string,
|
|
159
|
+
value: string | undefined,
|
|
160
|
+
choices: readonly T[],
|
|
161
|
+
): T | undefined {
|
|
162
|
+
if (value === undefined) return undefined
|
|
163
|
+
if (!(choices as readonly string[]).includes(value)) {
|
|
164
|
+
throw new UsageError(
|
|
165
|
+
`option '${flag}' argument '${value}' is invalid. Allowed choices are ${choices.join(", ")}.`,
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
return value as T
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Maps a `--no-x` boolean to the library's `x: false`, or undefined when unset. */
|
|
172
|
+
function negated(noFlag: boolean | undefined): false | undefined {
|
|
173
|
+
return noFlag ? false : undefined
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Returns the name of whichever of `names` appears last on the command line. */
|
|
177
|
+
function lastFlagToken(tokens: ParsedTokens, names: readonly string[]): string | undefined {
|
|
178
|
+
let lastName: string | undefined
|
|
179
|
+
for (const token of tokens) {
|
|
180
|
+
if (token.kind === "option" && names.includes(token.name)) lastName = token.name
|
|
181
|
+
}
|
|
182
|
+
return lastName
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function buildFlags(values: ParsedValues, tokens: ParsedTokens): ParsedFlags {
|
|
186
|
+
const flags: ParsedFlags = {
|
|
187
|
+
write: values.write,
|
|
188
|
+
check: values.check,
|
|
189
|
+
cache: negated(values["no-cache"]),
|
|
190
|
+
cacheLocation: values["cache-location"],
|
|
191
|
+
ignorePath: values["ignore-path"],
|
|
192
|
+
stdinFilepath: values["stdin-filepath"],
|
|
193
|
+
type: parseChoice("--type", values.type, FILE_TYPES),
|
|
194
|
+
punctuationStyle: parseChoice("--punctuation-style", values["punctuation-style"], PUNCTUATION_STYLES),
|
|
195
|
+
dashStyle: parseChoice("--dash-style", values["dash-style"], DASH_STYLES),
|
|
196
|
+
symbols: negated(values["no-symbols"]),
|
|
197
|
+
collapseSpaces: negated(values["no-collapse-spaces"]),
|
|
198
|
+
arrows: negated(values["no-arrows"]),
|
|
199
|
+
fractions: values.fractions,
|
|
200
|
+
degrees: values.degrees,
|
|
201
|
+
superscript: values.superscript,
|
|
202
|
+
ligatures: values.ligatures,
|
|
203
|
+
emphasisMarker: parseChoice("--emphasis-marker", values["emphasis-marker"], EMPHASIS_MARKERS),
|
|
204
|
+
strongMarker: parseChoice("--strong-marker", values["strong-marker"], EMPHASIS_MARKERS),
|
|
205
|
+
bulletMarker: parseChoice("--bullet-marker", values["bullet-marker"], BULLET_MARKERS),
|
|
206
|
+
ruleMarker: parseChoice("--rule-marker", values["rule-marker"], RULE_MARKERS),
|
|
207
|
+
skipTag: values["skip-tag"],
|
|
208
|
+
skipClass: values["skip-class"],
|
|
209
|
+
fragment: negated(values["no-fragment"]),
|
|
210
|
+
}
|
|
211
|
+
// nbsp is tri-state: --nbsp / --no-nbsp / unset stays undefined so the
|
|
212
|
+
// per-sink default applies (on for HTML, off for Markdown). When both
|
|
213
|
+
// appear, the last one on the command line wins.
|
|
214
|
+
const nbspToken = lastFlagToken(tokens, ["nbsp", "no-nbsp"])
|
|
215
|
+
if (nbspToken !== undefined) flags.nbsp = nbspToken === "nbsp"
|
|
216
|
+
// config is likewise last-one-wins between --config <path> and --no-config.
|
|
217
|
+
const configToken = lastFlagToken(tokens, ["config", "no-config"])
|
|
218
|
+
if (configToken !== undefined) flags.config = configToken === "config" ? values.config : false
|
|
219
|
+
return flags
|
|
103
220
|
}
|
|
104
221
|
|
|
105
222
|
function inferFileType(path: string, override?: FileType): FileType {
|
|
@@ -112,6 +229,12 @@ function inferFileType(path: string, override?: FileType): FileType {
|
|
|
112
229
|
)
|
|
113
230
|
}
|
|
114
231
|
|
|
232
|
+
// A config file may legitimately mix markdown-only and HTML-only keys for use
|
|
233
|
+
// across file types; transformContent strips the keys the sink doesn't accept.
|
|
234
|
+
const CLI_CONFIG_KEYS: readonly string[] = [
|
|
235
|
+
...new Set([...MARKDOWN_OPTION_KEYS, ...HTML_OPTION_KEYS]),
|
|
236
|
+
]
|
|
237
|
+
|
|
115
238
|
// Config keys must match library option names (punctuationStyle, skipTags, …),
|
|
116
239
|
// not the kebab-cased CLI flag names.
|
|
117
240
|
async function loadConfig(
|
|
@@ -123,6 +246,11 @@ async function loadConfig(
|
|
|
123
246
|
const explorer = cosmiconfig("punctilio")
|
|
124
247
|
const result = configPath ? await explorer.load(configPath) : await explorer.search(cwd)
|
|
125
248
|
if (!result || result.isEmpty) return {}
|
|
249
|
+
try {
|
|
250
|
+
assertKnownOptionKeys(result.config as object, CLI_CONFIG_KEYS, `config file ${result.filepath}`)
|
|
251
|
+
} catch (err) {
|
|
252
|
+
throw new UsageError((err as Error).message)
|
|
253
|
+
}
|
|
126
254
|
return result.config as CliOptions
|
|
127
255
|
}
|
|
128
256
|
|
|
@@ -212,7 +340,7 @@ function buildOptions(flags: ParsedFlags): CliOptions {
|
|
|
212
340
|
if (flags.punctuationStyle) opts.punctuationStyle = flags.punctuationStyle
|
|
213
341
|
if (flags.dashStyle) opts.dashStyle = flags.dashStyle
|
|
214
342
|
if (flags.symbols === false) opts.symbols = false
|
|
215
|
-
if (flags.nbsp
|
|
343
|
+
if (flags.nbsp !== undefined) opts.nbsp = flags.nbsp
|
|
216
344
|
if (flags.collapseSpaces === false) opts.collapseSpaces = false
|
|
217
345
|
if (flags.arrows === false) opts.includeArrows = false
|
|
218
346
|
if (flags.fractions) opts.fractions = true
|
|
@@ -229,8 +357,13 @@ function buildOptions(flags: ParsedFlags): CliOptions {
|
|
|
229
357
|
return opts
|
|
230
358
|
}
|
|
231
359
|
|
|
360
|
+
// The merged options may carry keys for the other sink (e.g. emphasisMarker
|
|
361
|
+
// alongside skipTags from one shared config); strip the inapplicable ones so
|
|
362
|
+
// each sink's strict key validation only sees keys it understands.
|
|
232
363
|
async function transformContent(input: string, type: FileType, opts: CliOptions): Promise<string> {
|
|
233
|
-
return type === "md"
|
|
364
|
+
return type === "md"
|
|
365
|
+
? transformMarkdown(input, omitKeys(opts, HTML_ONLY_OPTION_KEYS) as MarkdownOptions)
|
|
366
|
+
: transformHtml(input, omitKeys(opts, MARKDOWN_ONLY_OPTION_KEYS) as HtmlOptions)
|
|
234
367
|
}
|
|
235
368
|
|
|
236
369
|
// Unified pipelines unconditionally append a trailing newline; undo if original lacked one.
|
|
@@ -264,22 +397,18 @@ async function readPackageVersion(): Promise<string> {
|
|
|
264
397
|
/** Returns exit code: 0 success, 1 `--check` saw changes, 2 usage error. */
|
|
265
398
|
export async function runCli(args: string[], io: CliIO): Promise<number> {
|
|
266
399
|
const version = await readPackageVersion()
|
|
267
|
-
const program = buildProgram(version, io)
|
|
268
|
-
|
|
269
|
-
try {
|
|
270
|
-
program.parse(args, { from: "user" })
|
|
271
|
-
} catch (err) {
|
|
272
|
-
/* istanbul ignore if -- commander only throws CommanderError from parse */
|
|
273
|
-
if (!(err instanceof CommanderError)) throw err
|
|
274
|
-
if (err.code === "commander.helpDisplayed" || err.code === "commander.version") return 0
|
|
275
|
-
return 2
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const flags = program.opts() as ParsedFlags
|
|
279
|
-
const positionals = program.args
|
|
280
400
|
|
|
281
401
|
try {
|
|
282
|
-
|
|
402
|
+
const { positionals, tokens, values } = parseRawArgs(args)
|
|
403
|
+
if (values.help) {
|
|
404
|
+
io.stdout.write(HELP_TEXT)
|
|
405
|
+
return 0
|
|
406
|
+
}
|
|
407
|
+
if (values.version) {
|
|
408
|
+
io.stdout.write(`${version}\n`)
|
|
409
|
+
return 0
|
|
410
|
+
}
|
|
411
|
+
return await runValidated(buildFlags(values, tokens), positionals, io, version)
|
|
283
412
|
} catch (err) {
|
|
284
413
|
if (err instanceof UsageError) {
|
|
285
414
|
io.stderr.write(`Error: ${err.message}\n`)
|
|
@@ -298,7 +427,6 @@ async function runValidated(
|
|
|
298
427
|
flags: ParsedFlags,
|
|
299
428
|
positionals: string[],
|
|
300
429
|
io: CliIO,
|
|
301
|
-
program: Command,
|
|
302
430
|
version: string,
|
|
303
431
|
): Promise<number> {
|
|
304
432
|
const cwd = process.cwd()
|
|
@@ -309,8 +437,15 @@ async function runValidated(
|
|
|
309
437
|
)
|
|
310
438
|
const opts: CliOptions = { ...config, ...buildOptions(flags) }
|
|
311
439
|
const check = flags.check ?? false
|
|
440
|
+
const write = flags.write ?? false
|
|
441
|
+
if (check && write) {
|
|
442
|
+
throw new UsageError("--write and --check are mutually exclusive.")
|
|
443
|
+
}
|
|
312
444
|
|
|
313
445
|
if (readsStdin(positionals, flags)) {
|
|
446
|
+
if (write) {
|
|
447
|
+
throw new UsageError("--write cannot be used when reading from stdin.")
|
|
448
|
+
}
|
|
314
449
|
const fileArgs = positionals.filter((p) => p !== "-")
|
|
315
450
|
if (fileArgs.length > 0) {
|
|
316
451
|
throw new UsageError("'-' cannot be combined with file arguments.")
|
|
@@ -328,14 +463,16 @@ async function runValidated(
|
|
|
328
463
|
}
|
|
329
464
|
|
|
330
465
|
if (positionals.length === 0) {
|
|
331
|
-
io.stderr.write(
|
|
466
|
+
io.stderr.write(HELP_TEXT)
|
|
332
467
|
return 2
|
|
333
468
|
}
|
|
334
469
|
|
|
335
470
|
const ig = loadIgnore(cwd, flags.ignorePath)
|
|
336
471
|
const files = await discoverFiles(positionals, cwd, ig)
|
|
337
472
|
|
|
338
|
-
|
|
473
|
+
// In stdout mode every file's content must be produced, so a cache hit
|
|
474
|
+
// could never skip work; only --write and --check use the cache.
|
|
475
|
+
const cacheLocation = flags.cache === false || (!write && !check)
|
|
339
476
|
? undefined
|
|
340
477
|
: resolve(cwd, flags.cacheLocation ?? DEFAULT_CACHE_LOCATION)
|
|
341
478
|
const cache = cacheLocation ? loadCache(cacheLocation, io.stderr) : undefined
|
|
@@ -357,6 +494,10 @@ async function runValidated(
|
|
|
357
494
|
}
|
|
358
495
|
|
|
359
496
|
const formatted = matchTrailingNewline(original, await transformContent(original, type, opts))
|
|
497
|
+
if (!write && !check) {
|
|
498
|
+
io.stdout.write(formatted)
|
|
499
|
+
continue
|
|
500
|
+
}
|
|
360
501
|
const unchanged = original === formatted
|
|
361
502
|
if (!unchanged) {
|
|
362
503
|
anyChanged = true
|
package/src/dashes.ts
CHANGED
|
@@ -79,10 +79,10 @@ export function enDashNumberRange(text: string, options: DashOptions = {}): stri
|
|
|
79
79
|
// as ranges. Space/NNBSP/thin-space grouping isn't captured by the outer
|
|
80
80
|
// range regex, so those variants currently aren't affected here.
|
|
81
81
|
if (/^\d{3}$/.test(startNum) && /^\d{4}$/.test(endNum)) return args[0] as string
|
|
82
|
-
// Skip US toll-free prefix pattern: 1-800, 1-888, 1-877, etc.
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
if (/^1$/.test(startNum) && /^8
|
|
82
|
+
// Skip US toll-free prefix pattern: 1-800, 1-888, 1-877, etc. Only the
|
|
83
|
+
// seven real toll-free codes (800, 888, 877, 866, 855, 844, 833) are
|
|
84
|
+
// blocked, so genuine ranges like 1-850 or 1-810 still en-dash.
|
|
85
|
+
if (/^1$/.test(startNum) && /^8(?:00|88|77|66|55|44|33)$/.test(endNum)) return args[0] as string
|
|
86
86
|
return `${groups.precedingAreaCode ?? ""}${groups.start}${EN_DASH}${groups.end}${groups.suffix ?? ""}`
|
|
87
87
|
}
|
|
88
88
|
)
|
package/src/html.ts
CHANGED
|
@@ -3,8 +3,8 @@ import rehypeParse from "rehype-parse"
|
|
|
3
3
|
import rehypeStringify from "rehype-stringify"
|
|
4
4
|
import QuickLRU from "quick-lru"
|
|
5
5
|
|
|
6
|
-
import { rehypePunctilio, type RehypePunctilioOptions } from "./rehype.js"
|
|
7
|
-
import { stableStringify } from "./utils.js"
|
|
6
|
+
import { REHYPE_ONLY_OPTION_KEYS, REHYPE_OPTION_KEYS, rehypePunctilio, type RehypePunctilioOptions } from "./rehype.js"
|
|
7
|
+
import { assertKnownOptionKeys, stableStringify } from "./utils.js"
|
|
8
8
|
|
|
9
9
|
export interface HtmlOptions extends RehypePunctilioOptions {
|
|
10
10
|
/**
|
|
@@ -15,6 +15,13 @@ export interface HtmlOptions extends RehypePunctilioOptions {
|
|
|
15
15
|
fragment?: boolean
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/** Option keys handled by the HTML pipeline (parser plus rehype plugin)
|
|
19
|
+
* rather than `transform()`. */
|
|
20
|
+
export const HTML_ONLY_OPTION_KEYS: readonly string[] = ["fragment", ...REHYPE_ONLY_OPTION_KEYS]
|
|
21
|
+
|
|
22
|
+
/** Runtime list of valid `transformHtml` option keys. */
|
|
23
|
+
export const HTML_OPTION_KEYS: readonly string[] = [...REHYPE_OPTION_KEYS, "fragment"]
|
|
24
|
+
|
|
18
25
|
function createProcessor(options: HtmlOptions) {
|
|
19
26
|
const { fragment, ...punctilioOptions } = options
|
|
20
27
|
return unified()
|
|
@@ -38,6 +45,8 @@ export async function transformHtml(
|
|
|
38
45
|
input: string,
|
|
39
46
|
options: HtmlOptions = {}
|
|
40
47
|
): Promise<string> {
|
|
48
|
+
assertKnownOptionKeys(options, HTML_OPTION_KEYS, "transformHtml")
|
|
49
|
+
|
|
41
50
|
// Function-valued options aren't JSON-serializable, so the cache key
|
|
42
51
|
// can't distinguish them. Bypass the cache to avoid returning a
|
|
43
52
|
// processor wired to a different shouldSkipText.
|
package/src/index.ts
CHANGED
|
@@ -139,9 +139,11 @@ export interface TransformOptions {
|
|
|
139
139
|
/**
|
|
140
140
|
* Whether to verify that the transformation is idempotent (running twice
|
|
141
141
|
* produces the same result). When enabled, throws an error if the second
|
|
142
|
-
* pass produces a different result than the first.
|
|
142
|
+
* pass produces a different result than the first. The check doubles the
|
|
143
|
+
* cost of every transform and only detects punctilio's own bugs, so it is
|
|
144
|
+
* off by default; enable it in test suites or when debugging.
|
|
143
145
|
*
|
|
144
|
-
* Default:
|
|
146
|
+
* Default: false
|
|
145
147
|
*/
|
|
146
148
|
checkIdempotency?: boolean
|
|
147
149
|
|
|
@@ -151,7 +153,7 @@ import { niceQuotes } from "./quotes.js"
|
|
|
151
153
|
import { hyphenReplace } from "./dashes.js"
|
|
152
154
|
import { collapseSpaces as collapseSpacesTransform, degrees as degreesTransform, fractions as fractionsTransform, punctuationLigatures as ligaturesTransform, primeMarks, superscriptOrdinal as superscriptTransform, symbolTransform } from "./symbols.js"
|
|
153
155
|
import { nbspTransform as nbspTransformFn } from "./nbsp.js"
|
|
154
|
-
import { assertSeparatorCountPreserved, filterUndefined, formatErrorString } from "./utils.js"
|
|
156
|
+
import { assertKnownOptionKeys, assertSeparatorCountPreserved, filterUndefined, formatErrorString } from "./utils.js"
|
|
155
157
|
import { DEFAULT_SEPARATOR, ISSUES_URL, UNICODE_SYMBOLS } from "./constants.js"
|
|
156
158
|
|
|
157
159
|
export {
|
|
@@ -181,12 +183,21 @@ const defaultOpts: Required<Omit<TransformOptions, "separator">> = {
|
|
|
181
183
|
ligatures: false,
|
|
182
184
|
nbsp: true,
|
|
183
185
|
collapseSpaces: true,
|
|
184
|
-
checkIdempotency:
|
|
186
|
+
checkIdempotency: false,
|
|
185
187
|
punctuationStyle: "american",
|
|
186
188
|
dashStyle: "american",
|
|
187
189
|
}
|
|
188
190
|
|
|
191
|
+
/** Runtime list of valid `transform()` option keys, derived from the option
|
|
192
|
+
* defaults so it cannot drift from {@link TransformOptions}. */
|
|
193
|
+
export const TRANSFORM_OPTION_KEYS: readonly string[] = [
|
|
194
|
+
...Object.keys(defaultOpts),
|
|
195
|
+
"separator",
|
|
196
|
+
]
|
|
197
|
+
|
|
189
198
|
export function transform(text: string, options: TransformOptions = {}): string {
|
|
199
|
+
assertKnownOptionKeys(options, TRANSFORM_OPTION_KEYS, "transform")
|
|
200
|
+
|
|
190
201
|
const separator = options.separator ?? DEFAULT_SEPARATOR
|
|
191
202
|
|
|
192
203
|
if (separator.length === 0) {
|