polytypo 1.0.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +109 -0
- data/lib/polytypo/data/README.md +20 -0
- data/lib/polytypo/data/UNICODE +1 -0
- data/lib/polytypo/data/VERSION +1 -0
- data/lib/polytypo/data/fixtures/de-CH.json +501 -0
- data/lib/polytypo/data/fixtures/de-DE.json +547 -0
- data/lib/polytypo/data/fixtures/el.json +239 -0
- data/lib/polytypo/data/fixtures/en-GB.json +1274 -0
- data/lib/polytypo/data/fixtures/en-US.json +1807 -0
- data/lib/polytypo/data/fixtures/fi.json +1306 -0
- data/lib/polytypo/data/fixtures/fr-CA.json +268 -0
- data/lib/polytypo/data/fixtures/fr.json +603 -0
- data/lib/polytypo/data/fixtures/locale-resolution.json +209 -0
- data/lib/polytypo/data/fixtures/ru.json +688 -0
- data/lib/polytypo/data/fixtures/sv.json +1290 -0
- data/lib/polytypo/data/locales/de-CH.json +77 -0
- data/lib/polytypo/data/locales/de-DE.json +76 -0
- data/lib/polytypo/data/locales/el.json +90 -0
- data/lib/polytypo/data/locales/en-GB.json +115 -0
- data/lib/polytypo/data/locales/en-US.json +133 -0
- data/lib/polytypo/data/locales/fi.json +136 -0
- data/lib/polytypo/data/locales/fr-CA.json +78 -0
- data/lib/polytypo/data/locales/fr.json +84 -0
- data/lib/polytypo/data/locales/registry.json +9 -0
- data/lib/polytypo/data/locales/ru.json +112 -0
- data/lib/polytypo/data/locales/sv.json +124 -0
- data/lib/polytypo/data/rules/dashes.md +1238 -0
- data/lib/polytypo/data/rules/order.json +78 -0
- data/lib/polytypo/data/schema/fixtures.schema.json +79 -0
- data/lib/polytypo/data/schema/locale.schema.json +235 -0
- data/lib/polytypo/data/schema/registry.schema.json +29 -0
- data/lib/polytypo/data/schema/resolution.schema.json +50 -0
- data/lib/polytypo/engine/codepoints.rb +24 -0
- data/lib/polytypo/engine/edits.rb +64 -0
- data/lib/polytypo/engine/locale.rb +138 -0
- data/lib/polytypo/engine/pipeline.rb +61 -0
- data/lib/polytypo/engine/registry.rb +47 -0
- data/lib/polytypo/engine/rules/apostrophe.rb +127 -0
- data/lib/polytypo/engine/rules/dash_shared.rb +342 -0
- data/lib/polytypo/engine/rules/dashes.rb +125 -0
- data/lib/polytypo/engine/rules/ellipsis.rb +100 -0
- data/lib/polytypo/engine/rules/hyphen.rb +207 -0
- data/lib/polytypo/engine/rules/nbsp.rb +616 -0
- data/lib/polytypo/engine/rules/quote_ambiguity.rb +241 -0
- data/lib/polytypo/engine/rules/quotes.rb +420 -0
- data/lib/polytypo/engine/rules/ranges.rb +124 -0
- data/lib/polytypo/engine/rules/spaces.rb +232 -0
- data/lib/polytypo/engine/rules/symbols.rb +291 -0
- data/lib/polytypo/engine/rules.rb +19 -0
- data/lib/polytypo/engine/sentinels.rb +23 -0
- data/lib/polytypo/engine/unicode_util.rb +390 -0
- data/lib/polytypo/errors.rb +24 -0
- data/lib/polytypo/modes/html.rb +233 -0
- data/lib/polytypo/modes/markdown.rb +187 -0
- data/lib/polytypo/modes/parse_error.rb +19 -0
- data/lib/polytypo/modes/runner.rb +57 -0
- data/lib/polytypo/modes/spans.rb +132 -0
- data/lib/polytypo/version.rb +5 -0
- data/lib/polytypo.rb +91 -0
- data/polytypo.gemspec +37 -0
- metadata +122 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
{
|
|
2
|
+
"spec": "1.0.0",
|
|
3
|
+
"locale": "fr-CA",
|
|
4
|
+
"cases": [
|
|
5
|
+
{
|
|
6
|
+
"id": "fr-ca-spaces-collapse-run",
|
|
7
|
+
"rule": "spaces",
|
|
8
|
+
"mode": "text",
|
|
9
|
+
"in": "Bonjour le monde.",
|
|
10
|
+
"out": "Bonjour le monde.",
|
|
11
|
+
"note": "spaces.md §6 case 1: locale-independent, identical to fr."
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"id": "fr-ca-spaces-brackets",
|
|
15
|
+
"rule": "spaces",
|
|
16
|
+
"mode": "text",
|
|
17
|
+
"in": "a ( b , c ) d",
|
|
18
|
+
"out": "a (b, c) d"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "fr-ca-ellipsis-three-dots",
|
|
22
|
+
"rule": "ellipsis",
|
|
23
|
+
"mode": "text",
|
|
24
|
+
"in": "Attendez... quoi?",
|
|
25
|
+
"out": "Attendez… quoi?",
|
|
26
|
+
"note": "Unlike fr, the space before ? is not reinserted: narrowBeforePunctuation is empty for fr-CA."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "fr-ca-ellipsis-existing",
|
|
30
|
+
"rule": "ellipsis",
|
|
31
|
+
"mode": "text",
|
|
32
|
+
"in": "Il est parti…",
|
|
33
|
+
"out": "Il est parti…"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "fr-ca-dashes-parenthetical-spaced-input",
|
|
37
|
+
"rule": "dashes",
|
|
38
|
+
"mode": "text",
|
|
39
|
+
"in": "Le plan - il existe - échoue.",
|
|
40
|
+
"out": "Le plan — il existe — échoue.",
|
|
41
|
+
"note": "Identical to fr: no Quebec-specific dash source found."
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "fr-ca-dashes-authored-en-dash-declined",
|
|
45
|
+
"rule": "dashes",
|
|
46
|
+
"mode": "text",
|
|
47
|
+
"in": "Le plan–peut-être–échoue.",
|
|
48
|
+
"out": "Le plan–peut-être–échoue.",
|
|
49
|
+
"note": "dashes.md §3.4 P5 (spec 0.6.0), identical to fr-dashes-authored-en-dash-declined: no Quebec-specific dash source found, so the behaviour is inherited unchanged. A pure single authored U+2013 is declined unconditionally, even though fr-CA's `dash.parenthetical` is `em-spaced`. Correction pass (spec 0.6.0): re-witnessed with `peut-être` instead of `s'il` to remove the unrelated apostrophe conversion, so the whole-output `transform(in) === in` assertion isolates the P5 claim alone."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "fr-ca-ranges-none-years",
|
|
53
|
+
"rule": "ranges",
|
|
54
|
+
"mode": "text",
|
|
55
|
+
"in": "Les années 1914-1918 furent longues.",
|
|
56
|
+
"out": "Les années 1914-1918 furent longues.",
|
|
57
|
+
"rules": {
|
|
58
|
+
"ranges": true
|
|
59
|
+
},
|
|
60
|
+
"note": "spec 0.5.0 ownership correction: moved from `dashes` to `ranges` (`rules: {ranges: true}` explicit — `ranges` defaults off) — this case tests G1-G5/cluster/binding behaviour that `dashes` no longer evaluates at all for a digit-flanked token; it was vacuous under `rule: \"dashes\"` after the split. ranges.md §2 is the current normative source for the `range: \"none\"` no-op."
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "fr-ca-hyphen-noop-compounds",
|
|
64
|
+
"rule": "hyphen",
|
|
65
|
+
"mode": "text",
|
|
66
|
+
"in": "Le porte-parole a parlé du savoir-faire.",
|
|
67
|
+
"out": "Le porte-parole a parlé du savoir-faire.",
|
|
68
|
+
"note": "Identical to fr: hyphen lists are empty for both."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "fr-ca-quotes-primary",
|
|
72
|
+
"rule": "quotes",
|
|
73
|
+
"mode": "text",
|
|
74
|
+
"in": "Il a dit \"bonjour\".",
|
|
75
|
+
"out": "Il a dit « bonjour ».",
|
|
76
|
+
"note": "Same glyphs and inner space as fr — OQLF confirms chevrons and an unqualified 'espace insécable'."
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"id": "fr-ca-quotes-nested",
|
|
80
|
+
"rule": "quotes",
|
|
81
|
+
"mode": "text",
|
|
82
|
+
"in": "\"Il a dit 'oui' hier.\"",
|
|
83
|
+
"out": "« Il a dit 'oui' hier. »",
|
|
84
|
+
"note": "spec 0.5.0: same as fr-quotes-nested — 'oui' is preserved by the general ambiguous-medial-span veto."
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "fr-ca-apostrophe-elision",
|
|
88
|
+
"rule": "apostrophe",
|
|
89
|
+
"mode": "text",
|
|
90
|
+
"in": "l'été et l'hiver",
|
|
91
|
+
"out": "l’été et l’hiver",
|
|
92
|
+
"note": "Locale-independent within fr* — identical to fr."
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": "fr-ca-symbols-copyright",
|
|
96
|
+
"rule": "symbols",
|
|
97
|
+
"mode": "text",
|
|
98
|
+
"in": "Copyright (c) 2026 Iurii Rogulia",
|
|
99
|
+
"out": "Copyright © 2026 Iurii Rogulia"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"id": "fr-ca-symbols-multiplication",
|
|
103
|
+
"rule": "symbols",
|
|
104
|
+
"mode": "text",
|
|
105
|
+
"in": "Une carte 3x5",
|
|
106
|
+
"out": "Une carte 3×5"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"id": "fr-ca-nbsp-question-no-space",
|
|
110
|
+
"rule": "nbsp",
|
|
111
|
+
"mode": "text",
|
|
112
|
+
"in": "Ça va ?",
|
|
113
|
+
"out": "Ça va?",
|
|
114
|
+
"note": "The flagship fr vs fr-CA divergence: OQLF 'opte pour l'absence d'espace' before ? ! ; — the spaces rule strips the typed space and nbsp does not reinsert one, unlike fr where U+202F lands here."
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"id": "fr-ca-nbsp-exclamation-no-space",
|
|
118
|
+
"rule": "nbsp",
|
|
119
|
+
"mode": "text",
|
|
120
|
+
"in": "Bonjour !",
|
|
121
|
+
"out": "Bonjour!"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"id": "fr-ca-nbsp-semicolon-no-space",
|
|
125
|
+
"rule": "nbsp",
|
|
126
|
+
"mode": "text",
|
|
127
|
+
"in": "Oui ; non",
|
|
128
|
+
"out": "Oui; non"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"id": "fr-ca-nbsp-already-tight-no-space",
|
|
132
|
+
"rule": "nbsp",
|
|
133
|
+
"mode": "text",
|
|
134
|
+
"in": "Bonjour!",
|
|
135
|
+
"out": "Bonjour!",
|
|
136
|
+
"note": "Idempotency in the direction that matters most for fr-CA: an author who already omits the space, per OQLF practice, gets no space inserted."
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"id": "fr-ca-nbsp-wide-colon",
|
|
140
|
+
"rule": "nbsp",
|
|
141
|
+
"mode": "text",
|
|
142
|
+
"in": "Voici : la suite",
|
|
143
|
+
"out": "Voici : la suite",
|
|
144
|
+
"note": "Colon behaviour is shared with fr — both OQLF pages consulted agree with André on U+00A0 before ':'."
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"id": "fr-ca-nbsp-colon-inserted-with-no-space",
|
|
148
|
+
"rule": "nbsp",
|
|
149
|
+
"mode": "text",
|
|
150
|
+
"in": "Voici: la suite",
|
|
151
|
+
"out": "Voici : la suite"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"id": "fr-ca-nbsp-double-terminal",
|
|
155
|
+
"rule": "nbsp",
|
|
156
|
+
"mode": "text",
|
|
157
|
+
"in": "Vraiment ?!",
|
|
158
|
+
"out": "Vraiment?!",
|
|
159
|
+
"note": "Both marks are in the empty narrowBeforePunctuation list; contrast fr's 'Vraiment ?!' where only the leading mark gets the fine space."
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"id": "fr-ca-nbsp-guillemet-inner",
|
|
163
|
+
"rule": "nbsp",
|
|
164
|
+
"mode": "text",
|
|
165
|
+
"in": "Il a dit «mot».",
|
|
166
|
+
"out": "Il a dit « mot ».",
|
|
167
|
+
"note": "Quotes inner space is shared with fr."
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"id": "fr-ca-nbsp-unit-percent",
|
|
171
|
+
"rule": "nbsp",
|
|
172
|
+
"mode": "text",
|
|
173
|
+
"in": "98 % des cas",
|
|
174
|
+
"out": "98 % des cas",
|
|
175
|
+
"note": "Shared with fr — OQLF confirms nbsp before SI units and %."
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"id": "fr-ca-nbsp-unit-km",
|
|
179
|
+
"rule": "nbsp",
|
|
180
|
+
"mode": "text",
|
|
181
|
+
"in": "20 km à pied",
|
|
182
|
+
"out": "20 km à pied"
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"id": "fr-ca-nbsp-before-number",
|
|
186
|
+
"rule": "nbsp",
|
|
187
|
+
"mode": "text",
|
|
188
|
+
"in": "voir art. 237 et fig. 3",
|
|
189
|
+
"out": "voir art. 237 et fig. 3",
|
|
190
|
+
"note": "Inherited from fr's André citation; not independently re-verified against a Quebec-specific source."
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"id": "fr-ca-nbsp-before-word-mme",
|
|
194
|
+
"rule": "nbsp",
|
|
195
|
+
"mode": "text",
|
|
196
|
+
"in": "Mme Hugo et Mlle Durand",
|
|
197
|
+
"out": "Mme Hugo et Mlle Durand",
|
|
198
|
+
"note": "Inherited from fr's André citation."
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"id": "fr-ca-nbsp-initials",
|
|
202
|
+
"rule": "nbsp",
|
|
203
|
+
"mode": "text",
|
|
204
|
+
"in": "N. Bourbaki",
|
|
205
|
+
"out": "N. Bourbaki",
|
|
206
|
+
"note": "Inherited from fr's André citation (initialBinding: \"single\", spec 0.6.0 — the field used to be the boolean bindInitials)."
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"id": "fr-ca-nbsp-abbreviation",
|
|
210
|
+
"rule": "nbsp",
|
|
211
|
+
"mode": "text",
|
|
212
|
+
"in": "p. ex. ceci",
|
|
213
|
+
"out": "p. ex. ceci",
|
|
214
|
+
"note": "Inherited from fr's André citation."
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"id": "fr-ca-roundtrip-typeset-sentence",
|
|
218
|
+
"rule": "nbsp",
|
|
219
|
+
"mode": "text",
|
|
220
|
+
"in": "Il a dit : « bonjour », il est parti",
|
|
221
|
+
"out": "Il a dit : « bonjour », il est parti",
|
|
222
|
+
"note": "Round-trip on already-correct fr-CA typesetting — no ! ? ; involved, so identical to the fr round-trip."
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"id": "fr-ca-combined-sentence",
|
|
226
|
+
"rule": "nbsp",
|
|
227
|
+
"mode": "text",
|
|
228
|
+
"in": "Il a dit : \"Le trajet -- 20 km environ -- prendra une heure !\"",
|
|
229
|
+
"out": "Il a dit : « Le trajet — 20 km environ — prendra une heure! »",
|
|
230
|
+
"note": "Every rule in one sentence: colon gets U+00A0 (shared with fr), the guillemets get U+00A0 inner space, the dash pair becomes em-spaced, the unit gets U+00A0 — but the exclamation mark gets no space at all, the one point where fr-CA diverges from fr."
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"id": "fr-ca-quotes-030-bug3b-witness",
|
|
234
|
+
"rule": "quotes",
|
|
235
|
+
"mode": "html",
|
|
236
|
+
"in": "\"<p class=\"x\">«[t](u)",
|
|
237
|
+
"out": "\"<p class=\"x\">« [t](u)",
|
|
238
|
+
"note": "quotes.md §6 row 3b, and the pinned pipeline-idempotency.md §6 witness \"<p class=\"x\">«[t](u): the mode adapter splices a MARKER between the two text spans; « 's closeRight reads Rskip (because « ∈ SPACE-RIGHT) and finds [, so it is never canClose and no pair forms from `quotes` alone. The visible space after « is `nbsp` N8, which inserts unconditionally beside a primary-open occurrence regardless of pairing; verified idempotent by running the full pipeline twice."
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
"id": "fr-ca-quotes-030-adversarial-interleaved",
|
|
242
|
+
"rule": "quotes",
|
|
243
|
+
"mode": "text",
|
|
244
|
+
"in": "«a“b«c»d”e»",
|
|
245
|
+
"out": "« a“b« c »d”e »",
|
|
246
|
+
"note": "quotes.md §6, ru/el/fr/fr-CA adversarial sweep: already-curly interleaved WIDE-only text, mirroring fr's own case."
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"id": "fr-ca-quotes-rock-n-roll",
|
|
250
|
+
"rule": "quotes",
|
|
251
|
+
"mode": "text",
|
|
252
|
+
"in": "rock 'n' roll",
|
|
253
|
+
"out": "rock 'n' roll",
|
|
254
|
+
"note": "spec 0.5.0: fr-CA has no cited quotes.elisionIdioms entry for this idiom (quotes.md §7 item 8 — only en-US does, via Chicago Manual of Style Online and the American Heritage Dictionary). The general ambiguous-medial-span veto (quotes.md 3.2) preserves both marks as literal U+0027 rather than pairing them as an ordinary quotation, closing the false positive AUDIT_REMEDIATION_AND_RELEASE_PLAN.md 3.1 named for \"equivalent cases in several locales.\""
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"id": "fr-ca-ranges-none",
|
|
258
|
+
"rule": "ranges",
|
|
259
|
+
"mode": "text",
|
|
260
|
+
"in": "Les années 1914-1918",
|
|
261
|
+
"out": "Les années 1914-1918",
|
|
262
|
+
"rules": {
|
|
263
|
+
"ranges": true
|
|
264
|
+
},
|
|
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
|
+
}
|