punctilio 3.9.2 → 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
package/src/html.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convenience module for HTML-to-HTML typography transformations.
|
|
3
|
+
*
|
|
4
|
+
* Provides a single function that takes HTML input and returns
|
|
5
|
+
* typographically improved HTML output.
|
|
6
|
+
*
|
|
7
|
+
* Requires `unified`, `rehype-parse`, and `rehype-stringify` to be installed.
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { unified } from "unified"
|
|
13
|
+
import rehypeParse from "rehype-parse"
|
|
14
|
+
import rehypeStringify from "rehype-stringify"
|
|
15
|
+
import QuickLRU from "quick-lru"
|
|
16
|
+
|
|
17
|
+
import { rehypePunctilio, type RehypePunctilioOptions } from "./rehype.js"
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Options for the HTML transform pipeline.
|
|
21
|
+
*/
|
|
22
|
+
export interface HtmlOptions extends RehypePunctilioOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Whether to parse the input as an HTML fragment (no implicit `<html>`,
|
|
25
|
+
* `<head>`, or `<body>` wrapping). Set to `false` to parse a complete
|
|
26
|
+
* document. Default: `true`.
|
|
27
|
+
*/
|
|
28
|
+
fragment?: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createProcessor(options: HtmlOptions) {
|
|
32
|
+
const { fragment, ...punctilioOptions } = options
|
|
33
|
+
return unified()
|
|
34
|
+
.use(rehypeParse, { fragment: fragment ?? true })
|
|
35
|
+
.use(rehypePunctilio, punctilioOptions)
|
|
36
|
+
.use(rehypeStringify)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const MAX_PROCESSOR_CACHE_SIZE = 8
|
|
40
|
+
|
|
41
|
+
const processorCache = new QuickLRU<string, ReturnType<typeof createProcessor>>({
|
|
42
|
+
maxSize: MAX_PROCESSOR_CACHE_SIZE,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Clears the processor cache. Exported for test isolation only.
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
export function clearProcessorCache(): void {
|
|
50
|
+
processorCache.clear()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Transforms HTML text with typographic improvements.
|
|
55
|
+
*
|
|
56
|
+
* Parses the input as HTML, applies punctilio typography transformations,
|
|
57
|
+
* and serializes back to HTML. The unified processor pipeline is cached
|
|
58
|
+
* and reused across calls with identical options.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* import { transformHtml } from 'punctilio/html'
|
|
63
|
+
*
|
|
64
|
+
* await transformHtml('<p>"Hello" -- world</p>')
|
|
65
|
+
* // → '<p>“Hello”—world</p>'
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export async function transformHtml(
|
|
69
|
+
input: string,
|
|
70
|
+
options: HtmlOptions = {}
|
|
71
|
+
): Promise<string> {
|
|
72
|
+
// Function-valued options aren't JSON-serializable, so the cache key
|
|
73
|
+
// can't distinguish them. Bypass the cache to avoid returning a
|
|
74
|
+
// processor wired to a different shouldSkipText.
|
|
75
|
+
if (typeof options.shouldSkipText === "function") {
|
|
76
|
+
const result = await createProcessor(options).process(input)
|
|
77
|
+
return String(result)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const optionsKey = JSON.stringify(
|
|
81
|
+
Object.entries(options).filter(([, v]) => v !== undefined).sort(([a], [b]) => a.localeCompare(b))
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
let processor = processorCache.get(optionsKey)
|
|
85
|
+
if (!processor) {
|
|
86
|
+
processor = createProcessor(options)
|
|
87
|
+
processorCache.set(optionsKey, processor)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const result = await processor.process(input)
|
|
91
|
+
return String(result)
|
|
92
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
|
|
16
|
+
import { dirname } from "node:path"
|
|
17
|
+
|
|
18
|
+
import * as prettierMarkdown from "prettier/plugins/markdown"
|
|
19
|
+
import type { Parser, Plugin } from "prettier"
|
|
20
|
+
import type { Root } from "mdast"
|
|
21
|
+
import { cosmiconfig } from "cosmiconfig"
|
|
22
|
+
|
|
23
|
+
import { remarkPunctilio, type RemarkPunctilioOptions } from "./remark.js"
|
|
24
|
+
|
|
25
|
+
const upstreamMarkdown = prettierMarkdown.parsers.markdown as Parser<Root>
|
|
26
|
+
const explorer = cosmiconfig("punctilio")
|
|
27
|
+
|
|
28
|
+
async function loadPunctilioConfig(searchFrom: string): Promise<RemarkPunctilioOptions> {
|
|
29
|
+
const result = await explorer.search(searchFrom)
|
|
30
|
+
return !result || result.isEmpty ? {} : (result.config as RemarkPunctilioOptions)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const plugin: Plugin = {
|
|
34
|
+
parsers: {
|
|
35
|
+
markdown: {
|
|
36
|
+
...upstreamMarkdown,
|
|
37
|
+
async parse(text, options) {
|
|
38
|
+
const ast = await upstreamMarkdown.parse(text, options)
|
|
39
|
+
const searchFrom = typeof options.filepath === "string" ? dirname(options.filepath) : process.cwd()
|
|
40
|
+
const opts = await loadPunctilioConfig(searchFrom)
|
|
41
|
+
const transform = remarkPunctilio(opts) as (tree: Root) => void
|
|
42
|
+
transform(ast)
|
|
43
|
+
return ast
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default plugin
|
package/src/quotes.ts
CHANGED
|
@@ -25,7 +25,8 @@ const TERMINAL_PUNCTUATION_CLASS = TERMINAL_PUNCTUATION.join("")
|
|
|
25
25
|
*/
|
|
26
26
|
const MAX_NESTED_QUOTES = 4
|
|
27
27
|
|
|
28
|
-
export
|
|
28
|
+
export const PUNCTUATION_STYLES = ["american", "british", "german", "french", "none"] as const
|
|
29
|
+
export type PunctuationStyle = (typeof PUNCTUATION_STYLES)[number]
|
|
29
30
|
|
|
30
31
|
export interface QuoteOptions {
|
|
31
32
|
/** Boundary marker for HTML element boundaries. Default: "\uE000\uE001" */
|