polytypo 1.10.0 → 1.12.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/polytypo/data/VERSION +1 -1
  3. data/lib/polytypo/data/fixtures/bg.json +253 -0
  4. data/lib/polytypo/data/fixtures/cs.json +1 -1
  5. data/lib/polytypo/data/fixtures/de-CH.json +1 -1
  6. data/lib/polytypo/data/fixtures/de-DE.json +1 -1
  7. data/lib/polytypo/data/fixtures/el.json +1 -1
  8. data/lib/polytypo/data/fixtures/en-GB.json +1 -1
  9. data/lib/polytypo/data/fixtures/en-US.json +880 -3
  10. data/lib/polytypo/data/fixtures/es.json +1 -1
  11. data/lib/polytypo/data/fixtures/fi.json +1 -1
  12. data/lib/polytypo/data/fixtures/fr-CA.json +1 -1
  13. data/lib/polytypo/data/fixtures/fr.json +1 -1
  14. data/lib/polytypo/data/fixtures/hr.json +253 -0
  15. data/lib/polytypo/data/fixtures/hu.json +264 -0
  16. data/lib/polytypo/data/fixtures/it.json +1 -1
  17. data/lib/polytypo/data/fixtures/locale-resolution.json +1 -1
  18. data/lib/polytypo/data/fixtures/nl.json +1 -1
  19. data/lib/polytypo/data/fixtures/pl.json +1 -1
  20. data/lib/polytypo/data/fixtures/pt-BR.json +1 -1
  21. data/lib/polytypo/data/fixtures/pt-PT.json +1 -1
  22. data/lib/polytypo/data/fixtures/ro.json +234 -0
  23. data/lib/polytypo/data/fixtures/ru.json +1 -1
  24. data/lib/polytypo/data/fixtures/sk.json +278 -0
  25. data/lib/polytypo/data/fixtures/sv.json +1 -1
  26. data/lib/polytypo/data/fixtures/tr.json +1 -1
  27. data/lib/polytypo/data/fixtures/uk.json +1 -1
  28. data/lib/polytypo/data/locales/bg.json +82 -0
  29. data/lib/polytypo/data/locales/hr.json +82 -0
  30. data/lib/polytypo/data/locales/hu.json +88 -0
  31. data/lib/polytypo/data/locales/registry.json +7 -2
  32. data/lib/polytypo/data/locales/ro.json +82 -0
  33. data/lib/polytypo/data/locales/sk.json +94 -0
  34. data/lib/polytypo/data/rules/dashes.md +2 -1
  35. data/lib/polytypo/data/rules/modes.md +173 -72
  36. data/lib/polytypo/data/rules/order.json +1 -1
  37. data/lib/polytypo/data/rules/quotes.md +9 -0
  38. data/lib/polytypo/data/rules/ranges.md +6 -5
  39. data/lib/polytypo/data/schema/locale.schema.json +2 -2
  40. data/lib/polytypo/modes/yaml.rb +167 -29
  41. data/lib/polytypo/version.rb +1 -1
  42. metadata +11 -1
@@ -34,9 +34,32 @@ module Polytypo
34
34
  spans = []
35
35
  li = 0
36
36
  li = scan_line(chars, lines, li, keys, spans) while li < lines.length
37
- spans
37
+ drop_carriage_return_regions(chars, lines, spans)
38
38
  end
39
39
 
40
+ # 3.8.4's carriage-return region: a line containing a U+000D yields no spans, and neither does
41
+ # any later line up to the first one that is non-empty, does not begin with U+0020 and
42
+ # contains none. It removes spans and never adds one.
43
+ def self.drop_carriage_return_regions(chars, lines, spans)
44
+ in_region = false
45
+ declined = lines.map do |line|
46
+ if lone_cr?(chars, line)
47
+ in_region = true
48
+ elsif line.end > line.start && chars[line.start] != " "
49
+ in_region = false
50
+ end
51
+ in_region
52
+ end
53
+ return spans unless declined.any?
54
+
55
+ li = 0
56
+ spans.reject do |span|
57
+ li += 1 while li + 1 < lines.length && span.start >= lines[li + 1].start
58
+ declined[li]
59
+ end
60
+ end
61
+ private_class_method :drop_carriage_return_regions
62
+
40
63
  # 3.8.4: a line ends at U+000A, and A U+000D IMMEDIATELY BEFORE IT IS NOT PART OF THE LINE
41
64
  # -- it is a terminator like the U+000A itself, so it lies outside every span and comes back
42
65
  # untouched. Without that clause a CRLF file behaves differently from the same bytes with
@@ -70,6 +93,12 @@ module Polytypo
70
93
  end
71
94
  private_class_method :blank?
72
95
 
96
+ # 3.8.4 step 7: a line of the value run that is not blank. Blank lines alone are not one.
97
+ def self.continuation?(chars, lines, li, run_end)
98
+ ((li + 1)...run_end).any? { |k| !blank?(chars, lines[k]) }
99
+ end
100
+ private_class_method :continuation?
101
+
73
102
  def self.tab?(chars, line)
74
103
  (line.start...line.end).any? { |i| chars[i] == TAB }
75
104
  end
@@ -129,72 +158,181 @@ module Polytypo
129
158
  end
130
159
  private_class_method :value_run_end
131
160
 
161
+ # 3.8.4 step 5: the index of the quote that closes the one at q, or -1. In a double-quoted
162
+ # scalar a U+005C and the code point after it never close the quote, which decodes nothing:
163
+ # no YAML escape ends in anything that would be a closing quote. A quote not closed on the
164
+ # line returns -1.
165
+ def self.closing_quote(chars, line, q)
166
+ quote = chars[q]
167
+ j = q + 1
168
+ while j < line.end
169
+ c = chars[j]
170
+ if (quote == '"' && c == "\\") || (quote == "'" && c == quote && j + 1 < line.end && chars[j + 1] == "'")
171
+ j += 2
172
+ elsif c != quote
173
+ j += 1
174
+ else
175
+ return j
176
+ end
177
+ end
178
+ -1
179
+ end
180
+ private_class_method :closing_quote
181
+
182
+ # 3.8.4 step 3: the next line that begins in column 0 with a document marker.
183
+ def self.document_run_end(chars, lines, li)
184
+ k = li + 1
185
+ k += 1 while k < lines.length && !document_marker?(chars, lines[k].start, lines[k].end)
186
+ k
187
+ end
188
+ private_class_method :document_run_end
189
+
190
+ # 3.8.4 step 5's own-line node: everything up to the first later line that is non-empty and
191
+ # does not begin with U+0020, because the scan does not track the key above.
192
+ def self.own_line_run_end(chars, lines, li, indent)
193
+ return document_run_end(chars, lines, li) if indent.zero?
194
+
195
+ k = li + 1
196
+ while k < lines.length
197
+ nxt = lines[k]
198
+ break if nxt.end > nxt.start && chars[nxt.start] != SPACE
199
+
200
+ k += 1
201
+ end
202
+ k
203
+ end
204
+ private_class_method :own_line_run_end
205
+
206
+ # 3.8.4 step 4: "-", "?" or ":" followed by U+0020 or the line end.
207
+ def self.node_marker?(chars, j, to)
208
+ return false unless ["-", "?", ":"].include?(chars[j])
209
+
210
+ j + 1 == to || chars[j + 1] == SPACE
211
+ end
212
+ private_class_method :node_marker?
213
+
214
+ # 3.8.4 step 5. Returns [colon, key_end, declined, open_quote]; colon is -1 for a line with
215
+ # no key, which is a node line.
216
+ def self.locate_key(chars, line, i)
217
+ first = chars[i]
218
+ if ['"', "'"].include?(first)
219
+ close = closing_quote(chars, line, i)
220
+ return [-1, -1, false, true] if close.negative?
221
+
222
+ p = close + 1
223
+ p += 1 while p < line.end && chars[p] == SPACE
224
+ return [p, close + 1, true, false] if p < line.end && indicator_colon?(chars, p, line.end)
225
+
226
+ return [-1, -1, false, false]
227
+ end
228
+ return [-1, -1, false, false] if ["{", "[", "&", "*", "!"].include?(first)
229
+
230
+ declined = false
231
+ (i...line.end).each do |j|
232
+ c = chars[j]
233
+ break if c == "#" && chars[j - 1] == SPACE
234
+
235
+ if indicator_colon?(chars, j, line.end)
236
+ key_end = j
237
+ key_end -= 1 while key_end > i && chars[key_end - 1] == SPACE
238
+ return [j, key_end, declined, false]
239
+ end
240
+ declined = true if DECLINING_KEY_CHARS.include?(c)
241
+ end
242
+ [-1, -1, false, false]
243
+ end
244
+ private_class_method :locate_key
245
+
246
+ # 3.8.4 step 7: whether a declined key's value can continue on the next line.
247
+ def self.declined_value_open?(chars, line, v)
248
+ value = chars[v]
249
+ return true if ["|", ">", "{", "[", "&", "!"].include?(value)
250
+ return false unless ['"', "'"].include?(value)
251
+
252
+ close = closing_quote(chars, line, v)
253
+ return true if close.negative?
254
+
255
+ p = close + 1
256
+ p += 1 while p < line.end && chars[p] == SPACE
257
+ p < line.end && chars[p] != "#"
258
+ end
259
+ private_class_method :declined_value_open?
260
+
132
261
  # One step of 3.8.4. Returns the index of the next line to scan.
133
262
  def self.scan_line(chars, lines, li, keys, spans)
134
263
  line = lines[li]
135
264
  skip_line = li + 1
136
265
 
137
- # step 1 -- blank, or a tab anywhere, which makes indentation undecidable.
266
+ # step 1 -- blank. A tab makes indentation undecidable, so the line and whatever it
267
+ # absorbs yield no spans, but steps 2 to 7 still decide what it absorbs.
138
268
  start = first_non_space(chars, line)
139
- return skip_line if start == line.end || tab?(chars, line)
269
+ return skip_line if start == line.end
140
270
 
271
+ quiet = tab?(chars, line)
141
272
  indent = start - line.start
142
273
 
143
- # steps 2 and 3 -- comment, directive, document marker.
274
+ # steps 2 and 3 -- comment, directive, document marker. A marker with a node after it
275
+ # absorbs the document.
144
276
  return skip_line if ["#", "%"].include?(chars[start])
145
- return skip_line if document_marker?(chars, start, line.end)
146
277
 
147
- # step 4 -- block sequence entries are consumed, not skipped; "- - key: v" nests.
278
+ if document_marker?(chars, start, line.end)
279
+ p = start + 3
280
+ p += 1 while p < line.end && chars[p] == SPACE
281
+ return skip_line if p >= line.end || chars[p] == "#"
282
+
283
+ return indent.zero? ? document_run_end(chars, lines, li) : skip_line
284
+ end
285
+
286
+ # step 4 -- node markers are consumed, not skipped; "- - key: v" nests.
148
287
  i = start
149
- while i < line.end && sequence_dash?(chars, i, line.end)
288
+ while i < line.end && node_marker?(chars, i, line.end)
150
289
  i += 2
151
290
  i += 1 while i < line.end && chars[i] == SPACE
152
291
  end
153
- return skip_line if i >= line.end
292
+ return skip_line if i >= line.end || chars[i] == "#"
154
293
 
155
294
  # step 5 -- find the key. A colon NOT followed by U+0020 or the line end is an ordinary
156
295
  # key character, so "a:b: v" has the key "a:b"; stating that is what keeps five scanners
157
296
  # agreeing.
158
- key_start = i
159
- colon = -1
160
- (i...line.end).each do |j|
161
- return skip_line if DECLINING_KEY_CHARS.include?(chars[j])
297
+ colon, key_end, declined, open_quote = locate_key(chars, line, i)
162
298
 
163
- if indicator_colon?(chars, j, line.end)
164
- colon = j
165
- break
166
- end
167
- end
168
- return skip_line if colon.negative?
299
+ # No key: the line is a node of its own, and it absorbs its value run. One that opens a
300
+ # flow collection, a block scalar or a quote and sits on a line of its own absorbs the
301
+ # region.
302
+ if colon.negative?
303
+ own_line = i == start && (open_quote || ["{", "[", "|", ">"].include?(chars[i]))
304
+ return own_line_run_end(chars, lines, li, indent) if own_line
169
305
 
170
- key_end = colon
171
- key_end -= 1 while key_end > key_start && chars[key_end - 1] == SPACE
172
- return skip_line if key_end <= key_start
306
+ return value_run_end(chars, lines, li, indent)
307
+ end
173
308
 
174
309
  # step 6 -- an empty value means a nested node, whose lines ARE scanned on their own.
175
310
  v = colon + 1
176
311
  v += 1 while v < line.end && chars[v] == SPACE
177
312
  return skip_line if v >= line.end
178
313
 
179
- # step 7 -- the line carries an inline value, so its continuation lines belong to it.
314
+ # step 7 -- the line carries an inline value, so its continuation lines belong to it. A
315
+ # declined key's value absorbs them only if it can continue on the next line.
316
+ if declined
317
+ return declined_value_open?(chars, line, v) ? value_run_end(chars, lines, li, indent) : skip_line
318
+ end
319
+
180
320
  nxt = value_run_end(chars, lines, li, indent)
181
321
 
182
322
  # step 8 -- the key must be listed. Checked before the value's form, so an unlisted key
183
323
  # costs nothing to decline: this is what makes "run:", "if:" and "image:" unreachable.
184
- return nxt unless keys.include?(chars[key_start...key_end].join)
185
-
186
- # step 9 -- a lone U+000D declines the line, and its value run is still consumed.
187
- return nxt if lone_cr?(chars, line)
324
+ return nxt unless keys.include?(chars[i...key_end].join)
325
+ return nxt if quiet
188
326
 
189
- # step 10 -- the scalar form.
327
+ # step 9 -- the scalar form.
190
328
  value = chars[v]
191
329
  return nxt if NON_SCALAR_VALUE_CHARS.include?(value)
192
330
 
193
331
  if ["|", ">"].include?(value)
194
332
  block_scalar(chars, line, lines[(li + 1)...nxt], indent, v, spans)
195
333
  elsif ['"', "'"].include?(value)
196
- quoted_scalar(chars, line, v, spans) if nxt == li + 1
197
- elsif nxt == li + 1
334
+ quoted_scalar(chars, line, v, spans) unless continuation?(chars, lines, li, nxt)
335
+ elsif !continuation?(chars, lines, li, nxt)
198
336
  plain_scalar(chars, line, v, spans)
199
337
  end
200
338
  nxt
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polytypo
4
- VERSION = "1.10.0"
4
+ VERSION = "1.12.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polytypo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.10.0
4
+ version: 1.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Iurii Rogulia
@@ -40,6 +40,7 @@ files:
40
40
  - lib/polytypo/data/README.md
41
41
  - lib/polytypo/data/UNICODE
42
42
  - lib/polytypo/data/VERSION
43
+ - lib/polytypo/data/fixtures/bg.json
43
44
  - lib/polytypo/data/fixtures/cs.json
44
45
  - lib/polytypo/data/fixtures/de-CH.json
45
46
  - lib/polytypo/data/fixtures/de-DE.json
@@ -50,16 +51,21 @@ files:
50
51
  - lib/polytypo/data/fixtures/fi.json
51
52
  - lib/polytypo/data/fixtures/fr-CA.json
52
53
  - lib/polytypo/data/fixtures/fr.json
54
+ - lib/polytypo/data/fixtures/hr.json
55
+ - lib/polytypo/data/fixtures/hu.json
53
56
  - lib/polytypo/data/fixtures/it.json
54
57
  - lib/polytypo/data/fixtures/locale-resolution.json
55
58
  - lib/polytypo/data/fixtures/nl.json
56
59
  - lib/polytypo/data/fixtures/pl.json
57
60
  - lib/polytypo/data/fixtures/pt-BR.json
58
61
  - lib/polytypo/data/fixtures/pt-PT.json
62
+ - lib/polytypo/data/fixtures/ro.json
59
63
  - lib/polytypo/data/fixtures/ru.json
64
+ - lib/polytypo/data/fixtures/sk.json
60
65
  - lib/polytypo/data/fixtures/sv.json
61
66
  - lib/polytypo/data/fixtures/tr.json
62
67
  - lib/polytypo/data/fixtures/uk.json
68
+ - lib/polytypo/data/locales/bg.json
63
69
  - lib/polytypo/data/locales/cs.json
64
70
  - lib/polytypo/data/locales/de-CH.json
65
71
  - lib/polytypo/data/locales/de-DE.json
@@ -70,13 +76,17 @@ files:
70
76
  - lib/polytypo/data/locales/fi.json
71
77
  - lib/polytypo/data/locales/fr-CA.json
72
78
  - lib/polytypo/data/locales/fr.json
79
+ - lib/polytypo/data/locales/hr.json
80
+ - lib/polytypo/data/locales/hu.json
73
81
  - lib/polytypo/data/locales/it.json
74
82
  - lib/polytypo/data/locales/nl.json
75
83
  - lib/polytypo/data/locales/pl.json
76
84
  - lib/polytypo/data/locales/pt-BR.json
77
85
  - lib/polytypo/data/locales/pt-PT.json
78
86
  - lib/polytypo/data/locales/registry.json
87
+ - lib/polytypo/data/locales/ro.json
79
88
  - lib/polytypo/data/locales/ru.json
89
+ - lib/polytypo/data/locales/sk.json
80
90
  - lib/polytypo/data/locales/sv.json
81
91
  - lib/polytypo/data/locales/tr.json
82
92
  - lib/polytypo/data/locales/uk.json