polytypo 1.1.0 → 1.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.
- checksums.yaml +4 -4
- data/lib/polytypo/data/VERSION +1 -1
- data/lib/polytypo/data/fixtures/de-CH.json +9 -1
- data/lib/polytypo/data/fixtures/de-DE.json +33 -1
- data/lib/polytypo/data/fixtures/el.json +9 -1
- data/lib/polytypo/data/fixtures/en-GB.json +17 -1
- data/lib/polytypo/data/fixtures/en-US.json +65 -1
- data/lib/polytypo/data/fixtures/fi.json +9 -1
- data/lib/polytypo/data/fixtures/fr-CA.json +26 -1
- data/lib/polytypo/data/fixtures/fr.json +107 -1
- data/lib/polytypo/data/fixtures/locale-resolution.json +1 -1
- data/lib/polytypo/data/fixtures/ru.json +25 -1
- data/lib/polytypo/data/fixtures/sv.json +9 -1
- data/lib/polytypo/data/locales/registry.json +1 -1
- data/lib/polytypo/data/rules/order.json +1 -1
- data/lib/polytypo/engine/rules/apostrophe.rb +10 -1
- data/lib/polytypo/engine/rules/nbsp.rb +10 -13
- data/lib/polytypo/engine/rules/spaces.rb +8 -1
- data/lib/polytypo/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 83d0c7e30bb5525df1fcde26cfc166534b11b92e83a68490b2fdb468af059a93
|
|
4
|
+
data.tar.gz: fba93f881b2fffc39b572cabf93dd5d0a0af8073ffe3179c9594b76c17e6d052
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 736115dda633084592307b6402ce41c40183c2b71d04b62ad95512f7ad8d62c5f43a29ea4380e718f8f1ee6d7b83da5af527f24a049e947b389b4b0e56b83980
|
|
7
|
+
data.tar.gz: dd20929d2101f1151bfd004bb9cdcfb89e3a7793442864fb35e897bf9295a21fbca7224f55efc3077ade7ed9d2079487493c8f26c051b70f6b0f39cfe701709c
|
data/lib/polytypo/data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.2.0
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"spec": "1.
|
|
2
|
+
"spec": "1.2.0",
|
|
3
3
|
"locale": "de-CH",
|
|
4
4
|
"cases": [
|
|
5
5
|
{
|
|
@@ -496,6 +496,14 @@
|
|
|
496
496
|
"in": "rock 'n' roll",
|
|
497
497
|
"out": "rock ’n’ roll",
|
|
498
498
|
"note": "spec 1.1.0, quotes.md §3.2's universal medial-n elision veto: by operator decision the idiom is international, so `quotes` declines the pairing in every locale rather than only where a cited quotes.elisionIdioms entry exists, and `apostrophe` (order 50) converts each mark by its own case ladder — leading elision (apostrophe.md §3.3 case 4), then trailing elision (case 3). Same output and same two case decisions as en-us-quotes-rock-n-roll, which reaches them through the cited idiom instead. History: spec 0.5.0's general ambiguous-medial-span veto preserved both marks here as literal U+0027; before 0.5.0 `quotes` paired them as an ordinary depth-1 quotation in this locale's primary glyphs."
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
"id": "de-ch-apostrophe-elision-before-guillemet",
|
|
502
|
+
"rule": "apostrophe",
|
|
503
|
+
"mode": "text",
|
|
504
|
+
"in": "die Zeitung l'\"Express\"",
|
|
505
|
+
"out": "die Zeitung l’«Express»",
|
|
506
|
+
"note": "apostrophe.md §3.3 case 3a (spec 1.2.0): `quotes` gives de-CH's « », and the elision before « converts."
|
|
499
507
|
}
|
|
500
508
|
]
|
|
501
509
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"spec": "1.
|
|
2
|
+
"spec": "1.2.0",
|
|
3
3
|
"locale": "de-DE",
|
|
4
4
|
"cases": [
|
|
5
5
|
{
|
|
@@ -542,6 +542,38 @@
|
|
|
542
542
|
"in": "rock 'n' roll",
|
|
543
543
|
"out": "rock ’n’ roll",
|
|
544
544
|
"note": "spec 1.1.0, quotes.md §3.2's universal medial-n elision veto: by operator decision the idiom is international, so `quotes` declines the pairing in every locale rather than only where a cited quotes.elisionIdioms entry exists, and `apostrophe` (order 50) converts each mark by its own case ladder — leading elision (apostrophe.md §3.3 case 4), then trailing elision (case 3). Same output and same two case decisions as en-us-quotes-rock-n-roll, which reaches them through the cited idiom instead. History: spec 0.5.0's general ambiguous-medial-span veto preserved both marks here as literal U+0027; before 0.5.0 `quotes` paired them as an ordinary depth-1 quotation in this locale's primary glyphs."
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
"id": "de-de-spaces-dot-word-start-dotfile",
|
|
548
|
+
"rule": "spaces",
|
|
549
|
+
"mode": "text",
|
|
550
|
+
"in": "Die Datei .gitignore ist leer .",
|
|
551
|
+
"out": "Die Datei .gitignore ist leer.",
|
|
552
|
+
"note": "spaces.md §3.4 word-start clause (spec 1.2.0): the space before `.gitignore` survives because a letter follows the dot; the space before the final dot is deleted because nothing follows it."
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
"id": "de-de-apostrophe-possessive-before-closing-quote",
|
|
556
|
+
"rule": "apostrophe",
|
|
557
|
+
"mode": "text",
|
|
558
|
+
"in": "Er sagte „Hans'“ und ging.",
|
|
559
|
+
"out": "Er sagte „Hans’“ und ging.",
|
|
560
|
+
"note": "apostrophe.md §3.3 case 3a (spec 1.2.0), §6 row 17: U+201C closes a de-DE quotation but is not in CLOSEISH, so case 3 missed the possessive; case 3a reaches the same U+2019 without reading locale data."
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
"id": "de-de-apostrophe-closing-single-before-closing-double-not-elision",
|
|
564
|
+
"rule": "apostrophe",
|
|
565
|
+
"mode": "text",
|
|
566
|
+
"in": "\"Er sagte 'Hans'\" und ging.",
|
|
567
|
+
"out": "„Er sagte ‚Hans‘“ und ging.",
|
|
568
|
+
"note": "apostrophe.md §3.3 case 3a (spec 1.2.0), §7 item 7: in de-DE the closing double quote U+201C is in OPENQUOTE, so a closing single quote directly before it has exactly case 3a's shape. `quotes` (order 40) pairs both levels first, so no U+0027 reaches this rule and the nested quotation keeps its de-DE glyphs."
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
"id": "de-de-apostrophe-mixed-curly-and-straight-closing-single",
|
|
572
|
+
"rule": "apostrophe",
|
|
573
|
+
"mode": "text",
|
|
574
|
+
"in": "„Er sagte ‚Hans'“ und ging.",
|
|
575
|
+
"out": "„Er sagte ‚Hans‘“ und ging.",
|
|
576
|
+
"note": "apostrophe.md §3.3 case 3a (spec 1.2.0): the same shape with the outer pair and the opening single quote already curly. `quotes` still pairs the straight closing single quote with ‚, so case 3a never sees it."
|
|
545
577
|
}
|
|
546
578
|
]
|
|
547
579
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"spec": "1.
|
|
2
|
+
"spec": "1.2.0",
|
|
3
3
|
"locale": "el",
|
|
4
4
|
"cases": [
|
|
5
5
|
{
|
|
@@ -234,6 +234,14 @@
|
|
|
234
234
|
"ranges": true
|
|
235
235
|
},
|
|
236
236
|
"note": "spec 0.5.0: el's dash.range is \"none\" (no verified convention), so `ranges` substitutes nothing even when explicitly enabled (ranges.md §2, §3.3) — the same invariant the pre-0.5.0 `dashes`-tagged no-op fixtures for this locale already proved for the combined rule."
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"id": "el-apostrophe-elision-before-guillemet",
|
|
240
|
+
"rule": "apostrophe",
|
|
241
|
+
"mode": "text",
|
|
242
|
+
"in": "η εφημερίδα l'\"Humanité\"",
|
|
243
|
+
"out": "η εφημερίδα l’«Humanité»",
|
|
244
|
+
"note": "apostrophe.md §3.3 case 3a (spec 1.2.0): `quotes` gives el's « », and the elision before « converts."
|
|
237
245
|
}
|
|
238
246
|
]
|
|
239
247
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"spec": "1.
|
|
2
|
+
"spec": "1.2.0",
|
|
3
3
|
"locale": "en-GB",
|
|
4
4
|
"cases": [
|
|
5
5
|
{
|
|
@@ -1269,6 +1269,22 @@
|
|
|
1269
1269
|
"in": "'90s were fun,' he said",
|
|
1270
1270
|
"out": "‘90s were fun,’ he said",
|
|
1271
1271
|
"note": "quotes.md §6 row E2s: the straight-input control, confirming the corruption in E2 predates mandate 1."
|
|
1272
|
+
},
|
|
1273
|
+
{
|
|
1274
|
+
"id": "en-gb-spaces-dot-word-start-file-extensions",
|
|
1275
|
+
"rule": "spaces",
|
|
1276
|
+
"mode": "text",
|
|
1277
|
+
"in": "CAD files (.DWG, .STEP) on request",
|
|
1278
|
+
"out": "CAD files (.DWG, .STEP) on request",
|
|
1279
|
+
"note": "spaces.md §3.4 word-start clause (spec 1.2.0), §6 row 10g: `.STEP` starts a token, so the space after the comma survives. Spec 1.1.0 produced `(.DWG,.STEP)`."
|
|
1280
|
+
},
|
|
1281
|
+
{
|
|
1282
|
+
"id": "en-gb-apostrophe-elision-before-curly-quote",
|
|
1283
|
+
"rule": "apostrophe",
|
|
1284
|
+
"mode": "text",
|
|
1285
|
+
"in": "dell'“arte” moderna",
|
|
1286
|
+
"out": "dell’‘arte’ moderna",
|
|
1287
|
+
"note": "apostrophe.md §3.3 case 3a (spec 1.2.0), §6 row 16: `quotes` re-typesets the curly pair as en-GB's primary single quotes, and the elision before the opening glyph converts to U+2019."
|
|
1272
1288
|
}
|
|
1273
1289
|
]
|
|
1274
1290
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"spec": "1.
|
|
2
|
+
"spec": "1.2.0",
|
|
3
3
|
"locale": "en-US",
|
|
4
4
|
"cases": [
|
|
5
5
|
{
|
|
@@ -1802,6 +1802,70 @@
|
|
|
1802
1802
|
"ranges": true
|
|
1803
1803
|
},
|
|
1804
1804
|
"note": "ranges.md §3.2, G3 — not part of a decimal or a path. `before` is U+002E (.) for the first candidate and U+002F (/) for the second; `after` is U+002F (/) for the second. This is currently the only canonical fixture exercising G3 — previously untested by any canonical case even before the spec 0.5.0 ownership correction (dashes.test.ts's engine-level 'must not touch' list covered it, but no fixture did)."
|
|
1805
|
+
},
|
|
1806
|
+
{
|
|
1807
|
+
"id": "en-us-spaces-dot-word-start-dotnet",
|
|
1808
|
+
"rule": "spaces",
|
|
1809
|
+
"mode": "text",
|
|
1810
|
+
"in": "Use .NET, .NET Core or .NET Framework",
|
|
1811
|
+
"out": "Use .NET, .NET Core or .NET Framework",
|
|
1812
|
+
"note": "spaces.md §3.4 word-start clause (spec 1.2.0), §6 row 10f: each dot is followed by a letter, so it starts a token and the space before it is not deleted. Spec 1.1.0 produced `Use.NET,.NET Core or.NET Framework`."
|
|
1813
|
+
},
|
|
1814
|
+
{
|
|
1815
|
+
"id": "en-us-spaces-dot-word-start-decimal",
|
|
1816
|
+
"rule": "spaces",
|
|
1817
|
+
"mode": "text",
|
|
1818
|
+
"in": "the values .5 and .9 apply",
|
|
1819
|
+
"out": "the values .5 and .9 apply",
|
|
1820
|
+
"note": "spaces.md §3.4 word-start clause (spec 1.2.0), §6 row 10h: an ASCII digit after the dot counts the same as a letter."
|
|
1821
|
+
},
|
|
1822
|
+
{
|
|
1823
|
+
"id": "en-us-spaces-dot-word-start-misplaced-full-stop",
|
|
1824
|
+
"rule": "spaces",
|
|
1825
|
+
"mode": "text",
|
|
1826
|
+
"in": "the end .Next sentence",
|
|
1827
|
+
"out": "the end .Next sentence",
|
|
1828
|
+
"note": "spaces.md §3.4, §6 row 10i: the accepted cost of the word-start clause. A misplaced full stop followed directly by a letter keeps its stray space, because deleting it would glue two words that a later pass cannot separate again."
|
|
1829
|
+
},
|
|
1830
|
+
{
|
|
1831
|
+
"id": "en-us-spaces-dot-terminal-still-strips",
|
|
1832
|
+
"rule": "spaces",
|
|
1833
|
+
"mode": "text",
|
|
1834
|
+
"in": "See p. 12 . Next",
|
|
1835
|
+
"out": "See p. 12. Next",
|
|
1836
|
+
"note": "spaces.md §3.4, §6 row 10j: a dot followed by a space is terminal punctuation, so the space before it is still deleted under spec 1.2.0."
|
|
1837
|
+
},
|
|
1838
|
+
{
|
|
1839
|
+
"id": "en-us-nbsp-span-boundary-no-punctuation-data",
|
|
1840
|
+
"rule": "nbsp",
|
|
1841
|
+
"mode": "html",
|
|
1842
|
+
"in": "<strong>Note :</strong> read this",
|
|
1843
|
+
"out": "<strong>Note:</strong> read this",
|
|
1844
|
+
"note": "nbsp.md §3.3 step 2 (spec 1.2.0): accepting the marker changes nothing in a locale whose punctuation lists are empty. `spaces` deletes the space and nothing is put back, the same result as in text mode."
|
|
1845
|
+
},
|
|
1846
|
+
{
|
|
1847
|
+
"id": "en-us-apostrophe-elision-before-quote",
|
|
1848
|
+
"rule": "apostrophe",
|
|
1849
|
+
"mode": "text",
|
|
1850
|
+
"in": "the magazine l'\"Uomo\" today",
|
|
1851
|
+
"out": "the magazine l’“Uomo” today",
|
|
1852
|
+
"note": "apostrophe.md §3.3 case 3a (spec 1.2.0): `quotes` converts the straight double quotes to U+201C/U+201D, and the elision before U+201C converts to U+2019."
|
|
1853
|
+
},
|
|
1854
|
+
{
|
|
1855
|
+
"id": "en-us-apostrophe-prime-before-bracket-untouched",
|
|
1856
|
+
"rule": "apostrophe",
|
|
1857
|
+
"mode": "text",
|
|
1858
|
+
"in": "f'(x) = 2x",
|
|
1859
|
+
"out": "f'(x) = 2x",
|
|
1860
|
+
"note": "apostrophe.md §3.3 case 3a (spec 1.2.0), §6 row 18: `(` is in OPENISH but not in OPENQUOTE, so a prime on a function name is left alone."
|
|
1861
|
+
},
|
|
1862
|
+
{
|
|
1863
|
+
"id": "en-us-spaces-dot-before-span-boundary-is-terminal",
|
|
1864
|
+
"rule": "spaces",
|
|
1865
|
+
"mode": "html",
|
|
1866
|
+
"in": "Use .<b>NET</b> today",
|
|
1867
|
+
"out": "Use.<b>NET</b> today",
|
|
1868
|
+
"note": "spaces.md §3.4 word-start clause (spec 1.2.0): the code point after the dot is the −1 span boundary marker, which is neither a LETTER nor an ASCII digit (modes.md §3.3), so the dot counts as terminal and the space before it is deleted."
|
|
1805
1869
|
}
|
|
1806
1870
|
]
|
|
1807
1871
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"spec": "1.
|
|
2
|
+
"spec": "1.2.0",
|
|
3
3
|
"locale": "fi",
|
|
4
4
|
"cases": [
|
|
5
5
|
{
|
|
@@ -1309,6 +1309,14 @@
|
|
|
1309
1309
|
"in": "'90s were fun,' he said",
|
|
1310
1310
|
"out": "”90s were fun,” he said",
|
|
1311
1311
|
"note": "quotes.md §6 row E3s: the straight-input control for E3."
|
|
1312
|
+
},
|
|
1313
|
+
{
|
|
1314
|
+
"id": "fi-apostrophe-elision-before-shared-quote-glyph",
|
|
1315
|
+
"rule": "apostrophe",
|
|
1316
|
+
"mode": "text",
|
|
1317
|
+
"in": "lehti l'\"Humanité\"",
|
|
1318
|
+
"out": "lehti l’”Humanité”",
|
|
1319
|
+
"note": "apostrophe.md §3.3 cases 3 and 3a (spec 1.2.0): fi opens and closes with U+201D, which is in CLOSEISH, so case 3 already converted this before 1.2.0. Pinned so that case 3a is not read as changing a shared-glyph locale."
|
|
1312
1320
|
}
|
|
1313
1321
|
]
|
|
1314
1322
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"spec": "1.
|
|
2
|
+
"spec": "1.2.0",
|
|
3
3
|
"locale": "fr-CA",
|
|
4
4
|
"cases": [
|
|
5
5
|
{
|
|
@@ -263,6 +263,31 @@
|
|
|
263
263
|
"ranges": true
|
|
264
264
|
},
|
|
265
265
|
"note": "spec 0.5.0: fr-CA's dash.range is \"none\" (no verified convention), so `ranges` substitutes nothing even when explicitly enabled (ranges.md §2, §3.3) — the same invariant the pre-0.5.0 `dashes`-tagged no-op fixtures for this locale already proved for the combined rule."
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"id": "fr-ca-nbsp-span-boundary-strong-colon",
|
|
269
|
+
"rule": "nbsp",
|
|
270
|
+
"mode": "html",
|
|
271
|
+
"in": "<strong>Résistant au gel :</strong> il reste souple",
|
|
272
|
+
"out": "<strong>Résistant au gel :</strong> il reste souple",
|
|
273
|
+
"note": "nbsp.md §3.3 step 2 (spec 1.2.0): same as the `fr` case; `fr-CA` lists `:` in its own punctuation data."
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"id": "fr-ca-nbsp-span-boundary-markdown-strong-colon",
|
|
277
|
+
"rule": "nbsp",
|
|
278
|
+
"mode": "markdown",
|
|
279
|
+
"dialect": "commonmark",
|
|
280
|
+
"in": "**Note :** voir plus bas\n",
|
|
281
|
+
"out": "**Note :** voir plus bas\n",
|
|
282
|
+
"note": "nbsp.md §3.3 step 2 (spec 1.2.0): the colon ends the strong span; the −1 marker after it is in CLOSEISH, so N1 restores the U+00A0 that `spaces` deleted. (fr-CA lists no punctuation for N2, so `?` would take nothing back in any mode.)"
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"id": "fr-ca-apostrophe-elision-before-straight-quote",
|
|
286
|
+
"rule": "apostrophe",
|
|
287
|
+
"mode": "text",
|
|
288
|
+
"in": "l'\"idée\" est simple",
|
|
289
|
+
"out": "l’« idée » est simple",
|
|
290
|
+
"note": "apostrophe.md §3.3 case 3a (spec 1.2.0): same as `fr`."
|
|
266
291
|
}
|
|
267
292
|
]
|
|
268
293
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"spec": "1.
|
|
2
|
+
"spec": "1.2.0",
|
|
3
3
|
"locale": "fr",
|
|
4
4
|
"cases": [
|
|
5
5
|
{
|
|
@@ -598,6 +598,112 @@
|
|
|
598
598
|
"ranges": true
|
|
599
599
|
},
|
|
600
600
|
"note": "spec 0.5.0: fr's dash.range is \"none\" (no verified convention), so `ranges` substitutes nothing even when explicitly enabled (ranges.md §2, §3.3) — the same invariant the pre-0.5.0 `dashes`-tagged no-op fixtures for this locale already proved for the combined rule."
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
"id": "fr-spaces-dot-word-start-dotfile",
|
|
604
|
+
"rule": "spaces",
|
|
605
|
+
"mode": "text",
|
|
606
|
+
"in": "Voir le fichier .htaccess .",
|
|
607
|
+
"out": "Voir le fichier .htaccess.",
|
|
608
|
+
"note": "spaces.md §3.4 word-start clause (spec 1.2.0): the space before `.htaccess` survives, the space before the terminal dot is deleted. `.` is in no `nbsp` list for `fr`, so nothing is put back."
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
"id": "fr-nbsp-span-boundary-strong-colon",
|
|
612
|
+
"rule": "nbsp",
|
|
613
|
+
"mode": "html",
|
|
614
|
+
"in": "<strong>Résistant au gel :</strong> il reste souple",
|
|
615
|
+
"out": "<strong>Résistant au gel :</strong> il reste souple",
|
|
616
|
+
"note": "nbsp.md §3.3 step 2 (spec 1.2.0), §6 row 6c; modes.md §3.3: `spaces` deletes the typed space before `:`, and the −1 span boundary marker after the mark is in `nbsp`'s CLOSEISH, so N1 inserts U+00A0 at the mark's index, inside the span. Spec 1.1.0 runtimes produced `gel:</strong>`."
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
"id": "fr-nbsp-span-boundary-br-question",
|
|
620
|
+
"rule": "nbsp",
|
|
621
|
+
"mode": "html",
|
|
622
|
+
"in": "Et la réglementation ?<br>Oui",
|
|
623
|
+
"out": "Et la réglementation ?<br>Oui",
|
|
624
|
+
"note": "nbsp.md §3.3 step 2 (spec 1.2.0), §6 row 6d: `<br>` leaves no line terminator in the gap, so the marker is −1; it is in CLOSEISH and N2 restores U+202F."
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
"id": "fr-nbsp-span-boundary-link-question",
|
|
628
|
+
"rule": "nbsp",
|
|
629
|
+
"mode": "html",
|
|
630
|
+
"in": "<a href=\"/faq\">Comment ?</a> Oui",
|
|
631
|
+
"out": "<a href=\"/faq\">Comment ?</a> Oui",
|
|
632
|
+
"note": "nbsp.md §3.3 step 2 (spec 1.2.0): the mark ends the link text, the marker follows it, and N2 restores U+202F instead of leaving `Comment?`."
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
"id": "fr-nbsp-span-boundary-markdown-strong-colon",
|
|
636
|
+
"rule": "nbsp",
|
|
637
|
+
"mode": "markdown",
|
|
638
|
+
"dialect": "commonmark",
|
|
639
|
+
"in": "**Résistant au gel :** il reste souple\n",
|
|
640
|
+
"out": "**Résistant au gel :** il reste souple\n",
|
|
641
|
+
"note": "nbsp.md §3.3 step 2 (spec 1.2.0): the Markdown form of the French definition pattern; the closing `**` is a skipped region with no line terminator, so the marker is −1 and N1 restores U+00A0."
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
"id": "fr-nbsp-span-boundary-not-openish-em",
|
|
645
|
+
"rule": "nbsp",
|
|
646
|
+
"mode": "html",
|
|
647
|
+
"in": "Il dit <em>non</em> ! Oui",
|
|
648
|
+
"out": "Il dit <em>non</em> ! Oui",
|
|
649
|
+
"note": "nbsp.md §3.3 step 3 (spec 1.2.0), §6 row 6e; modes.md §3.3: the marker is not in `nbsp`'s OPENISH, so the quote-glyph guard does not decline and N2 converts the space. A port that applied the quote-glyph guard to the marker would leave a plain U+0020 here."
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
"id": "fr-nbsp-span-boundary-not-openish-link",
|
|
653
|
+
"rule": "nbsp",
|
|
654
|
+
"mode": "markdown",
|
|
655
|
+
"dialect": "commonmark",
|
|
656
|
+
"in": "Voir [ceci](http://example.org) : oui\n",
|
|
657
|
+
"out": "Voir [ceci](http://example.org) : oui\n",
|
|
658
|
+
"note": "nbsp.md §3.3 step 3 (spec 1.2.0): the second witness that the quote-glyph guard does not decline at a span boundary. The link text ends two places left of `:`, and N1 still converts the space. The marker's absence from the rest of `nbsp`'s OPENISH is pinned by ru-nbsp-span-boundary-not-openish-short-word."
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
"id": "fr-nbsp-span-boundary-mark-alone-in-span",
|
|
662
|
+
"rule": "nbsp",
|
|
663
|
+
"mode": "html",
|
|
664
|
+
"in": "mot<em>!</em>",
|
|
665
|
+
"out": "mot<em>!</em>",
|
|
666
|
+
"note": "nbsp.md §3.3 step 2; modes.md §3.4 edge-growth rule: the mark is alone in its span, so the insertion point is the span edge and the insertion is discarded, as it was before spec 1.2.0."
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
"id": "fr-nbsp-span-boundary-colon-before-digit-span",
|
|
670
|
+
"rule": "nbsp",
|
|
671
|
+
"mode": "html",
|
|
672
|
+
"in": "Il est 12:<b>30</b>",
|
|
673
|
+
"out": "Il est 12 :<b>30</b>",
|
|
674
|
+
"note": "nbsp.md §3.3 step 2 (spec 1.2.0), §6 row 6f: the stated cost of accepting the marker. Step 2 cannot see past the span boundary, so a colon that ends one span while a digit starts the next is treated as sentence punctuation."
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
"id": "fr-apostrophe-elision-before-guillemet",
|
|
678
|
+
"rule": "apostrophe",
|
|
679
|
+
"mode": "text",
|
|
680
|
+
"in": "un mélange d'« urine diluée »",
|
|
681
|
+
"out": "un mélange d’« urine diluée »",
|
|
682
|
+
"note": "apostrophe.md §3.3 case 3a (spec 1.2.0), §6 row 15: letter left, opening quotation glyph right. Spec 1.1.0 left the apostrophe straight, because no case matched a letter followed by an OPENISH-only glyph."
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
"id": "fr-apostrophe-elision-before-straight-quote",
|
|
686
|
+
"rule": "apostrophe",
|
|
687
|
+
"mode": "text",
|
|
688
|
+
"in": "l'\"idée\" est simple",
|
|
689
|
+
"out": "l’« idée » est simple",
|
|
690
|
+
"note": "apostrophe.md §3.2, §3.3 case 3a (spec 1.2.0): `quotes` (order 40) turns the straight `\"` into `«` first, so the mark reaches this rule next to an opening glyph and case 3a converts it."
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
"id": "fr-apostrophe-elision-qu-before-guillemet",
|
|
694
|
+
"rule": "apostrophe",
|
|
695
|
+
"mode": "text",
|
|
696
|
+
"in": "Il faut qu'« il vienne »",
|
|
697
|
+
"out": "Il faut qu’« il vienne »",
|
|
698
|
+
"note": "apostrophe.md §3.3 case 3a (spec 1.2.0): `qu'` before a quotation."
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
"id": "fr-nbsp-span-boundary-colon-before-url-span",
|
|
702
|
+
"rule": "nbsp",
|
|
703
|
+
"mode": "html",
|
|
704
|
+
"in": "Voir http:<a href=\"//x.org\">//x.org</a>",
|
|
705
|
+
"out": "Voir http :<a href=\"//x.org\">//x.org</a>",
|
|
706
|
+
"note": "nbsp.md §3.3 step 2 (spec 1.2.0): the second stated cost of accepting the marker. Step 2 cannot see the `/` in the next span, so the colon is treated as sentence punctuation and N1 inserts U+00A0."
|
|
601
707
|
}
|
|
602
708
|
]
|
|
603
709
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"spec": "1.
|
|
2
|
+
"spec": "1.2.0",
|
|
3
3
|
"locale": "ru",
|
|
4
4
|
"cases": [
|
|
5
5
|
{
|
|
@@ -691,6 +691,30 @@
|
|
|
691
691
|
"in": "rock ’n’ roll",
|
|
692
692
|
"out": "rock ’n’ roll",
|
|
693
693
|
"note": "quotes.md §3.2, the regression witness for spec 1.1.0's widening of the veto from straight ASCII to the whole NARROW class. This is the output of ru-quotes-rock-n-roll fed back in, so it is that case's idempotency obligation made explicit — and it is the case a naive implementation gets wrong. A port that matches U+0027 only (as spec 0.5.0's withdrawn predicate did, correctly for its own preserve-then-stop outcome) does not recognise the U+2019 marks it just produced: the pair falls through to ordinary NARROW quotation and this input becomes `rock «n» roll`, ru's primary pair at depth 1. Measured, not hypothesised. `apostrophe` emits U+2019 only in place of U+0027 (apostrophe.md §1), so once the veto matches, nothing edits these marks and the form is stable."
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
"id": "ru-spaces-dot-word-start-dotfile",
|
|
697
|
+
"rule": "spaces",
|
|
698
|
+
"mode": "text",
|
|
699
|
+
"in": "Проверьте файл .env сейчас",
|
|
700
|
+
"out": "Проверьте файл .env сейчас",
|
|
701
|
+
"note": "spaces.md §3.4 word-start clause (spec 1.2.0): a dot followed by a letter starts a token in every locale; `spaces` reads no locale data (§2)."
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
"id": "ru-apostrophe-elision-before-guillemet",
|
|
705
|
+
"rule": "apostrophe",
|
|
706
|
+
"mode": "text",
|
|
707
|
+
"in": "газета l'\"Humanité\"",
|
|
708
|
+
"out": "газета l’«Humanité»",
|
|
709
|
+
"note": "apostrophe.md §3.3 case 3a (spec 1.2.0): a French title in Russian text; `quotes` gives « », and the elision before « converts."
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
"id": "ru-nbsp-span-boundary-not-openish-short-word",
|
|
713
|
+
"rule": "nbsp",
|
|
714
|
+
"mode": "html",
|
|
715
|
+
"in": "Он живёт в <em>Москве</em>",
|
|
716
|
+
"out": "Он живёт в <em>Москве</em>",
|
|
717
|
+
"note": "nbsp.md §3.1, §7 item 12 (spec 1.2.0); modes.md §3.3: the −1 span boundary marker is not in `nbsp`'s OPENISH, so N3's following-token guard does not accept it and the space after `в` stays U+0020. This pins the membership split itself: a port that also put the marker in OPENISH would bind here."
|
|
694
718
|
}
|
|
695
719
|
]
|
|
696
720
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"spec": "1.
|
|
2
|
+
"spec": "1.2.0",
|
|
3
3
|
"locale": "sv",
|
|
4
4
|
"cases": [
|
|
5
5
|
{
|
|
@@ -1285,6 +1285,14 @@
|
|
|
1285
1285
|
"in": "'90s were fun,' he said",
|
|
1286
1286
|
"out": "”90s were fun,” he said",
|
|
1287
1287
|
"note": "quotes.md §6 row E4s: the straight-input control for E4."
|
|
1288
|
+
},
|
|
1289
|
+
{
|
|
1290
|
+
"id": "sv-apostrophe-elision-before-shared-quote-glyph",
|
|
1291
|
+
"rule": "apostrophe",
|
|
1292
|
+
"mode": "text",
|
|
1293
|
+
"in": "tidningen l'\"Humanité\"",
|
|
1294
|
+
"out": "tidningen l’”Humanité”",
|
|
1295
|
+
"note": "apostrophe.md §3.3 cases 3 and 3a (spec 1.2.0): same as fi; U+201D is in CLOSEISH and the output is unchanged from spec 1.1.0."
|
|
1288
1296
|
}
|
|
1289
1297
|
]
|
|
1290
1298
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"spec": "1.
|
|
2
|
+
"spec": "1.2.0",
|
|
3
3
|
"$comment": "Locale resolution input. Algorithm is specified in spec/rules/locale-resolution.md and is identical in every runtime — never delegate it to a platform locale-negotiation library. \"spec\" here must track spec/VERSION exactly — it is not itself the global version source; scripts/validate-spec.mjs enforces the match.",
|
|
4
4
|
"locales": ["en-US", "en-GB", "de-DE", "de-CH", "fr", "fr-CA", "ru", "fi", "sv", "el"],
|
|
5
5
|
"aliases": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"spec": "1.
|
|
2
|
+
"spec": "1.2.0",
|
|
3
3
|
"$comment": "Single source of truth for pipeline order. Rules run in ascending `order`. Disabling a rule removes it from the sequence and never reorders the rest. Rule ids are public API (see docs/ARCHITECTURE.md section 5). \"spec\" here must track spec/VERSION exactly — it is not itself the global version source; scripts/validate-spec.mjs enforces the match.",
|
|
4
4
|
"rules": [
|
|
5
5
|
{
|
|
@@ -9,7 +9,7 @@ require_relative "../registry"
|
|
|
9
9
|
module Polytypo
|
|
10
10
|
module Engine
|
|
11
11
|
module Rules
|
|
12
|
-
# spec/rules/apostrophe.md (spec
|
|
12
|
+
# spec/rules/apostrophe.md (spec 1.2.0), order 50.
|
|
13
13
|
#
|
|
14
14
|
# Converts a straight U+0027 to U+2019 where it is genuinely an apostrophe: a contraction,
|
|
15
15
|
# an elision, a possessive, or a decade elision. Runs immediately after `quotes` (order 40)
|
|
@@ -47,6 +47,10 @@ module Polytypo
|
|
|
47
47
|
0x2D, 0x2011, 0x2013, 0x2014
|
|
48
48
|
].freeze
|
|
49
49
|
|
|
50
|
+
# OPENQUOTE is apostrophe.md 3.1 OPENQUOTE (spec 1.2.0): the quotation glyphs of OPENISH,
|
|
51
|
+
# without its brackets, its dashes and Engine::MARKER. Read only by case 3a.
|
|
52
|
+
OPENQUOTE = [0xAB, 0x2018, 0x201A, 0x201B, 0x201C, 0x201E, 0x201F, 0x2039].freeze
|
|
53
|
+
|
|
50
54
|
BREAK = [0x0A, 0x0D, 0x0B, 0x0C, 0x85, 0x2028, 0x2029, Engine::LINE_MARKER].freeze
|
|
51
55
|
|
|
52
56
|
# SPACELIKE, including Engine::LINE_MARKER as a member of BREAK for every rule everywhere
|
|
@@ -81,6 +85,11 @@ module Polytypo
|
|
|
81
85
|
return true
|
|
82
86
|
end
|
|
83
87
|
|
|
88
|
+
# Case 3a (spec 1.2.0) -- elision before a quotation: `d'« urine »`, `l'“idea”`. Case 3
|
|
89
|
+
# misses it because an opening quotation glyph is not in CLOSEISH; `f'(x)` stays straight
|
|
90
|
+
# because a bracket is not in OPENQUOTE.
|
|
91
|
+
return true if UnicodeUtil.letter?(left) && OPENQUOTE.include?(right)
|
|
92
|
+
|
|
84
93
|
# Case 4 -- leading elision: `'90s`, `'tis`, `'em`, `'n'` (the leading mark). The
|
|
85
94
|
# replacement is U+2019, never U+2018 -- a leading elision is a raised comma, not an
|
|
86
95
|
# opening quotation mark, and `quotes` has already had its chance to claim the mark as a
|
|
@@ -9,7 +9,7 @@ require_relative "../../errors"
|
|
|
9
9
|
module Polytypo
|
|
10
10
|
module Engine
|
|
11
11
|
module Rules
|
|
12
|
-
# `nbsp` -- spec/rules/nbsp.md (spec
|
|
12
|
+
# `nbsp` -- spec/rules/nbsp.md (spec 1.2.0), order 70 (last).
|
|
13
13
|
#
|
|
14
14
|
# Ten sub-rules, N1 through N10, evaluated in that fixed order (3.2); each produces
|
|
15
15
|
# candidate edits keyed by the index of the space (or insertion point) it claims, and the
|
|
@@ -54,16 +54,7 @@ module Polytypo
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
# BREAK (nbsp.md 3.1), including LINE_MARKER -- a member of BREAK for every rule,
|
|
57
|
-
# everywhere (modes.md 3.2).
|
|
58
|
-
#
|
|
59
|
-
# MARKER is deliberately NOT a member here, and openish?/closeish? below do not add it
|
|
60
|
-
# either. modes.md 3.3's table says nbsp's OPENISH/CLOSEISH include the span-boundary
|
|
61
|
-
# MARKER, the same way quotes' and apostrophe's do -- but the JS reference
|
|
62
|
-
# implementation's own isBreak/isOpenish/isCloseish never test for MARKER, only
|
|
63
|
-
# LINE_MARKER via isBreak. That is a documented, preserved discrepancy (tracked, not
|
|
64
|
-
# resolved, in the roadmap), carried forward here unchanged rather than "corrected"
|
|
65
|
-
# against the table, for cross-runtime consistency with the already-shipped JS, Python
|
|
66
|
-
# and Go ports.
|
|
57
|
+
# everywhere (modes.md 3.2). MARKER is not a member of BREAK; see closeish? below.
|
|
67
58
|
def self.break?(cp)
|
|
68
59
|
cp == 0x0A || cp == 0x0D || cp == 0x0B || cp == 0x0C || cp == 0x85 ||
|
|
69
60
|
cp == 0x2028 || cp == 0x2029 || cp == LINE_MARKER
|
|
@@ -197,13 +188,19 @@ module Polytypo
|
|
|
197
188
|
end
|
|
198
189
|
|
|
199
190
|
# OPENISH / CLOSEISH (nbsp.md 3.1): the ASCII brackets plus every locale quote glyph.
|
|
200
|
-
#
|
|
191
|
+
#
|
|
192
|
+
# Since spec 1.2.0 the span boundary MARKER is a member of CLOSEISH and not of OPENISH
|
|
193
|
+
# (nbsp.md 3.1 and 7 item 12, modes.md 3.3). CLOSEISH is read only by N1/N2's right-context
|
|
194
|
+
# guard, where membership lets French `<strong>gel :</strong>` keep its no-break space
|
|
195
|
+
# after `spaces` deletes the U+0020. OPENISH stays marker-free: with the marker in it,
|
|
196
|
+
# N1/N2's quote-glyph guard would decline `<em>non</em> ! Oui`, and the left-boundary
|
|
197
|
+
# tests of N3, N7, N9, N10 would widen at span edges.
|
|
201
198
|
def self.openish?(prep, cp)
|
|
202
199
|
prep.opens.include?(cp)
|
|
203
200
|
end
|
|
204
201
|
|
|
205
202
|
def self.closeish?(prep, cp)
|
|
206
|
-
prep.closes.include?(cp)
|
|
203
|
+
cp == MARKER || prep.closes.include?(cp)
|
|
207
204
|
end
|
|
208
205
|
|
|
209
206
|
def self.mark?(prep, cp)
|
|
@@ -107,10 +107,17 @@ module Polytypo
|
|
|
107
107
|
# after any edit had been applied would break the Chicago spaced ellipsis
|
|
108
108
|
# "Hello . . .", where every dot is a lone dot at decision time and all three spaces
|
|
109
109
|
# must still strip.
|
|
110
|
+
#
|
|
111
|
+
# Spec 1.2.0's word-start clause: a single dot directly followed by a letter or an ASCII
|
|
112
|
+
# digit starts a token (".NET", ".env", ".5"), so "Use .NET" keeps its space. A span
|
|
113
|
+
# boundary marker after the dot is neither, so "a .</em>" still strips.
|
|
110
114
|
def self.lone_dot?(cp, e)
|
|
111
115
|
return true if at(cp, e) != FULL_STOP
|
|
112
116
|
|
|
113
|
-
|
|
117
|
+
after = at(cp, e + 1)
|
|
118
|
+
return false if UnicodeUtil.letter?(after) || digit_ascii?(after)
|
|
119
|
+
|
|
120
|
+
!dotlike?(after)
|
|
114
121
|
end
|
|
115
122
|
|
|
116
123
|
def self.digit_ascii?(value)
|
data/lib/polytypo/version.rb
CHANGED