punctilio 3.9.3 → 3.10.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 +27 -0
- package/README.md +11 -1
- package/dist/cli.d.ts +21 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +324 -0
- package/dist/cli.js.map +1 -0
- package/dist/dashes.d.ts +2 -1
- package/dist/dashes.d.ts.map +1 -1
- package/dist/dashes.js +1 -0
- package/dist/dashes.js.map +1 -1
- package/dist/html.d.ts +44 -0
- package/dist/html.d.ts.map +1 -0
- package/dist/html.js +66 -0
- package/dist/html.js.map +1 -0
- package/dist/prettier-plugin.d.ts +18 -0
- package/dist/prettier-plugin.d.ts.map +1 -0
- package/dist/prettier-plugin.js +41 -0
- package/dist/prettier-plugin.js.map +1 -0
- package/dist/quotes.d.ts +2 -1
- package/dist/quotes.d.ts.map +1 -1
- package/dist/quotes.js +1 -0
- package/dist/quotes.js.map +1 -1
- package/package.json +25 -2
- package/src/cli.ts +393 -0
- package/src/dashes.ts +2 -1
- package/src/html.ts +92 -0
- package/src/prettier-plugin.ts +49 -0
- package/src/quotes.ts +2 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prettier plugin applying punctilio's typographic transforms to Markdown.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```jsonc
|
|
6
|
+
* // .prettierrc
|
|
7
|
+
* { "plugins": ["punctilio/prettier-plugin"] }
|
|
8
|
+
*
|
|
9
|
+
* // .punctiliorc.json (optional)
|
|
10
|
+
* { "punctuationStyle": "british", "skipTags": ["pre", "code"] }
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* @packageDocumentation
|
|
14
|
+
*/
|
|
15
|
+
import { dirname } from "node:path";
|
|
16
|
+
import * as prettierMarkdown from "prettier/plugins/markdown";
|
|
17
|
+
import { cosmiconfig } from "cosmiconfig";
|
|
18
|
+
import { remarkPunctilio } from "./remark.js";
|
|
19
|
+
const upstreamMarkdown = prettierMarkdown.parsers.markdown;
|
|
20
|
+
const explorer = cosmiconfig("punctilio");
|
|
21
|
+
async function loadPunctilioConfig(searchFrom) {
|
|
22
|
+
const result = await explorer.search(searchFrom);
|
|
23
|
+
return !result || result.isEmpty ? {} : result.config;
|
|
24
|
+
}
|
|
25
|
+
const plugin = {
|
|
26
|
+
parsers: {
|
|
27
|
+
markdown: {
|
|
28
|
+
...upstreamMarkdown,
|
|
29
|
+
async parse(text, options) {
|
|
30
|
+
const ast = await upstreamMarkdown.parse(text, options);
|
|
31
|
+
const searchFrom = typeof options.filepath === "string" ? dirname(options.filepath) : process.cwd();
|
|
32
|
+
const opts = await loadPunctilioConfig(searchFrom);
|
|
33
|
+
const transform = remarkPunctilio(opts);
|
|
34
|
+
transform(ast);
|
|
35
|
+
return ast;
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
export default plugin;
|
|
41
|
+
//# sourceMappingURL=prettier-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prettier-plugin.js","sourceRoot":"","sources":["../src/prettier-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,OAAO,KAAK,gBAAgB,MAAM,2BAA2B,CAAA;AAG7D,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEzC,OAAO,EAAE,eAAe,EAA+B,MAAM,aAAa,CAAA;AAE1E,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,OAAO,CAAC,QAAwB,CAAA;AAC1E,MAAM,QAAQ,GAAG,WAAW,CAAC,WAAW,CAAC,CAAA;AAEzC,KAAK,UAAU,mBAAmB,CAAC,UAAkB;IACnD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;IAChD,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAE,MAAM,CAAC,MAAiC,CAAA;AACnF,CAAC;AAED,MAAM,MAAM,GAAW;IACrB,OAAO,EAAE;QACP,QAAQ,EAAE;YACR,GAAG,gBAAgB;YACnB,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO;gBACvB,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;gBACvD,MAAM,UAAU,GAAG,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAA;gBACnG,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,UAAU,CAAC,CAAA;gBAClD,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,CAAyB,CAAA;gBAC/D,SAAS,CAAC,GAAG,CAAC,CAAA;gBACd,OAAO,GAAG,CAAA;YACZ,CAAC;SACF;KACF;CACF,CAAA;AAED,eAAe,MAAM,CAAA"}
|
package/dist/quotes.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Smart quote transformation: straight quotes → curly quotes.
|
|
3
3
|
*/
|
|
4
|
-
export
|
|
4
|
+
export declare const PUNCTUATION_STYLES: readonly ["american", "british", "german", "french", "none"];
|
|
5
|
+
export type PunctuationStyle = (typeof PUNCTUATION_STYLES)[number];
|
|
5
6
|
export interface QuoteOptions {
|
|
6
7
|
/** Boundary marker for HTML element boundaries. Default: "\uE000\uE001" */
|
|
7
8
|
separator?: string;
|
package/dist/quotes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quotes.d.ts","sourceRoot":"","sources":["../src/quotes.ts"],"names":[],"mappings":"AAAA;;GAEG;AAyBH,
|
|
1
|
+
{"version":3,"file":"quotes.d.ts","sourceRoot":"","sources":["../src/quotes.ts"],"names":[],"mappings":"AAAA;;GAEG;AAyBH,eAAO,MAAM,kBAAkB,8DAA+D,CAAA;AAC9F,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAA;AAElE,MAAM,WAAW,YAAY;IAC3B,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;CACpC;AAmTD,+CAA+C;AAC/C,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM,CAK3E;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM,CAEpF"}
|
package/dist/quotes.js
CHANGED
|
@@ -13,6 +13,7 @@ const TERMINAL_PUNCTUATION_CLASS = TERMINAL_PUNCTUATION.join("");
|
|
|
13
13
|
* without any following period or comma.
|
|
14
14
|
*/
|
|
15
15
|
const MAX_NESTED_QUOTES = 4;
|
|
16
|
+
export const PUNCTUATION_STYLES = ["american", "british", "german", "french", "none"];
|
|
16
17
|
/** Convert straight single quotes to curly quotes and apostrophes */
|
|
17
18
|
function convertSingleQuotes(text, sep) {
|
|
18
19
|
const escapedSep = getEscapedSeparator({ separator: sep });
|
package/dist/quotes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quotes.js","sourceRoot":"","sources":["../src/quotes.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,kBAAkB,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,aAAa,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAE3I,MAAM,EACJ,OAAO,EACP,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,0BAA0B,GAC3B,GAAG,eAAe,CAAA;AAEnB,+FAA+F;AAC/F,MAAM,0BAA0B,GAAG,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AAEhE;;;;;GAKG;AACH,MAAM,iBAAiB,GAAG,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"quotes.js","sourceRoot":"","sources":["../src/quotes.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "punctilio",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.10.0",
|
|
4
4
|
"description": "Smart typography transformations: curly quotes, em-dashes, en-dashes, and more",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"main": "./dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"punctilio": "./dist/cli.js"
|
|
11
|
+
},
|
|
9
12
|
"exports": {
|
|
10
13
|
".": {
|
|
11
14
|
"types": "./dist/index.d.ts",
|
|
@@ -26,13 +29,24 @@
|
|
|
26
29
|
"types": "./dist/markdown.d.ts",
|
|
27
30
|
"import": "./dist/markdown.js",
|
|
28
31
|
"default": "./dist/markdown.js"
|
|
32
|
+
},
|
|
33
|
+
"./html": {
|
|
34
|
+
"types": "./dist/html.d.ts",
|
|
35
|
+
"import": "./dist/html.js",
|
|
36
|
+
"default": "./dist/html.js"
|
|
37
|
+
},
|
|
38
|
+
"./prettier-plugin": {
|
|
39
|
+
"types": "./dist/prettier-plugin.d.ts",
|
|
40
|
+
"import": "./dist/prettier-plugin.js",
|
|
41
|
+
"default": "./dist/prettier-plugin.js"
|
|
29
42
|
}
|
|
30
43
|
},
|
|
31
44
|
"files": [
|
|
32
45
|
"dist",
|
|
33
46
|
"src",
|
|
34
47
|
"!src/tests",
|
|
35
|
-
"tsconfig.json"
|
|
48
|
+
"tsconfig.json",
|
|
49
|
+
".pre-commit-hooks.yaml"
|
|
36
50
|
],
|
|
37
51
|
"keywords": [
|
|
38
52
|
"typography",
|
|
@@ -77,6 +91,7 @@
|
|
|
77
91
|
"hastscript": "9.0.1",
|
|
78
92
|
"jest": "29.7.0",
|
|
79
93
|
"lint-staged": "16.4.0",
|
|
94
|
+
"prettier": "3.8.1",
|
|
80
95
|
"recheck": "4.5.0",
|
|
81
96
|
"rehype-parse": "9.0.1",
|
|
82
97
|
"rehype-stringify": "10.0.1",
|
|
@@ -97,6 +112,7 @@
|
|
|
97
112
|
"unified": "11.0.5"
|
|
98
113
|
},
|
|
99
114
|
"peerDependencies": {
|
|
115
|
+
"prettier": "^3.0.0",
|
|
100
116
|
"rehype-parse": "^9.0.0",
|
|
101
117
|
"rehype-stringify": "^10.0.0",
|
|
102
118
|
"remark-gfm": "^4.0.0",
|
|
@@ -105,6 +121,9 @@
|
|
|
105
121
|
"unified": "^11.0.0"
|
|
106
122
|
},
|
|
107
123
|
"peerDependenciesMeta": {
|
|
124
|
+
"prettier": {
|
|
125
|
+
"optional": true
|
|
126
|
+
},
|
|
108
127
|
"unified": {
|
|
109
128
|
"optional": true
|
|
110
129
|
},
|
|
@@ -125,8 +144,12 @@
|
|
|
125
144
|
}
|
|
126
145
|
},
|
|
127
146
|
"dependencies": {
|
|
147
|
+
"commander": "14.0.3",
|
|
148
|
+
"cosmiconfig": "9.0.1",
|
|
128
149
|
"escape-string-regexp": "5.0.0",
|
|
150
|
+
"ignore": "7.0.5",
|
|
129
151
|
"quick-lru": "7.3.0",
|
|
152
|
+
"tinyglobby": "0.2.16",
|
|
130
153
|
"unist-util-visit-parents": "6.0.2"
|
|
131
154
|
},
|
|
132
155
|
"lint-staged": {
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Command-line entry point: format Markdown and HTML files in place,
|
|
4
|
+
* or check whether they would be reformatted (for use as a pre-commit hook).
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createHash } from "node:crypto"
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
11
|
+
import { readFile, writeFile } from "node:fs/promises"
|
|
12
|
+
import { dirname, extname, join, relative, resolve } from "node:path"
|
|
13
|
+
import { fileURLToPath } from "node:url"
|
|
14
|
+
|
|
15
|
+
import { Command, CommanderError, Option } from "commander"
|
|
16
|
+
import { cosmiconfig } from "cosmiconfig"
|
|
17
|
+
import ignore, { type Ignore } from "ignore"
|
|
18
|
+
import { glob } from "tinyglobby"
|
|
19
|
+
|
|
20
|
+
import { transformMarkdown, type MarkdownOptions } from "./markdown.js"
|
|
21
|
+
import { transformHtml, type HtmlOptions } from "./html.js"
|
|
22
|
+
import { PUNCTUATION_STYLES, type PunctuationStyle } from "./quotes.js"
|
|
23
|
+
import { DASH_STYLES, type DashStyle } from "./dashes.js"
|
|
24
|
+
|
|
25
|
+
type CliOptions = MarkdownOptions & HtmlOptions
|
|
26
|
+
type FileType = "md" | "html"
|
|
27
|
+
|
|
28
|
+
interface CliIO {
|
|
29
|
+
stdin: AsyncIterable<string | Buffer>
|
|
30
|
+
stdout: NodeJS.WritableStream
|
|
31
|
+
stderr: NodeJS.WritableStream
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class UsageError extends Error {}
|
|
35
|
+
|
|
36
|
+
const FILE_TYPES = ["md", "html"] as const satisfies readonly FileType[]
|
|
37
|
+
const EMPHASIS_MARKERS = ["*", "_"] as const
|
|
38
|
+
const BULLET_MARKERS = ["-", "*", "+"] as const
|
|
39
|
+
const RULE_MARKERS = ["-", "*", "_"] as const
|
|
40
|
+
|
|
41
|
+
const MARKDOWN_EXTENSIONS = new Set([".md", ".markdown"])
|
|
42
|
+
const HTML_EXTENSIONS = new Set([".html", ".htm"])
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Shape of `program.opts()` after parsing. Fields named after their CLI
|
|
46
|
+
* flags (camelCased by commander). `commander.choices()` guarantees the
|
|
47
|
+
* literal types at runtime; we assert them at the boundary.
|
|
48
|
+
*/
|
|
49
|
+
interface ParsedFlags {
|
|
50
|
+
check?: boolean
|
|
51
|
+
cache?: boolean
|
|
52
|
+
cacheLocation?: string
|
|
53
|
+
config?: string | false // string if --config <path>, false if --no-config
|
|
54
|
+
ignorePath?: string
|
|
55
|
+
stdinFilepath?: string
|
|
56
|
+
type?: FileType
|
|
57
|
+
punctuationStyle?: PunctuationStyle
|
|
58
|
+
dashStyle?: DashStyle
|
|
59
|
+
symbols?: boolean
|
|
60
|
+
nbsp?: boolean
|
|
61
|
+
collapseSpaces?: boolean
|
|
62
|
+
arrows?: boolean
|
|
63
|
+
fractions?: boolean
|
|
64
|
+
degrees?: boolean
|
|
65
|
+
superscript?: boolean
|
|
66
|
+
ligatures?: boolean
|
|
67
|
+
emphasisMarker?: "*" | "_"
|
|
68
|
+
strongMarker?: "*" | "_"
|
|
69
|
+
bulletMarker?: "-" | "*" | "+"
|
|
70
|
+
ruleMarker?: "-" | "*" | "_"
|
|
71
|
+
skipTag?: string[]
|
|
72
|
+
skipClass?: string[]
|
|
73
|
+
fragment?: boolean
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function buildProgram(version: string, io: CliIO): Command {
|
|
77
|
+
return new Command()
|
|
78
|
+
.name("punctilio")
|
|
79
|
+
.description("Apply typographic improvements to Markdown and HTML files.")
|
|
80
|
+
.version(version, "-V, --version", "show version")
|
|
81
|
+
.argument("[files...]", "files to format (pass '-' to read from stdin)")
|
|
82
|
+
.option("--check", "exit 1 if any file would change; write nothing")
|
|
83
|
+
.option("--config <path>", "path to a punctilio config file (otherwise auto-discovered)")
|
|
84
|
+
.option("--no-config", "ignore any auto-discovered config file")
|
|
85
|
+
.option("--ignore-path <path>", "path to an ignore file (otherwise .punctilioignore in cwd)")
|
|
86
|
+
.option("--no-cache", "disable the incremental cache (default location node_modules/.cache/punctilio/cache.json)")
|
|
87
|
+
.option("--cache-location <path>", "override the cache file location")
|
|
88
|
+
.option("--stdin-filepath <path>", "treat stdin as if it were this filename (infers type from extension)")
|
|
89
|
+
.addOption(new Option("--type <type>", "force file type (otherwise inferred from extension)").choices(FILE_TYPES))
|
|
90
|
+
.addOption(new Option("--punctuation-style <style>", "quote and punctuation style").choices(PUNCTUATION_STYLES))
|
|
91
|
+
.addOption(new Option("--dash-style <style>", "dash style").choices(DASH_STYLES))
|
|
92
|
+
.option("--no-symbols", "disable symbol transforms (ellipsis, ×, ©, etc.)")
|
|
93
|
+
.option("--no-nbsp", "disable non-breaking space insertion")
|
|
94
|
+
.option("--no-collapse-spaces", "preserve runs of consecutive spaces")
|
|
95
|
+
.option("--no-arrows", "disable -> → arrow transforms")
|
|
96
|
+
.option("--fractions", "enable 1/2 → ½")
|
|
97
|
+
.option("--degrees", "enable 20 C → 20 °C")
|
|
98
|
+
.option("--superscript", "enable 1st → 1ˢᵗ")
|
|
99
|
+
.option("--ligatures", "enable !? → ⁉")
|
|
100
|
+
.addOption(new Option("--emphasis-marker <char>", "markdown emphasis character").choices(EMPHASIS_MARKERS))
|
|
101
|
+
.addOption(new Option("--strong-marker <char>", "markdown strong emphasis character").choices(EMPHASIS_MARKERS))
|
|
102
|
+
.addOption(new Option("--bullet-marker <char>", "markdown list bullet character").choices(BULLET_MARKERS))
|
|
103
|
+
.addOption(new Option("--rule-marker <char>", "markdown thematic break character").choices(RULE_MARKERS))
|
|
104
|
+
.option("--skip-tag <tag...>", "HTML tags whose contents are left alone (repeatable)")
|
|
105
|
+
.option("--skip-class <class...>", "HTML class names whose elements are skipped (repeatable)")
|
|
106
|
+
.option("--no-fragment", "parse input as a full HTML document")
|
|
107
|
+
.exitOverride()
|
|
108
|
+
.configureOutput({
|
|
109
|
+
writeOut: (str) => { io.stdout.write(str) },
|
|
110
|
+
writeErr: (str) => { io.stderr.write(str) },
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function inferFileType(path: string, override?: FileType): FileType {
|
|
115
|
+
if (override !== undefined) return override
|
|
116
|
+
const ext = extname(path).toLowerCase()
|
|
117
|
+
if (MARKDOWN_EXTENSIONS.has(ext)) return "md"
|
|
118
|
+
if (HTML_EXTENSIONS.has(ext)) return "html"
|
|
119
|
+
throw new UsageError(
|
|
120
|
+
`Cannot infer file type from extension "${ext}" for ${path}. Pass --type md|html.`,
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Config keys must match library option names (punctuationStyle, skipTags, …),
|
|
125
|
+
// not the kebab-cased CLI flag names.
|
|
126
|
+
async function loadConfig(
|
|
127
|
+
cwd: string,
|
|
128
|
+
configPath: string | undefined,
|
|
129
|
+
disabled: boolean,
|
|
130
|
+
): Promise<CliOptions> {
|
|
131
|
+
if (disabled) return {}
|
|
132
|
+
const explorer = cosmiconfig("punctilio")
|
|
133
|
+
const result = configPath ? await explorer.load(configPath) : await explorer.search(cwd)
|
|
134
|
+
if (!result || result.isEmpty) return {}
|
|
135
|
+
return result.config as CliOptions
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function loadIgnore(cwd: string, ignorePath: string | undefined): Ignore {
|
|
139
|
+
const ig = ignore()
|
|
140
|
+
const effective = ignorePath ?? join(cwd, ".punctilioignore")
|
|
141
|
+
if (existsSync(effective)) ig.add(readFileSync(effective, "utf8"))
|
|
142
|
+
return ig
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isGlobPattern(pattern: string): boolean {
|
|
146
|
+
return /[*?[\]{}]/.test(pattern)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Non-glob positionals pass through as literal paths so they error loudly
|
|
150
|
+
// if missing, matching the prior behavior of `punctilio file.md`.
|
|
151
|
+
async function discoverFiles(
|
|
152
|
+
patterns: string[],
|
|
153
|
+
cwd: string,
|
|
154
|
+
ig: Ignore,
|
|
155
|
+
): Promise<string[]> {
|
|
156
|
+
const literals = patterns.filter((p) => !isGlobPattern(p))
|
|
157
|
+
const globs = patterns.filter(isGlobPattern)
|
|
158
|
+
const expanded = globs.length > 0
|
|
159
|
+
? await glob(globs, { cwd, absolute: true, onlyFiles: true })
|
|
160
|
+
: []
|
|
161
|
+
const all = [...literals.map((p) => resolve(cwd, p)), ...expanded]
|
|
162
|
+
return all.filter((file) => {
|
|
163
|
+
const rel = relative(cwd, file)
|
|
164
|
+
return rel.startsWith("..") || !ig.ignores(rel)
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
interface CacheEntry {
|
|
169
|
+
contentHash: string
|
|
170
|
+
optionsHash: string
|
|
171
|
+
version: string
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
interface Cache {
|
|
175
|
+
files: Record<string, CacheEntry>
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const DEFAULT_CACHE_LOCATION = join("node_modules", ".cache", "punctilio", "cache.json")
|
|
179
|
+
|
|
180
|
+
function hashString(s: string): string {
|
|
181
|
+
return createHash("sha256").update(s).digest("hex").slice(0, 16)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function hashOptions(opts: CliOptions): string {
|
|
185
|
+
const stable = Object.entries(opts)
|
|
186
|
+
.filter(([, v]) => v !== undefined)
|
|
187
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
188
|
+
return hashString(JSON.stringify(stable))
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function loadCache(location: string): Cache {
|
|
192
|
+
if (!existsSync(location)) return { files: {} }
|
|
193
|
+
try {
|
|
194
|
+
const parsed = JSON.parse(readFileSync(location, "utf8")) as Cache
|
|
195
|
+
return parsed.files ? parsed : { files: {} }
|
|
196
|
+
} catch {
|
|
197
|
+
return { files: {} }
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function saveCache(location: string, cache: Cache): void {
|
|
202
|
+
mkdirSync(dirname(location), { recursive: true })
|
|
203
|
+
writeFileSync(location, JSON.stringify(cache))
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function buildOptions(flags: ParsedFlags): CliOptions {
|
|
207
|
+
const opts: CliOptions = {}
|
|
208
|
+
if (flags.punctuationStyle) opts.punctuationStyle = flags.punctuationStyle
|
|
209
|
+
if (flags.dashStyle) opts.dashStyle = flags.dashStyle
|
|
210
|
+
if (flags.symbols === false) opts.symbols = false
|
|
211
|
+
if (flags.nbsp === false) opts.nbsp = false
|
|
212
|
+
if (flags.collapseSpaces === false) opts.collapseSpaces = false
|
|
213
|
+
if (flags.arrows === false) opts.includeArrows = false
|
|
214
|
+
if (flags.fractions) opts.fractions = true
|
|
215
|
+
if (flags.degrees) opts.degrees = true
|
|
216
|
+
if (flags.superscript) opts.superscript = true
|
|
217
|
+
if (flags.ligatures) opts.ligatures = true
|
|
218
|
+
if (flags.emphasisMarker) opts.emphasisMarker = flags.emphasisMarker
|
|
219
|
+
if (flags.strongMarker) opts.strongMarker = flags.strongMarker
|
|
220
|
+
if (flags.bulletMarker) opts.bulletMarker = flags.bulletMarker
|
|
221
|
+
if (flags.ruleMarker) opts.ruleMarker = flags.ruleMarker
|
|
222
|
+
if (flags.skipTag) opts.skipTags = flags.skipTag
|
|
223
|
+
if (flags.skipClass) opts.skipClasses = flags.skipClass
|
|
224
|
+
if (flags.fragment === false) opts.fragment = false
|
|
225
|
+
return opts
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function transformContent(input: string, type: FileType, opts: CliOptions): Promise<string> {
|
|
229
|
+
return type === "md" ? transformMarkdown(input, opts) : transformHtml(input, opts)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Preserves whether the original file ends with a trailing newline.
|
|
234
|
+
* The unified pipelines for Markdown and HTML both unconditionally append
|
|
235
|
+
* one, so without this adjustment every file lacking a trailing newline
|
|
236
|
+
* would always appear as needing a rewrite.
|
|
237
|
+
*/
|
|
238
|
+
function matchTrailingNewline(original: string, formatted: string): string {
|
|
239
|
+
const originalHas = original.endsWith("\n")
|
|
240
|
+
const formattedHas = formatted.endsWith("\n")
|
|
241
|
+
if (originalHas === formattedHas) return formatted
|
|
242
|
+
if (originalHas) return formatted + "\n"
|
|
243
|
+
return formatted.replace(/\n$/, "")
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function readStdin(stdin: AsyncIterable<string | Buffer>): Promise<string> {
|
|
247
|
+
const chunks: Buffer[] = []
|
|
248
|
+
for await (const chunk of stdin) {
|
|
249
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk)
|
|
250
|
+
}
|
|
251
|
+
return Buffer.concat(chunks).toString("utf8")
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function readPackageVersion(): Promise<string> {
|
|
255
|
+
const pkgPath = new URL("../package.json", import.meta.url)
|
|
256
|
+
/* istanbul ignore next -- the catch only fires when package.json is missing or unreadable */
|
|
257
|
+
try {
|
|
258
|
+
const raw = await readFile(pkgPath, "utf8")
|
|
259
|
+
return JSON.parse(raw).version ?? "unknown"
|
|
260
|
+
} catch {
|
|
261
|
+
return "unknown"
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Runs the CLI with the given arguments and I/O streams.
|
|
267
|
+
*
|
|
268
|
+
* @returns Exit code: 0 if no changes (or all changes written),
|
|
269
|
+
* 1 if `--check` saw a file that would change, 2 for usage errors.
|
|
270
|
+
*/
|
|
271
|
+
export async function runCli(args: string[], io: CliIO): Promise<number> {
|
|
272
|
+
const version = await readPackageVersion()
|
|
273
|
+
const program = buildProgram(version, io)
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
program.parse(args, { from: "user" })
|
|
277
|
+
} catch (err) {
|
|
278
|
+
/* istanbul ignore if -- commander only throws CommanderError from parse */
|
|
279
|
+
if (!(err instanceof CommanderError)) throw err
|
|
280
|
+
if (err.code === "commander.helpDisplayed" || err.code === "commander.version") return 0
|
|
281
|
+
return 2
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const flags = program.opts() as ParsedFlags
|
|
285
|
+
const positionals = program.args
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
return await runValidated(flags, positionals, io, program, version)
|
|
289
|
+
} catch (err) {
|
|
290
|
+
if (err instanceof UsageError) {
|
|
291
|
+
io.stderr.write(`Error: ${err.message}\n`)
|
|
292
|
+
return 2
|
|
293
|
+
}
|
|
294
|
+
throw err
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function readsStdin(positionals: string[], flags: ParsedFlags): boolean {
|
|
299
|
+
if (positionals.includes("-")) return true
|
|
300
|
+
return flags.stdinFilepath !== undefined && positionals.length === 0
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function runValidated(
|
|
304
|
+
flags: ParsedFlags,
|
|
305
|
+
positionals: string[],
|
|
306
|
+
io: CliIO,
|
|
307
|
+
program: Command,
|
|
308
|
+
version: string,
|
|
309
|
+
): Promise<number> {
|
|
310
|
+
const cwd = process.cwd()
|
|
311
|
+
const config = await loadConfig(
|
|
312
|
+
cwd,
|
|
313
|
+
typeof flags.config === "string" ? flags.config : undefined,
|
|
314
|
+
flags.config === false,
|
|
315
|
+
)
|
|
316
|
+
const opts: CliOptions = { ...config, ...buildOptions(flags) }
|
|
317
|
+
const check = flags.check ?? false
|
|
318
|
+
|
|
319
|
+
if (readsStdin(positionals, flags)) {
|
|
320
|
+
const fileArgs = positionals.filter((p) => p !== "-")
|
|
321
|
+
if (fileArgs.length > 0) {
|
|
322
|
+
throw new UsageError("'-' cannot be combined with file arguments.")
|
|
323
|
+
}
|
|
324
|
+
const type: FileType | undefined =
|
|
325
|
+
flags.type ?? (flags.stdinFilepath !== undefined ? inferFileType(flags.stdinFilepath) : undefined)
|
|
326
|
+
if (type === undefined) {
|
|
327
|
+
throw new UsageError("Reading from stdin requires --type md|html or --stdin-filepath <path>.")
|
|
328
|
+
}
|
|
329
|
+
const input = await readStdin(io.stdin)
|
|
330
|
+
const output = matchTrailingNewline(input, await transformContent(input, type, opts))
|
|
331
|
+
if (check) return input === output ? 0 : 1
|
|
332
|
+
io.stdout.write(output)
|
|
333
|
+
return 0
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (positionals.length === 0) {
|
|
337
|
+
io.stderr.write(program.helpInformation())
|
|
338
|
+
return 2
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const ig = loadIgnore(cwd, flags.ignorePath)
|
|
342
|
+
const files = await discoverFiles(positionals, cwd, ig)
|
|
343
|
+
|
|
344
|
+
const cacheLocation = flags.cache === false
|
|
345
|
+
? undefined
|
|
346
|
+
: resolve(cwd, flags.cacheLocation ?? DEFAULT_CACHE_LOCATION)
|
|
347
|
+
const cache = cacheLocation ? loadCache(cacheLocation) : undefined
|
|
348
|
+
const optionsHash = cache ? hashOptions(opts) : ""
|
|
349
|
+
|
|
350
|
+
let anyChanged = false
|
|
351
|
+
for (const path of files) {
|
|
352
|
+
const type = inferFileType(path, flags.type)
|
|
353
|
+
const original = await readFile(path, "utf8")
|
|
354
|
+
if (cache) {
|
|
355
|
+
const entry = cache.files[path]
|
|
356
|
+
if (entry?.contentHash === hashString(original)
|
|
357
|
+
&& entry.optionsHash === optionsHash
|
|
358
|
+
&& entry.version === version) continue
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const formatted = matchTrailingNewline(original, await transformContent(original, type, opts))
|
|
362
|
+
const unchanged = original === formatted
|
|
363
|
+
if (!unchanged) {
|
|
364
|
+
anyChanged = true
|
|
365
|
+
if (check) {
|
|
366
|
+
io.stderr.write(`Would reformat: ${path}\n`)
|
|
367
|
+
} else {
|
|
368
|
+
await writeFile(path, formatted)
|
|
369
|
+
io.stdout.write(`Reformatted: ${path}\n`)
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (cache && (unchanged || !check)) {
|
|
373
|
+
cache.files[path] = { contentHash: hashString(formatted), optionsHash, version }
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (cacheLocation && cache) saveCache(cacheLocation, cache)
|
|
378
|
+
return check && anyChanged ? 1 : 0
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/* istanbul ignore next -- entry-point bootstrap; logic is covered via runCli */
|
|
382
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
383
|
+
runCli(process.argv.slice(2), {
|
|
384
|
+
stdin: process.stdin,
|
|
385
|
+
stdout: process.stdout,
|
|
386
|
+
stderr: process.stderr,
|
|
387
|
+
})
|
|
388
|
+
.then((code) => process.exit(code))
|
|
389
|
+
.catch((err: Error) => {
|
|
390
|
+
process.stderr.write(`Error: ${err.message}\n`)
|
|
391
|
+
process.exit(2)
|
|
392
|
+
})
|
|
393
|
+
}
|
package/src/dashes.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { UNICODE_SYMBOLS, DEFAULT_SEPARATOR, LATIN_LETTERS, wordBoundaryStart, wordBoundaryEnd, getEscapedSeparator, cachedRegExp } from "./constants.js"
|
|
6
6
|
|
|
7
|
-
export
|
|
7
|
+
export const DASH_STYLES = ["american", "british", "none"] as const
|
|
8
|
+
export type DashStyle = (typeof DASH_STYLES)[number]
|
|
8
9
|
|
|
9
10
|
export interface DashOptions {
|
|
10
11
|
/** Boundary marker for HTML element boundaries. Default: "\uE000\uE001" */
|