punctilio 5.0.1 → 5.0.3
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 +2 -4
- package/dist/constants.d.ts +20 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +26 -0
- package/dist/constants.js.map +1 -1
- package/dist/dashes.d.ts +3 -6
- package/dist/dashes.d.ts.map +1 -1
- package/dist/dashes.js +54 -60
- package/dist/dashes.js.map +1 -1
- package/dist/nbsp.d.ts +8 -16
- package/dist/nbsp.d.ts.map +1 -1
- package/dist/nbsp.js +17 -31
- package/dist/nbsp.js.map +1 -1
- package/dist/prose-view.d.ts +13 -0
- package/dist/prose-view.d.ts.map +1 -1
- package/dist/prose-view.js +20 -0
- package/dist/prose-view.js.map +1 -1
- package/dist/quote-classifier.d.ts.map +1 -1
- package/dist/quote-classifier.js +23 -54
- package/dist/quote-classifier.js.map +1 -1
- package/dist/symbols.d.ts +11 -22
- package/dist/symbols.d.ts.map +1 -1
- package/dist/symbols.js +28 -43
- package/dist/symbols.js.map +1 -1
- package/dist/utils.d.ts +0 -11
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +0 -18
- package/dist/utils.js.map +1 -1
- package/package.json +3 -2
- package/src/constants.ts +32 -0
- package/src/dashes.ts +33 -71
- package/src/nbsp.ts +14 -48
- package/src/prose-view.ts +24 -0
- package/src/quote-classifier.ts +23 -60
- package/src/symbols.ts +18 -67
- package/src/utils.ts +0 -19
package/src/quote-classifier.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FOLDED_WORD_CHARS, LATIN_LETTER_RE, SPACE_CHAR_RE, TERMINAL_PUNCTUATION, UNICODE_SYMBOLS, WORD_RE } from "./constants.js"
|
|
2
2
|
import type { ProseView } from "./prose-view.js"
|
|
3
3
|
|
|
4
4
|
const {
|
|
@@ -24,16 +24,11 @@ const {
|
|
|
24
24
|
EXCLAMATION_QUESTION,
|
|
25
25
|
DOUBLE_EXCLAMATION,
|
|
26
26
|
INTERROBANG,
|
|
27
|
-
MULTIPLICATION,
|
|
28
27
|
APPROXIMATE,
|
|
29
28
|
GREATER_EQUAL,
|
|
30
29
|
LESS_EQUAL,
|
|
31
30
|
NOT_EQUAL,
|
|
32
31
|
PLUS_MINUS,
|
|
33
|
-
SUPERSCRIPT_ST,
|
|
34
|
-
SUPERSCRIPT_ND,
|
|
35
|
-
SUPERSCRIPT_RD,
|
|
36
|
-
SUPERSCRIPT_TH,
|
|
37
32
|
} = UNICODE_SYMBOLS
|
|
38
33
|
|
|
39
34
|
/**
|
|
@@ -84,7 +79,7 @@ const ROLE_BY_CHAR = new Map<string, Exclude<QuoteRole, "LITERAL">>([
|
|
|
84
79
|
*
|
|
85
80
|
* The boundary-tolerance behavior of the boundary items reproduces the
|
|
86
81
|
* element-boundary semantics of the pre-v5 sentinel-marked pipeline (pinned by
|
|
87
|
-
* the
|
|
82
|
+
* the HTML regression corpus and the migration's differential fuzz): boundary-transparent
|
|
88
83
|
* rules skip them, and rules that treat a boundary as an ordinary non-space
|
|
89
84
|
* character do so.
|
|
90
85
|
*/
|
|
@@ -102,8 +97,6 @@ interface Item {
|
|
|
102
97
|
anchorBind: "left" | "right"
|
|
103
98
|
}
|
|
104
99
|
|
|
105
|
-
const LETTER_RE = new RegExp(`[${LATIN_LETTERS}]`)
|
|
106
|
-
const WORD_RE = /\w/
|
|
107
100
|
const SPACE_RE = /\s/
|
|
108
101
|
const DIGIT_RE = /\d/
|
|
109
102
|
|
|
@@ -115,9 +108,8 @@ const QUOTE_CANDIDATE_RE = new RegExp(
|
|
|
115
108
|
|
|
116
109
|
const TERMINAL_SET = new Set<string>(TERMINAL_PUNCTUATION)
|
|
117
110
|
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
const CLOSING_SET = new Set<string>([RIGHT_SINGLE_QUOTE, RIGHT_DOUBLE_QUOTE])
|
|
111
|
+
const CLOSING_CHARS = [RIGHT_SINGLE_QUOTE, RIGHT_DOUBLE_QUOTE] as const
|
|
112
|
+
const CLOSING_SET = new Set<string>(CLOSING_CHARS)
|
|
121
113
|
|
|
122
114
|
/**
|
|
123
115
|
* Placement alphabet for renders that collapse apostrophes into U+2019: a
|
|
@@ -126,7 +118,7 @@ const CLOSING_SET = new Set<string>([RIGHT_SINGLE_QUOTE, RIGHT_DOUBLE_QUOTE])
|
|
|
126
118
|
* APOSTROPHE item as part of a closing run for the transform to be a fixed
|
|
127
119
|
* point.
|
|
128
120
|
*/
|
|
129
|
-
const CLOSING_OR_APOSTROPHE_SET = new Set<string>([
|
|
121
|
+
const CLOSING_OR_APOSTROPHE_SET = new Set<string>([...CLOSING_CHARS, MODIFIER_LETTER_APOSTROPHE])
|
|
130
122
|
|
|
131
123
|
/** Chars from `'` ${LSQ} ${RSQ} ${MLA}; \w handled separately. */
|
|
132
124
|
const SINGLE_QUOTE_FAMILY = new Set<string>(["'", LEFT_SINGLE_QUOTE, RIGHT_SINGLE_QUOTE, MODIFIER_LETTER_APOSTROPHE])
|
|
@@ -162,12 +154,23 @@ const DOUBLE_EMPTY_AFTER_SET = new Set<string>([")", "]", "}", ".", "!", "?", ",
|
|
|
162
154
|
/** Ending-context chars after a closing double quote (plus any \s char). */
|
|
163
155
|
const DOUBLE_CLOSE_AFTER_SET = new Set<string>(["/", ")", ".", ",", ";", EM_DASH, ":", "-", "}", "!", "?", "s", ELLIPSIS, ...FOLDED_TERMINALS])
|
|
164
156
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
157
|
+
/** Builds an `(items, index)` predicate: in range, non-boundary, and `test(ch)`. */
|
|
158
|
+
function makeItemTester(test: (ch: string) => boolean): (items: Item[], index: number) => boolean {
|
|
159
|
+
return (items, index) => {
|
|
160
|
+
if (index < 0 || index >= items.length) return false
|
|
161
|
+
const item = items[index]
|
|
162
|
+
return !item.boundary && test(item.ch)
|
|
163
|
+
}
|
|
169
164
|
}
|
|
170
165
|
|
|
166
|
+
const isLetterItem = makeItemTester((ch) => LATIN_LETTER_RE.test(ch))
|
|
167
|
+
const isDigitItem = makeItemTester((ch) => DIGIT_RE.test(ch))
|
|
168
|
+
const isWordItem = makeItemTester((ch) => WORD_RE.test(ch) || FOLDED_WORD_CHARS.has(ch))
|
|
169
|
+
const isLetterOrDigitItem = makeItemTester((ch) => LATIN_LETTER_RE.test(ch) || DIGIT_RE.test(ch) || FOLDED_WORD_CHARS.has(ch))
|
|
170
|
+
/** Letter test that treats folded word glyphs as the letters they fold from
|
|
171
|
+
* (`X` → `×`, `st` → `ˢᵗ`); see {@link FOLDED_WORD_CHARS}. */
|
|
172
|
+
const isLetterOrFoldedItem = makeItemTester((ch) => LATIN_LETTER_RE.test(ch) || FOLDED_WORD_CHARS.has(ch))
|
|
173
|
+
|
|
171
174
|
function isCharItem(items: Item[], index: number, ch: string): boolean {
|
|
172
175
|
if (index < 0 || index >= items.length) return false
|
|
173
176
|
const item = items[index]
|
|
@@ -339,16 +342,6 @@ function singleCloserBefore(items: Item[], index: number): boolean {
|
|
|
339
342
|
return !SPACE_RE.test(prev.ch) && prev.ch !== LEFT_DOUBLE_QUOTE && prev.ch !== "'" && prev.ch !== '"'
|
|
340
343
|
}
|
|
341
344
|
|
|
342
|
-
/**
|
|
343
|
-
* Letter test that treats folded word glyphs as the letters they fold from
|
|
344
|
-
* (`X` → `×`, `st` → `ˢᵗ`); see {@link FOLDED_WORD_CHARS}.
|
|
345
|
-
*/
|
|
346
|
-
function isLetterOrFoldedItem(items: Item[], index: number): boolean {
|
|
347
|
-
if (index < 0 || index >= items.length) return false
|
|
348
|
-
const item = items[index]
|
|
349
|
-
return !item.boundary && (LETTER_RE.test(item.ch) || FOLDED_WORD_CHARS.has(item.ch))
|
|
350
|
-
}
|
|
351
|
-
|
|
352
345
|
/** Letter directly before (no boundary) and letter after (one boundary transparent). */
|
|
353
346
|
function isContractionContext(items: Item[], index: number): boolean {
|
|
354
347
|
return isLetterOrFoldedItem(items, index - 1) && isLetterOrFoldedItem(items, nextIndex(items, index, 1))
|
|
@@ -404,22 +397,6 @@ function classifyNAbbreviation(items: Item[]): void {
|
|
|
404
397
|
}
|
|
405
398
|
}
|
|
406
399
|
|
|
407
|
-
/**
|
|
408
|
-
* Glyphs the symbol passes fold word characters into (multiplication `x` →
|
|
409
|
-
* `×`, ordinal suffixes → superscripts). Word-gated rules must accept them,
|
|
410
|
-
* or a gate decided on `6x'` flips once a re-run sees `6×'`.
|
|
411
|
-
*/
|
|
412
|
-
const FOLDED_WORD_CHARS = new Set<string>([
|
|
413
|
-
MULTIPLICATION,
|
|
414
|
-
...SUPERSCRIPT_ST, ...SUPERSCRIPT_ND, ...SUPERSCRIPT_RD, ...SUPERSCRIPT_TH,
|
|
415
|
-
])
|
|
416
|
-
|
|
417
|
-
function isWordItem(items: Item[], index: number): boolean {
|
|
418
|
-
if (index < 0 || index >= items.length) return false
|
|
419
|
-
const item = items[index]
|
|
420
|
-
return !item.boundary && (WORD_RE.test(item.ch) || FOLDED_WORD_CHARS.has(item.ch))
|
|
421
|
-
}
|
|
422
|
-
|
|
423
400
|
/**
|
|
424
401
|
* Possessive (`dog's`) → APOSTROPHE; then closing single (`'` in ending
|
|
425
402
|
* context) → CLOSE_SINGLE. Returns true when any item changed.
|
|
@@ -471,18 +448,6 @@ function isDecadeElision(items: Item[], index: number): boolean {
|
|
|
471
448
|
return !isLetterOrDigitItem(items, after)
|
|
472
449
|
}
|
|
473
450
|
|
|
474
|
-
function isDigitItem(items: Item[], index: number): boolean {
|
|
475
|
-
if (index < 0 || index >= items.length) return false
|
|
476
|
-
const item = items[index]
|
|
477
|
-
return !item.boundary && DIGIT_RE.test(item.ch)
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
function isLetterOrDigitItem(items: Item[], index: number): boolean {
|
|
481
|
-
if (index < 0 || index >= items.length) return false
|
|
482
|
-
const item = items[index]
|
|
483
|
-
return !item.boundary && (LETTER_RE.test(item.ch) || DIGIT_RE.test(item.ch) || FOLDED_WORD_CHARS.has(item.ch))
|
|
484
|
-
}
|
|
485
|
-
|
|
486
451
|
/** `nʼ ` directly after the quote — a quote leading into an already-classified 'n'. */
|
|
487
452
|
function isNAbbreviationAhead(items: Item[], index: number): boolean {
|
|
488
453
|
return isCharItem(items, index + 1, "n")
|
|
@@ -664,8 +629,6 @@ function doubleOpenerPrefixOk(items: Item[], index: number): boolean {
|
|
|
664
629
|
return SPACE_RE.test(prev.ch) || DOUBLE_OPEN_BEFORE_SET.has(prev.ch)
|
|
665
630
|
}
|
|
666
631
|
|
|
667
|
-
const ELLIPSIS_GAP_SPACE_RE = new RegExp(`[${SPACE_CHARS}]`)
|
|
668
|
-
|
|
669
632
|
/**
|
|
670
633
|
* Index of the dot reachable across one inter-dot gap from the dot at
|
|
671
634
|
* `dotIndex` (the ellipsis pass's gap rule: the gap is nothing, one space
|
|
@@ -680,7 +643,7 @@ function nextEllipsisDotItem(items: Item[], dotIndex: number): number {
|
|
|
680
643
|
}
|
|
681
644
|
if (j >= items.length) return -1
|
|
682
645
|
if (items[j].ch === ".") return j
|
|
683
|
-
if (
|
|
646
|
+
if (SPACE_CHAR_RE.test(items[j].ch) && j + 1 < items.length && !items[j + 1].boundary && items[j + 1].ch === ".") {
|
|
684
647
|
return j + 1
|
|
685
648
|
}
|
|
686
649
|
return -1
|
|
@@ -1027,7 +990,7 @@ function prevEllipsisDotItem(order: Item[], index: number): number {
|
|
|
1027
990
|
}
|
|
1028
991
|
if (j < 0) return -1
|
|
1029
992
|
if (order[j].ch === ".") return j
|
|
1030
|
-
if (
|
|
993
|
+
if (SPACE_CHAR_RE.test(order[j].ch) && isCharItem(order, j - 1, ".")) return j - 1
|
|
1031
994
|
return -1
|
|
1032
995
|
}
|
|
1033
996
|
|
|
@@ -1208,7 +1171,7 @@ function movePunctuationOutside(order: Item[], punctChar: string, closingSet: Re
|
|
|
1208
1171
|
// collapse pass merges tabs and NBSPs into the closer's NNBSP padding
|
|
1209
1172
|
// the same way.
|
|
1210
1173
|
if (spaceBeforePaddedCloser && k < order.length && !order[k].boundary
|
|
1211
|
-
&& order[k].ch.length === 1 &&
|
|
1174
|
+
&& order[k].ch.length === 1 && SPACE_CHAR_RE.test(order[k].ch)
|
|
1212
1175
|
&& isCharItem(order, k + 1, RIGHT_DOUBLE_QUOTE)) {
|
|
1213
1176
|
k++
|
|
1214
1177
|
}
|
package/src/symbols.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { cachedRegExp, LATIN_LETTERS,
|
|
2
|
-
import { boundaryCountAt, overInput, type ProseView, replaceAllInView } from "./prose-view.js"
|
|
1
|
+
import { cachedRegExp, LATIN_LETTER_RE, LATIN_LETTERS, MAX_BOUNDARY_SEPARATORS, SPACE_CHAR_RE, UNICODE_SYMBOLS, WORD_RE } from "./constants.js"
|
|
2
|
+
import { boundaryCountAt, exceedsSingleBoundary, makeProsePass, overInput, type ProseView, replaceAllInView } from "./prose-view.js"
|
|
3
3
|
import { convertPrimeMarks } from "./quote-classifier.js"
|
|
4
4
|
|
|
5
5
|
export interface SymbolOptions {
|
|
@@ -36,19 +36,10 @@ const {
|
|
|
36
36
|
|
|
37
37
|
// The boundary-tolerance positions throughout this module reproduce the
|
|
38
38
|
// element-boundary semantics of the pre-v5 sentinel-marked pipeline; they are
|
|
39
|
-
// pinned by the
|
|
40
|
-
|
|
41
|
-
const LATIN_LETTER_RE = new RegExp(`[${LATIN_LETTERS}]`)
|
|
42
|
-
const WORD_RE = /\w/
|
|
39
|
+
// pinned by the HTML regression corpus and the migration's differential fuzz.
|
|
43
40
|
|
|
44
41
|
/** Convert "..." or ". . ." to "…". */
|
|
45
|
-
export
|
|
46
|
-
export function ellipsis(input: ProseView): void
|
|
47
|
-
export function ellipsis(input: string | ProseView): string | void {
|
|
48
|
-
return overInput(input, ellipsisOverView)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const SPACE_CHAR_RE = new RegExp(`[${SPACE_CHARS}]`)
|
|
42
|
+
export const ellipsis = makeProsePass(ellipsisOverView)
|
|
52
43
|
|
|
53
44
|
function ellipsisOverView(view: ProseView): void {
|
|
54
45
|
ellipsisFoldDots(view)
|
|
@@ -109,11 +100,7 @@ function ellipsisFoldDots(view: ProseView): void {
|
|
|
109
100
|
|
|
110
101
|
|
|
111
102
|
/** Convert "5x5" to "5×5". Skips hex (0x5F). */
|
|
112
|
-
export
|
|
113
|
-
export function multiplication(input: ProseView): void
|
|
114
|
-
export function multiplication(input: string | ProseView): string | void {
|
|
115
|
-
return overInput(input, multiplicationOverView)
|
|
116
|
-
}
|
|
103
|
+
export const multiplication = makeProsePass(multiplicationOverView)
|
|
117
104
|
|
|
118
105
|
// After a digit run, either a prime mark (10′) or a length/size unit may
|
|
119
106
|
// attach before the multiplication operator. Allowing both lets dimensions
|
|
@@ -257,7 +244,7 @@ function leftSuffixUnsplit(view: ProseView, text: string, digitsEnd: number, slo
|
|
|
257
244
|
// A prime or unit suffix begins after one optional space-or-boundary slot;
|
|
258
245
|
// a second boundary in that slot (before the suffix) leaves `\d+suffix`
|
|
259
246
|
// matching only the digits, detaching the operator.
|
|
260
|
-
if (slotStart > digitsEnd &&
|
|
247
|
+
if (slotStart > digitsEnd && exceedsSingleBoundary(view, digitsEnd)) return false
|
|
261
248
|
for (let i = digitsEnd + 1; i < slotStart; i++) {
|
|
262
249
|
if (LATIN_LETTER_RE.test(text[i - 1]) && LATIN_LETTER_RE.test(text[i]) && view.hasBoundary(i)) return false
|
|
263
250
|
}
|
|
@@ -371,7 +358,7 @@ function multiplicationOverView(view: ProseView): void {
|
|
|
371
358
|
const operatorOffset = match.index + num.length
|
|
372
359
|
trailingScan = match.index + match[0].length
|
|
373
360
|
// One optional boundary may sit between the digits and the operator.
|
|
374
|
-
if (
|
|
361
|
+
if (exceedsSingleBoundary(view, operatorOffset)) continue
|
|
375
362
|
// Leading guard and hex skip, on the boundary-free digit run ending at the
|
|
376
363
|
// operator (a boundary inside the digits truncates the operand).
|
|
377
364
|
const runStart = leftDigitRunStart(view, trailingText, operatorOffset, 0)
|
|
@@ -383,7 +370,7 @@ function multiplicationOverView(view: ProseView): void {
|
|
|
383
370
|
if (op === "*") continue
|
|
384
371
|
const afterOp = operatorOffset + 1
|
|
385
372
|
const followChar = trailingText[afterOp]
|
|
386
|
-
if (boundaryCountAt(view, afterOp) <=
|
|
373
|
+
if (boundaryCountAt(view, afterOp) <= MAX_BOUNDARY_SEPARATORS && followChar !== undefined && WORD_RE.test(followChar)) continue
|
|
387
374
|
view.replace(operatorOffset, afterOp, MULTIPLICATION)
|
|
388
375
|
}
|
|
389
376
|
view.commit()
|
|
@@ -403,11 +390,7 @@ const MATH_SYMBOL_MAP: MathSymbolRule[] = [
|
|
|
403
390
|
]
|
|
404
391
|
|
|
405
392
|
/** Convert !=, <=, >=, +/-, ~= to Unicode equivalents. */
|
|
406
|
-
export
|
|
407
|
-
export function mathSymbols(input: ProseView): void
|
|
408
|
-
export function mathSymbols(input: string | ProseView): string | void {
|
|
409
|
-
return overInput(input, mathSymbolsOverView)
|
|
410
|
-
}
|
|
393
|
+
export const mathSymbols = makeProsePass(mathSymbolsOverView)
|
|
411
394
|
|
|
412
395
|
function mathSymbolsOverView(view: ProseView): void {
|
|
413
396
|
for (const [left, right, forbiddenFollow, replacement] of MATH_SYMBOL_MAP) {
|
|
@@ -539,11 +522,7 @@ const LEGAL_REGISTERED_RE = "\\(r\\)"
|
|
|
539
522
|
const LEGAL_TRADEMARK_RE = "\\(tm\\)"
|
|
540
523
|
|
|
541
524
|
/** Convert (c), (r), (tm) to ©, ®, ™. */
|
|
542
|
-
export
|
|
543
|
-
export function legalSymbols(input: ProseView): void
|
|
544
|
-
export function legalSymbols(input: string | ProseView): string | void {
|
|
545
|
-
return overInput(input, legalSymbolsOverView)
|
|
546
|
-
}
|
|
525
|
+
export const legalSymbols = makeProsePass(legalSymbolsOverView)
|
|
547
526
|
|
|
548
527
|
function legalSymbolsOverView(view: ProseView): void {
|
|
549
528
|
// (c) → © only with positive copyright evidence (year or "copyright"
|
|
@@ -645,11 +624,7 @@ function arrowRightContextOk(view: ProseView, text: string, end: number): boolea
|
|
|
645
624
|
}
|
|
646
625
|
|
|
647
626
|
/** Convert -> and <-> to arrows. */
|
|
648
|
-
export
|
|
649
|
-
export function arrows(input: ProseView): void
|
|
650
|
-
export function arrows(input: string | ProseView): string | void {
|
|
651
|
-
return overInput(input, arrowsOverView)
|
|
652
|
-
}
|
|
627
|
+
export const arrows = makeProsePass(arrowsOverView)
|
|
653
628
|
|
|
654
629
|
function arrowsOverView(view: ProseView): void {
|
|
655
630
|
// Each arrow shape is matched by a left-to-right scan so a boundary that
|
|
@@ -669,11 +644,7 @@ function arrowsOverView(view: ProseView): void {
|
|
|
669
644
|
}
|
|
670
645
|
}
|
|
671
646
|
|
|
672
|
-
export
|
|
673
|
-
export function degrees(input: ProseView): void
|
|
674
|
-
export function degrees(input: string | ProseView): string | void {
|
|
675
|
-
return overInput(input, degreesOverView)
|
|
676
|
-
}
|
|
647
|
+
export const degrees = makeProsePass(degreesOverView)
|
|
677
648
|
|
|
678
649
|
function degreesOverView(view: ProseView): void {
|
|
679
650
|
// Temperature with optional space before C or F (uppercase only). One boundary
|
|
@@ -720,7 +691,7 @@ function degreeUnitFollowOk(text: string, view: ProseView, unitEnd: number): boo
|
|
|
720
691
|
// also removes the `\b`. Consecutive boundaries pile at the same clean offset,
|
|
721
692
|
// so count them there.
|
|
722
693
|
const followChar = text[unitEnd]
|
|
723
|
-
if (boundaryCountAt(view, unitEnd) <=
|
|
694
|
+
if (boundaryCountAt(view, unitEnd) <= MAX_BOUNDARY_SEPARATORS && followChar !== undefined && WORD_RE.test(followChar)) return false
|
|
724
695
|
return true
|
|
725
696
|
}
|
|
726
697
|
|
|
@@ -742,11 +713,7 @@ function digitSuffixBoundaryOk(match: RegExpExecArray, view: ProseView): boolean
|
|
|
742
713
|
}
|
|
743
714
|
|
|
744
715
|
/** Convert 5'10" to 5′10″ (prime marks). Call before smart quotes. */
|
|
745
|
-
export
|
|
746
|
-
export function primeMarks(input: ProseView): void
|
|
747
|
-
export function primeMarks(input: string | ProseView): string | void {
|
|
748
|
-
return overInput(input, convertPrimeMarks)
|
|
749
|
-
}
|
|
716
|
+
export const primeMarks = makeProsePass(convertPrimeMarks)
|
|
750
717
|
|
|
751
718
|
type FractionRule = [string, string, string]
|
|
752
719
|
|
|
@@ -771,11 +738,7 @@ const FRACTION_TUPLES: FractionRule[] = [
|
|
|
771
738
|
const FRACTION_MAP = Object.fromEntries(FRACTION_TUPLES.map(([n, d, u]) => [`${n}/${d}`, u]))
|
|
772
739
|
|
|
773
740
|
/** Convert 1/2, 1/4, etc. to ½, ¼, etc. Single-pass using alternation. */
|
|
774
|
-
export
|
|
775
|
-
export function fractions(input: ProseView): void
|
|
776
|
-
export function fractions(input: string | ProseView): string | void {
|
|
777
|
-
return overInput(input, fractionsOverView)
|
|
778
|
-
}
|
|
741
|
+
export const fractions = makeProsePass(fractionsOverView)
|
|
779
742
|
|
|
780
743
|
function fractionsOverView(view: ProseView): void {
|
|
781
744
|
// Build alternation of exact valid pairs: `1/4|1/2|...`. Only exact pairs
|
|
@@ -852,11 +815,7 @@ const ORDINAL_MAP: Record<string, string> = {
|
|
|
852
815
|
}
|
|
853
816
|
|
|
854
817
|
/** Convert 1st, 2nd, 3rd, 4th to superscript ordinals. */
|
|
855
|
-
export
|
|
856
|
-
export function superscriptOrdinal(input: ProseView): void
|
|
857
|
-
export function superscriptOrdinal(input: string | ProseView): string | void {
|
|
858
|
-
return overInput(input, superscriptOrdinalOverView)
|
|
859
|
-
}
|
|
818
|
+
export const superscriptOrdinal = makeProsePass(superscriptOrdinalOverView)
|
|
860
819
|
|
|
861
820
|
function superscriptOrdinalOverView(view: ProseView): void {
|
|
862
821
|
// Match number + ordinal suffix at word boundary, case-insensitively. One
|
|
@@ -876,11 +835,7 @@ function superscriptOrdinalOverView(view: ProseView): void {
|
|
|
876
835
|
}
|
|
877
836
|
|
|
878
837
|
// Preserves highest-priority space type (NBSP > NNBSP > regular) and leading indentation.
|
|
879
|
-
export
|
|
880
|
-
export function collapseSpaces(input: ProseView): void
|
|
881
|
-
export function collapseSpaces(input: string | ProseView): string | void {
|
|
882
|
-
return overInput(input, collapseSpacesOverView)
|
|
883
|
-
}
|
|
838
|
+
export const collapseSpaces = makeProsePass(collapseSpacesOverView)
|
|
884
839
|
|
|
885
840
|
/**
|
|
886
841
|
* Collapses each run of two or more space characters to its highest-priority
|
|
@@ -922,11 +877,7 @@ const PUNCTUATION_LIGATURE_MAP: LigatureRule[] = [
|
|
|
922
877
|
]
|
|
923
878
|
|
|
924
879
|
/** Convert ?? to ⁇, ?! to ⁈, !? to ⁉. Disabled by default (poor font support). */
|
|
925
|
-
export
|
|
926
|
-
export function punctuationLigatures(input: ProseView): void
|
|
927
|
-
export function punctuationLigatures(input: string | ProseView): string | void {
|
|
928
|
-
return overInput(input, punctuationLigaturesOverView)
|
|
929
|
-
}
|
|
880
|
+
export const punctuationLigatures = makeProsePass(punctuationLigaturesOverView)
|
|
930
881
|
|
|
931
882
|
function punctuationLigaturesOverView(view: ProseView): void {
|
|
932
883
|
for (const [first, repeated, replacement] of PUNCTUATION_LIGATURE_MAP) {
|
package/src/utils.ts
CHANGED
|
@@ -72,22 +72,3 @@ export function stableStringify(obj: object): string {
|
|
|
72
72
|
export function namedGroups<G>(args: unknown[]): G {
|
|
73
73
|
return args[args.length - 1] as G
|
|
74
74
|
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Extracts the match offset and input string from a `.replace()` callback's
|
|
78
|
-
* arguments, independent of capture-group count. The trailing arguments are
|
|
79
|
-
* `..., offset, input[, namedGroups]`: the input is the last string (the
|
|
80
|
-
* named-groups object, when present, follows it) and the offset is the
|
|
81
|
-
* number immediately before the input. @internal
|
|
82
|
-
*/
|
|
83
|
-
export function replaceCallbackContext(args: unknown[]): { offset: number; input: string } {
|
|
84
|
-
let inputIndex = args.length - 1
|
|
85
|
-
while (inputIndex >= 0 && typeof args[inputIndex] !== "string") {
|
|
86
|
-
inputIndex--
|
|
87
|
-
}
|
|
88
|
-
const offset = args[inputIndex - 1]
|
|
89
|
-
if (inputIndex < 1 || typeof offset !== "number") {
|
|
90
|
-
throw new Error("Could not locate offset and input in replace-callback arguments.")
|
|
91
|
-
}
|
|
92
|
-
return { offset, input: args[inputIndex] as string }
|
|
93
|
-
}
|