cton 0.1.0 → 0.1.1
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/.rubocop.yml +13 -0
- data/CHANGELOG.md +17 -0
- data/README.md +7 -3
- data/lib/cton/decoder.rb +491 -0
- data/lib/cton/encoder.rb +247 -0
- data/lib/cton/version.rb +1 -1
- data/lib/cton.rb +2 -557
- data/sig/cton.rbs +76 -4
- metadata +3 -1
data/lib/cton.rb
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require "bigdecimal"
|
|
4
4
|
require_relative "cton/version"
|
|
5
|
+
require_relative "cton/encoder"
|
|
6
|
+
require_relative "cton/decoder"
|
|
5
7
|
|
|
6
8
|
module Cton
|
|
7
9
|
class Error < StandardError; end
|
|
@@ -20,562 +22,5 @@ module Cton
|
|
|
20
22
|
Decoder.new(symbolize_names: symbolize_names).decode(cton_string)
|
|
21
23
|
end
|
|
22
24
|
alias parse load
|
|
23
|
-
|
|
24
|
-
class Encoder
|
|
25
|
-
SAFE_TOKEN = /\A[0-9A-Za-z_.:-]+\z/.freeze
|
|
26
|
-
NUMERIC_TOKEN = /\A-?(?:\d+)(?:\.\d+)?(?:[eE][+-]?\d+)?\z/.freeze
|
|
27
|
-
RESERVED_LITERALS = %w[true false null].freeze
|
|
28
|
-
|
|
29
|
-
def initialize(separator: "\n")
|
|
30
|
-
@separator = separator || ""
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def encode(payload)
|
|
34
|
-
encode_root(payload)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
private
|
|
38
|
-
|
|
39
|
-
attr_reader :separator
|
|
40
|
-
|
|
41
|
-
def encode_root(value)
|
|
42
|
-
case value
|
|
43
|
-
when Hash
|
|
44
|
-
value.map { |key, nested| encode_top_level_pair(key, nested) }.join(separator)
|
|
45
|
-
else
|
|
46
|
-
encode_value(value, context: :standalone)
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def encode_top_level_pair(key, value)
|
|
51
|
-
"#{format_key(key)}#{encode_value(value, context: :top_pair)}"
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def encode_value(value, context:)
|
|
55
|
-
case value
|
|
56
|
-
when Hash
|
|
57
|
-
encode_object(value)
|
|
58
|
-
when Array
|
|
59
|
-
encode_array(value)
|
|
60
|
-
else
|
|
61
|
-
prefix = context == :top_pair ? "=" : ""
|
|
62
|
-
"#{prefix}#{encode_scalar(value)}"
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def encode_object(hash)
|
|
67
|
-
return "()" if hash.empty?
|
|
68
|
-
|
|
69
|
-
pairs = hash.map do |key, value|
|
|
70
|
-
"#{format_key(key)}=#{encode_value(value, context: :object)}"
|
|
71
|
-
end
|
|
72
|
-
"(#{pairs.join(',')})"
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def encode_array(list)
|
|
76
|
-
length = list.length
|
|
77
|
-
return "[0]=" if length.zero?
|
|
78
|
-
|
|
79
|
-
if table_candidate?(list)
|
|
80
|
-
"[#{length}]#{encode_table(list)}"
|
|
81
|
-
else
|
|
82
|
-
body = if list.all? { |value| scalar?(value) }
|
|
83
|
-
list.map { |value| encode_scalar(value) }.join(",")
|
|
84
|
-
else
|
|
85
|
-
list.map { |value| encode_array_element(value) }.join(",")
|
|
86
|
-
end
|
|
87
|
-
"[#{length}]=#{body}"
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def encode_table(rows)
|
|
92
|
-
header = rows.first.keys
|
|
93
|
-
header_token = "{#{header.map { |key| format_key(key) }.join(',')}}"
|
|
94
|
-
table_rows = rows.map do |row|
|
|
95
|
-
header.map { |field| encode_scalar(row.fetch(field)) }.join(",")
|
|
96
|
-
end
|
|
97
|
-
"#{header_token}=#{table_rows.join(';')}"
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def encode_array_element(value)
|
|
101
|
-
encode_value(value, context: :array)
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def encode_scalar(value)
|
|
105
|
-
case value
|
|
106
|
-
when String
|
|
107
|
-
encode_string(value)
|
|
108
|
-
when TrueClass, FalseClass
|
|
109
|
-
value ? "true" : "false"
|
|
110
|
-
when NilClass
|
|
111
|
-
"null"
|
|
112
|
-
when Numeric
|
|
113
|
-
format_number(value)
|
|
114
|
-
else
|
|
115
|
-
raise EncodeError, "Unsupported value: #{value.class}"
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def encode_string(value)
|
|
120
|
-
return '""' if value.empty?
|
|
121
|
-
|
|
122
|
-
string_needs_quotes?(value) ? quote_string(value) : value
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
FLOAT_DECIMAL_PRECISION = Float::DIG
|
|
126
|
-
|
|
127
|
-
def format_number(value)
|
|
128
|
-
case value
|
|
129
|
-
when Float
|
|
130
|
-
return "null" if value.nan? || value.infinite?
|
|
131
|
-
|
|
132
|
-
normalize_decimal_string(float_decimal_string(value))
|
|
133
|
-
when Integer
|
|
134
|
-
value.to_s
|
|
135
|
-
else
|
|
136
|
-
if defined?(BigDecimal) && value.is_a?(BigDecimal)
|
|
137
|
-
normalize_decimal_string(value.to_s("F"))
|
|
138
|
-
else
|
|
139
|
-
value.to_s
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def normalize_decimal_string(string)
|
|
145
|
-
stripped = string.start_with?("+") ? string[1..-1] : string
|
|
146
|
-
return "0" if zero_string?(stripped)
|
|
147
|
-
|
|
148
|
-
if stripped.include?(".")
|
|
149
|
-
stripped = stripped.sub(/0+\z/, "")
|
|
150
|
-
stripped = stripped.sub(/\.\z/, "")
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
stripped
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def zero_string?(string)
|
|
157
|
-
string.match?(/\A-?0+(?:\.0+)?\z/)
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
def float_decimal_string(value)
|
|
161
|
-
if defined?(BigDecimal)
|
|
162
|
-
BigDecimal(value.to_s).to_s("F")
|
|
163
|
-
else
|
|
164
|
-
Kernel.format("%.#{FLOAT_DECIMAL_PRECISION}f", value)
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
def format_key(key)
|
|
169
|
-
key_string = key.to_s
|
|
170
|
-
unless SAFE_TOKEN.match?(key_string)
|
|
171
|
-
raise EncodeError, "Invalid key: #{key_string.inspect}"
|
|
172
|
-
end
|
|
173
|
-
key_string
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def string_needs_quotes?(value)
|
|
177
|
-
return true unless SAFE_TOKEN.match?(value)
|
|
178
|
-
RESERVED_LITERALS.include?(value) || numeric_like?(value)
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def numeric_like?(value)
|
|
182
|
-
NUMERIC_TOKEN.match?(value)
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def quote_string(value)
|
|
186
|
-
"\"#{escape_string(value)}\""
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
def escape_string(value)
|
|
190
|
-
value.gsub(/["\\\n\r\t]/) do |char|
|
|
191
|
-
case char
|
|
192
|
-
when "\n" then "\\n"
|
|
193
|
-
when "\r" then "\\r"
|
|
194
|
-
when "\t" then "\\t"
|
|
195
|
-
else
|
|
196
|
-
"\\#{char}"
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
def scalar?(value)
|
|
202
|
-
value.is_a?(String) || value.is_a?(Numeric) || value == true || value == false || value.nil?
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
def table_candidate?(rows)
|
|
206
|
-
return false if rows.empty?
|
|
207
|
-
|
|
208
|
-
first = rows.first
|
|
209
|
-
return false unless first.is_a?(Hash) && !first.empty?
|
|
210
|
-
|
|
211
|
-
keys = first.keys
|
|
212
|
-
rows.all? do |row|
|
|
213
|
-
row.is_a?(Hash) && row.keys == keys && row.values.all? { |val| scalar?(val) }
|
|
214
|
-
end
|
|
215
|
-
end
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
class Decoder
|
|
219
|
-
TERMINATORS = [",", ";", ")", "]", "}"].freeze
|
|
220
|
-
|
|
221
|
-
def initialize(symbolize_names: false)
|
|
222
|
-
@symbolize_names = symbolize_names
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
def decode(cton)
|
|
226
|
-
@source = cton.to_s
|
|
227
|
-
@index = 0
|
|
228
|
-
skip_ws
|
|
229
|
-
|
|
230
|
-
value = if key_ahead?(@index)
|
|
231
|
-
parse_document
|
|
232
|
-
else
|
|
233
|
-
parse_value(allow_key_boundary: true)
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
skip_ws
|
|
237
|
-
raise ParseError, "Unexpected trailing data" unless eof?
|
|
238
|
-
|
|
239
|
-
value
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
private
|
|
243
|
-
|
|
244
|
-
attr_reader :symbolize_names
|
|
245
|
-
|
|
246
|
-
def parse_document
|
|
247
|
-
result = {}
|
|
248
|
-
until eof?
|
|
249
|
-
key = parse_key_name
|
|
250
|
-
value = parse_value_for_key
|
|
251
|
-
assign_pair(result, key, value)
|
|
252
|
-
skip_ws
|
|
253
|
-
end
|
|
254
|
-
result
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
def parse_value_for_key
|
|
258
|
-
skip_ws
|
|
259
|
-
char = current_char
|
|
260
|
-
case char
|
|
261
|
-
when "("
|
|
262
|
-
parse_object
|
|
263
|
-
when "["
|
|
264
|
-
parse_array
|
|
265
|
-
when "="
|
|
266
|
-
advance
|
|
267
|
-
parse_scalar(allow_key_boundary: true)
|
|
268
|
-
else
|
|
269
|
-
raise ParseError, "Unexpected token #{char.inspect} while reading value"
|
|
270
|
-
end
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
def parse_object
|
|
274
|
-
expect!("(")
|
|
275
|
-
skip_ws
|
|
276
|
-
if current_char == ")"
|
|
277
|
-
expect!(")")
|
|
278
|
-
return {}
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
pairs = {}
|
|
282
|
-
loop do
|
|
283
|
-
key = parse_key_name
|
|
284
|
-
expect!("=")
|
|
285
|
-
value = parse_value
|
|
286
|
-
assign_pair(pairs, key, value)
|
|
287
|
-
skip_ws
|
|
288
|
-
break if current_char == ")"
|
|
289
|
-
expect!(",")
|
|
290
|
-
skip_ws
|
|
291
|
-
end
|
|
292
|
-
expect!(")")
|
|
293
|
-
pairs
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
def parse_array
|
|
297
|
-
expect!("[")
|
|
298
|
-
length = parse_integer_literal
|
|
299
|
-
expect!("]")
|
|
300
|
-
skip_ws
|
|
301
|
-
|
|
302
|
-
header = parse_header if current_char == "{"
|
|
303
|
-
|
|
304
|
-
expect!("=")
|
|
305
|
-
return [] if length.zero?
|
|
306
|
-
|
|
307
|
-
header ? parse_table_rows(length, header) : parse_array_elements(length)
|
|
308
|
-
end
|
|
309
|
-
|
|
310
|
-
def parse_header
|
|
311
|
-
expect!("{")
|
|
312
|
-
fields = []
|
|
313
|
-
loop do
|
|
314
|
-
fields << parse_key_name
|
|
315
|
-
break if current_char == "}"
|
|
316
|
-
expect!(",")
|
|
317
|
-
end
|
|
318
|
-
expect!("}")
|
|
319
|
-
fields
|
|
320
|
-
end
|
|
321
|
-
|
|
322
|
-
def parse_table_rows(length, header)
|
|
323
|
-
rows = []
|
|
324
|
-
length.times do |row_index|
|
|
325
|
-
row = {}
|
|
326
|
-
header.each_with_index do |field, column_index|
|
|
327
|
-
allow_boundary = row_index == length - 1 && column_index == header.length - 1
|
|
328
|
-
row[field] = parse_scalar(allow_key_boundary: allow_boundary)
|
|
329
|
-
expect!(",") if column_index < header.length - 1
|
|
330
|
-
end
|
|
331
|
-
rows << symbolize_keys(row)
|
|
332
|
-
expect!(";") if row_index < length - 1
|
|
333
|
-
end
|
|
334
|
-
rows
|
|
335
|
-
end
|
|
336
|
-
|
|
337
|
-
def parse_array_elements(length)
|
|
338
|
-
values = []
|
|
339
|
-
length.times do |index|
|
|
340
|
-
allow_boundary = index == length - 1
|
|
341
|
-
values << parse_value(allow_key_boundary: allow_boundary)
|
|
342
|
-
expect!(",") if index < length - 1
|
|
343
|
-
end
|
|
344
|
-
values
|
|
345
|
-
end
|
|
346
|
-
|
|
347
|
-
def parse_value(allow_key_boundary: false)
|
|
348
|
-
skip_ws
|
|
349
|
-
char = current_char
|
|
350
|
-
raise ParseError, "Unexpected end of input" if char.nil?
|
|
351
|
-
|
|
352
|
-
case char
|
|
353
|
-
when "("
|
|
354
|
-
parse_object
|
|
355
|
-
when "["
|
|
356
|
-
parse_array
|
|
357
|
-
when '"'
|
|
358
|
-
parse_string
|
|
359
|
-
else
|
|
360
|
-
parse_scalar(allow_key_boundary: allow_key_boundary)
|
|
361
|
-
end
|
|
362
|
-
end
|
|
363
|
-
|
|
364
|
-
def parse_scalar(terminators: TERMINATORS, allow_key_boundary: false)
|
|
365
|
-
skip_ws
|
|
366
|
-
return parse_string if current_char == '"'
|
|
367
|
-
|
|
368
|
-
start = @index
|
|
369
|
-
limit_index = allow_key_boundary ? next_key_index(@index) : nil
|
|
370
|
-
exit_reason = nil
|
|
371
|
-
|
|
372
|
-
while !eof?
|
|
373
|
-
if limit_index && @index >= limit_index
|
|
374
|
-
exit_reason = :boundary
|
|
375
|
-
break
|
|
376
|
-
end
|
|
377
|
-
|
|
378
|
-
char = current_char
|
|
379
|
-
|
|
380
|
-
if char.nil?
|
|
381
|
-
exit_reason = :eof
|
|
382
|
-
break
|
|
383
|
-
elsif terminators.include?(char)
|
|
384
|
-
exit_reason = :terminator
|
|
385
|
-
break
|
|
386
|
-
elsif whitespace?(char)
|
|
387
|
-
exit_reason = :whitespace
|
|
388
|
-
break
|
|
389
|
-
elsif "()[]{}".include?(char)
|
|
390
|
-
exit_reason = :structure
|
|
391
|
-
break
|
|
392
|
-
end
|
|
393
|
-
|
|
394
|
-
@index += 1
|
|
395
|
-
end
|
|
396
|
-
|
|
397
|
-
token = if exit_reason == :boundary && limit_index
|
|
398
|
-
@source[start...limit_index]
|
|
399
|
-
else
|
|
400
|
-
@source[start...@index]
|
|
401
|
-
end
|
|
402
|
-
|
|
403
|
-
raise ParseError, "Empty value" if token.nil? || token.empty?
|
|
404
|
-
|
|
405
|
-
convert_scalar(token)
|
|
406
|
-
end
|
|
407
|
-
|
|
408
|
-
def convert_scalar(token)
|
|
409
|
-
case token
|
|
410
|
-
when "true" then true
|
|
411
|
-
when "false" then false
|
|
412
|
-
when "null" then nil
|
|
413
|
-
else
|
|
414
|
-
if integer?(token)
|
|
415
|
-
token.to_i
|
|
416
|
-
elsif float?(token)
|
|
417
|
-
token.to_f
|
|
418
|
-
else
|
|
419
|
-
token
|
|
420
|
-
end
|
|
421
|
-
end
|
|
422
|
-
end
|
|
423
|
-
|
|
424
|
-
def parse_string
|
|
425
|
-
expect!("\"")
|
|
426
|
-
buffer = +""
|
|
427
|
-
while !eof?
|
|
428
|
-
char = current_char
|
|
429
|
-
raise ParseError, "Unterminated string" if char.nil?
|
|
430
|
-
|
|
431
|
-
if char == '\\'
|
|
432
|
-
@index += 1
|
|
433
|
-
escaped = current_char
|
|
434
|
-
raise ParseError, "Invalid escape sequence" if escaped.nil?
|
|
435
|
-
buffer << case escaped
|
|
436
|
-
when 'n' then "\n"
|
|
437
|
-
when 'r' then "\r"
|
|
438
|
-
when 't' then "\t"
|
|
439
|
-
when '"', '\\' then escaped
|
|
440
|
-
else
|
|
441
|
-
raise ParseError, "Unsupported escape sequence"
|
|
442
|
-
end
|
|
443
|
-
elsif char == '"'
|
|
444
|
-
break
|
|
445
|
-
else
|
|
446
|
-
buffer << char
|
|
447
|
-
end
|
|
448
|
-
@index += 1
|
|
449
|
-
end
|
|
450
|
-
expect!("\"")
|
|
451
|
-
buffer
|
|
452
|
-
end
|
|
453
|
-
|
|
454
|
-
def parse_key_name
|
|
455
|
-
skip_ws
|
|
456
|
-
start = @index
|
|
457
|
-
while !eof? && safe_key_char?(current_char)
|
|
458
|
-
@index += 1
|
|
459
|
-
end
|
|
460
|
-
token = @source[start...@index]
|
|
461
|
-
raise ParseError, "Invalid key" if token.nil? || token.empty?
|
|
462
|
-
symbolize_names ? token.to_sym : token
|
|
463
|
-
end
|
|
464
|
-
|
|
465
|
-
def parse_integer_literal
|
|
466
|
-
start = @index
|
|
467
|
-
while !eof? && current_char =~ /\d/
|
|
468
|
-
@index += 1
|
|
469
|
-
end
|
|
470
|
-
token = @source[start...@index]
|
|
471
|
-
raise ParseError, "Expected digits" if token.nil? || token.empty?
|
|
472
|
-
Integer(token, 10)
|
|
473
|
-
rescue ArgumentError
|
|
474
|
-
raise ParseError, "Invalid length literal"
|
|
475
|
-
end
|
|
476
|
-
|
|
477
|
-
def assign_pair(hash, key, value)
|
|
478
|
-
hash[key] = value
|
|
479
|
-
end
|
|
480
|
-
|
|
481
|
-
def symbolize_keys(row)
|
|
482
|
-
symbolize_names ? row.transform_keys(&:to_sym) : row
|
|
483
|
-
end
|
|
484
|
-
|
|
485
|
-
def expect!(char)
|
|
486
|
-
skip_ws
|
|
487
|
-
actual = current_char
|
|
488
|
-
raise ParseError, "Expected #{char.inspect}, got #{actual.inspect}" unless actual == char
|
|
489
|
-
@index += 1
|
|
490
|
-
end
|
|
491
|
-
|
|
492
|
-
def skip_ws
|
|
493
|
-
@index += 1 while !eof? && whitespace?(current_char)
|
|
494
|
-
end
|
|
495
|
-
|
|
496
|
-
def whitespace?(char)
|
|
497
|
-
char == " " || char == "\t" || char == "\n" || char == "\r"
|
|
498
|
-
end
|
|
499
|
-
|
|
500
|
-
def eof?
|
|
501
|
-
@index >= @source.length
|
|
502
|
-
end
|
|
503
|
-
|
|
504
|
-
def current_char
|
|
505
|
-
@source[@index]
|
|
506
|
-
end
|
|
507
|
-
|
|
508
|
-
def advance
|
|
509
|
-
@index += 1
|
|
510
|
-
end
|
|
511
|
-
|
|
512
|
-
def key_ahead?(offset)
|
|
513
|
-
idx = offset
|
|
514
|
-
idx += 1 while idx < @source.length && whitespace?(@source[idx])
|
|
515
|
-
start = idx
|
|
516
|
-
while idx < @source.length && safe_key_char?(@source[idx])
|
|
517
|
-
idx += 1
|
|
518
|
-
end
|
|
519
|
-
return false if idx == start
|
|
520
|
-
next_char = @source[idx]
|
|
521
|
-
["(", "[", "="].include?(next_char)
|
|
522
|
-
end
|
|
523
|
-
|
|
524
|
-
def safe_key_char?(char)
|
|
525
|
-
!char.nil? && char.match?(/[0-9A-Za-z_.:-]/)
|
|
526
|
-
end
|
|
527
|
-
|
|
528
|
-
def integer?(token)
|
|
529
|
-
token.match?(/\A-?(?:0|[1-9]\d*)\z/)
|
|
530
|
-
end
|
|
531
|
-
|
|
532
|
-
def float?(token)
|
|
533
|
-
token.match?(/\A-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?\z/)
|
|
534
|
-
end
|
|
535
|
-
|
|
536
|
-
def next_key_index(from_index)
|
|
537
|
-
idx = from_index
|
|
538
|
-
in_string = false
|
|
539
|
-
|
|
540
|
-
while idx < @source.length
|
|
541
|
-
char = @source[idx]
|
|
542
|
-
|
|
543
|
-
if in_string
|
|
544
|
-
if char == '\\'
|
|
545
|
-
idx += 2
|
|
546
|
-
next
|
|
547
|
-
elsif char == '"'
|
|
548
|
-
in_string = false
|
|
549
|
-
idx += 1
|
|
550
|
-
next
|
|
551
|
-
else
|
|
552
|
-
idx += 1
|
|
553
|
-
next
|
|
554
|
-
end
|
|
555
|
-
else
|
|
556
|
-
case char
|
|
557
|
-
when '"'
|
|
558
|
-
in_string = true
|
|
559
|
-
idx += 1
|
|
560
|
-
next
|
|
561
|
-
else
|
|
562
|
-
if safe_key_char?(char)
|
|
563
|
-
start = idx
|
|
564
|
-
idx += 1 while idx < @source.length && safe_key_char?(@source[idx])
|
|
565
|
-
next_char = @source[idx]
|
|
566
|
-
if start > from_index && ["(", "[", "="].include?(next_char)
|
|
567
|
-
return start
|
|
568
|
-
end
|
|
569
|
-
idx = start + 1
|
|
570
|
-
next
|
|
571
|
-
end
|
|
572
|
-
idx += 1
|
|
573
|
-
end
|
|
574
|
-
end
|
|
575
|
-
end
|
|
576
|
-
|
|
577
|
-
nil
|
|
578
|
-
end
|
|
579
|
-
end
|
|
580
25
|
end
|
|
581
26
|
|
data/sig/cton.rbs
CHANGED
|
@@ -4,8 +4,80 @@ module Cton
|
|
|
4
4
|
class EncodeError < Error; end
|
|
5
5
|
class ParseError < Error; end
|
|
6
6
|
|
|
7
|
-
def self.dump: (untyped, ?Hash[Symbol, untyped]) -> String
|
|
8
|
-
def self.generate: (untyped, ?Hash[Symbol, untyped]) -> String
|
|
9
|
-
def self.load: (String, ?symbolize_names: bool) -> untyped
|
|
10
|
-
def self.parse: (String, ?symbolize_names: bool) -> untyped
|
|
7
|
+
def self.dump: (untyped payload, ?Hash[Symbol, untyped] options) -> String
|
|
8
|
+
def self.generate: (untyped payload, ?Hash[Symbol, untyped] options) -> String
|
|
9
|
+
def self.load: (String cton_string, ?symbolize_names: bool) -> untyped
|
|
10
|
+
def self.parse: (String cton_string, ?symbolize_names: bool) -> untyped
|
|
11
|
+
|
|
12
|
+
class Encoder
|
|
13
|
+
SAFE_TOKEN: Regexp
|
|
14
|
+
NUMERIC_TOKEN: Regexp
|
|
15
|
+
RESERVED_LITERALS: Array[String]
|
|
16
|
+
FLOAT_DECIMAL_PRECISION: Integer
|
|
17
|
+
|
|
18
|
+
def initialize: (?separator: String) -> void
|
|
19
|
+
def encode: (untyped payload) -> String
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
attr_reader separator: String
|
|
23
|
+
attr_reader io: StringIO
|
|
24
|
+
|
|
25
|
+
def encode_root: (untyped value) -> void
|
|
26
|
+
def encode_top_level_pair: (String | Symbol key, untyped value) -> void
|
|
27
|
+
def encode_value: (untyped value, context: Symbol) -> void
|
|
28
|
+
def encode_object: (Hash[untyped, untyped] hash) -> void
|
|
29
|
+
def encode_array: (Array[untyped] list) -> void
|
|
30
|
+
def encode_table: (Array[Hash[untyped, untyped]] rows) -> void
|
|
31
|
+
def encode_scalar_list: (Array[untyped] list) -> void
|
|
32
|
+
def encode_mixed_list: (Array[untyped] list) -> void
|
|
33
|
+
def encode_scalar: (untyped value) -> void
|
|
34
|
+
def encode_string: (String value) -> void
|
|
35
|
+
def format_number: (Numeric value) -> String
|
|
36
|
+
def normalize_decimal_string: (String string) -> String
|
|
37
|
+
def zero_string?: (String string) -> bool
|
|
38
|
+
def float_decimal_string: (Numeric value) -> String
|
|
39
|
+
def format_key: (String | Symbol key) -> String
|
|
40
|
+
def string_needs_quotes?: (String value) -> bool
|
|
41
|
+
def numeric_like?: (String value) -> bool
|
|
42
|
+
def quote_string: (String value) -> String
|
|
43
|
+
def escape_string: (String value) -> String
|
|
44
|
+
def scalar?: (untyped value) -> bool
|
|
45
|
+
def table_candidate?: (Array[untyped] rows) -> bool
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class Decoder
|
|
49
|
+
TERMINATORS: Array[String]
|
|
50
|
+
|
|
51
|
+
def initialize: (?symbolize_names: bool) -> void
|
|
52
|
+
def decode: (String cton) -> untyped
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
attr_reader symbolize_names: bool
|
|
56
|
+
attr_reader scanner: StringScanner
|
|
57
|
+
|
|
58
|
+
def parse_document: -> Hash[untyped, untyped]
|
|
59
|
+
def parse_value_for_key: -> untyped
|
|
60
|
+
def parse_object: -> Hash[untyped, untyped]
|
|
61
|
+
def parse_array: -> Array[untyped]
|
|
62
|
+
def parse_header: -> Array[String | Symbol]
|
|
63
|
+
def parse_table_rows: (Integer length, Array[String | Symbol] header) -> Array[Hash[untyped, untyped]]
|
|
64
|
+
def parse_array_elements: (Integer length) -> Array[untyped]
|
|
65
|
+
def parse_value: (?allow_key_boundary: bool) -> untyped
|
|
66
|
+
def parse_scalar: (?allow_key_boundary: bool) -> untyped
|
|
67
|
+
def scan_until_terminator: -> String?
|
|
68
|
+
def scan_until_boundary_or_terminator: -> String?
|
|
69
|
+
def find_key_boundary: (Integer from_index) -> Integer?
|
|
70
|
+
def convert_scalar: (String token) -> untyped
|
|
71
|
+
def parse_string: -> String
|
|
72
|
+
def parse_key_name: -> (String | Symbol)
|
|
73
|
+
def parse_integer_literal: -> Integer
|
|
74
|
+
def symbolize_keys: (Hash[untyped, untyped] row) -> Hash[untyped, untyped]
|
|
75
|
+
def expect!: (String char) -> void
|
|
76
|
+
def skip_ws: -> void
|
|
77
|
+
def whitespace?: (String char) -> bool
|
|
78
|
+
def key_ahead?: -> bool
|
|
79
|
+
def safe_key_char?: (String? char) -> bool
|
|
80
|
+
def integer?: (String token) -> bool
|
|
81
|
+
def float?: (String token) -> bool
|
|
82
|
+
end
|
|
11
83
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cton
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Davide Santangelo
|
|
@@ -26,6 +26,8 @@ files:
|
|
|
26
26
|
- README.md
|
|
27
27
|
- Rakefile
|
|
28
28
|
- lib/cton.rb
|
|
29
|
+
- lib/cton/decoder.rb
|
|
30
|
+
- lib/cton/encoder.rb
|
|
29
31
|
- lib/cton/version.rb
|
|
30
32
|
- sig/cton.rbs
|
|
31
33
|
homepage: https://github.com/davidesantangelo/cton
|