perfect_toml 0.9.0 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4bdd721add5b9cda6201f1168e544ef143fe49057caadca19f84ce2415ef6511
4
- data.tar.gz: 96cd13ab0484dbf0e2491441149e9df9d8312f16b64676ebc132610f387c7d28
3
+ metadata.gz: a29524e28ddec2096db536394364d7c17049e4e27ad06ddbd0fc26af198092ae
4
+ data.tar.gz: 2efab92700117fdedbc7d77f88246fc4037569f1dfb245bf176dac56bf368b05
5
5
  SHA512:
6
- metadata.gz: c3b46cd8667c04e06e41c9b8fc9f9c902a7ffc8f79ed07d40ba187d845702062a78eed671ed3062b8cdb5ea67cd3e011af5caf60ed18c46f275364783dbf350f
7
- data.tar.gz: dd618bfdd46be20cf703c79422e5f11f989ccc0dd8558ccd40e51ef26c037f3a0612acf22b8d7878417c74731c04d58f2268917b716e8911358f076004a9b03e
6
+ metadata.gz: 4ef80224ba391be10d0ba7b096e9d42e113699a08c0c0f2b9cf5bd12a562ca0f8fb8203d5f2ca54e4987805abf245e25fedb8917aa86279f6b52962cdda19a3c
7
+ data.tar.gz: 33182e8c0e71d39934dc63883486d1c5312fb0f9922ec3fada66dec7f5b796d53cdad2f1517b9c62c4ffe8ba2eab8b131a8a9b2584394d8b2922a1de639a02e3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # PerfectTOML Changelog
2
2
 
3
+ ## 0.9.1 / 2026-02-26
4
+
5
+ * Add preliminary support for TOML 1.1.0 (`PerfectTOML.parse(str, version: "1.1")`)
6
+ * Improve the performance of datetime parsing
7
+ * Encode empty arrays correctly
8
+ * Prevent frozen-string-literal warnings
9
+ * Drop Ruby 2.7 and 3.0 support
10
+
3
11
  ## 0.9.0 / 2022-07-17
4
12
 
5
13
  * First release.
data/Gemfile CHANGED
@@ -7,3 +7,7 @@ gem "rake", "~> 13.0"
7
7
  gem "test-unit", "~> 3.0"
8
8
 
9
9
  gem "simplecov", "~> 0.21.2"
10
+
11
+ gem "json", ">= 2.7.2"
12
+
13
+ gem "rdoc", "~> 6.13"
data/README.md CHANGED
@@ -4,7 +4,7 @@ Yet another [TOML](https://github.com/toml-lang/toml) parser and generator.
4
4
 
5
5
  Features:
6
6
 
7
- * Fully compliant with [TOML v1.0.0](https://toml.io/en/v1.0.0). It passes [BurntSushi/toml-test](https://github.com/BurntSushi/toml-test).
7
+ * Fully compliant with [TOML v1.0.0](https://toml.io/en/v1.0.0). It passes [toml-lang/toml-test](https://github.com/toml-lang/toml-test).
8
8
  * Faster than existing TOML parsers for Ruby. See [Benchmark](#benchmark).
9
9
  * Single-file, plain old Ruby script without any dependencies: [perfect_toml.rb](https://github.com/mame/perfect_toml/blob/master/lib/perfect_toml.rb).
10
10
 
@@ -59,7 +59,7 @@ PerfectTOML provides dedicated classes, respectively,
59
59
  ```ruby
60
60
  require "perfect_toml"
61
61
 
62
- p PerfectTOML.parse("local-date = 1970-01-01)
62
+ p PerfectTOML.parse("local-date = 1970-01-01")
63
63
  #=> { "local-date" => #<PerfectTOML::LocalDate 1970-01-01> }
64
64
  ```
65
65
 
@@ -74,7 +74,7 @@ require "toml-rb"
74
74
  require "tomlrb"
75
75
 
76
76
  # https://raw.githubusercontent.com/toml-lang/toml/v0.5.0/examples/example-v0.4.0.toml
77
- toml = File.read("example-v0.4.0.toml")
77
+ data = File.read("example-v0.4.0.toml")
78
78
 
79
79
  Benchmark.ips do |x|
80
80
  x.report("emancu/toml-rb") { TomlRB.parse(data) }
data/Rakefile CHANGED
@@ -8,32 +8,58 @@ Rake::TestTask.new(:core_test) do |t|
8
8
  t.test_files = FileList["test/**/*_test.rb"]
9
9
  end
10
10
 
11
- TOML_TEST = "./toml-test-v1.2.0-linux-amd64"
11
+ TOML_TEST = "./toml-test-v2.1.0-linux-amd64"
12
12
 
13
13
  file TOML_TEST do
14
14
  require "open-uri"
15
15
  require "zlib"
16
- URI.open("https://github.com/BurntSushi/toml-test/releases/download/v1.2.0/toml-test-v1.2.0-linux-amd64.gz", "rb") do |f|
16
+ URI.open("https://github.com/toml-lang/toml-test/releases/download/v2.1.0/toml-test-v2.1.0-linux-amd64.gz", "rb") do |f|
17
17
  File.binwrite(TOML_TEST, Zlib::GzipReader.new(f).read)
18
18
  File.chmod(0o755, TOML_TEST)
19
19
  end
20
20
  end
21
21
 
22
- task :toml_decoder_test => TOML_TEST do
23
- sh "./toml-test-v1.2.0-linux-amd64", "./tool/decoder.rb"
22
+ task :toml_decoder_1_0_test => TOML_TEST do
23
+ ENV["TOML_DECODER_VERSION"] = "1.0"
24
+ sh TOML_TEST, "test", "-toml", "1.0", "-decoder", "./tool/decoder.rb"
24
25
  end
25
26
 
26
- task :toml_encoder_test => TOML_TEST do
27
+ task :toml_decoder_1_1_test => TOML_TEST do
28
+ ENV["TOML_DECODER_VERSION"] = "1.1"
29
+ sh TOML_TEST, "test", "-toml", "1.1", "-decoder", "./tool/decoder.rb"
30
+ end
31
+
32
+ task :toml_encoder_1_0_test => TOML_TEST do
33
+ ENV["TOML_DECODER_VERSION"] = "1.0"
34
+ ["0000", "1000", "0010", "0001", "0011"].each do |mode|
35
+ ENV["TOML_ENCODER_USE_DOT"] = mode[0]
36
+ ENV["TOML_ENCODER_SORT_KEYS"] = mode[1]
37
+ ENV["TOML_ENCODER_USE_LITERAL_STRING"] = mode[2]
38
+ ENV["TOML_ENCODER_USE_MULTILINE_STRING"] = mode[3]
39
+ ENV.delete("BURNTSUSHI_TOML_110") # https://github.com/toml-lang/toml-test/issues/173
40
+ sh TOML_TEST, "test", "-toml", "1.0", "-decoder", "./tool/decoder.rb", "--encoder", "./tool/encoder.rb"
41
+ end
42
+ end
43
+
44
+ task :toml_encoder_1_1_test => TOML_TEST do
45
+ ENV["TOML_DECODER_VERSION"] = "1.1"
27
46
  ["0000", "1000", "0010", "0001", "0011"].each do |mode|
28
47
  ENV["TOML_ENCODER_USE_DOT"] = mode[0]
29
48
  ENV["TOML_ENCODER_SORT_KEYS"] = mode[1]
30
49
  ENV["TOML_ENCODER_USE_LITERAL_STRING"] = mode[2]
31
50
  ENV["TOML_ENCODER_USE_MULTILINE_STRING"] = mode[3]
32
- sh "./toml-test-v1.2.0-linux-amd64", "./tool/encoder.rb", "--encoder", "-skip", "valid/string/multiline-quotes"
51
+ ENV["BURNTSUSHI_TOML_110"] = "1" # https://github.com/toml-lang/toml-test/issues/173
52
+ sh TOML_TEST, "test", "-toml", "1.1", "-decoder", "./tool/decoder.rb", "--encoder", "./tool/encoder.rb"
33
53
  end
34
54
  end
35
55
 
36
- task :test => [:core_test, :toml_decoder_test, :toml_encoder_test]
56
+ task :test => [
57
+ :core_test,
58
+ :toml_decoder_1_0_test,
59
+ :toml_decoder_1_1_test,
60
+ :toml_encoder_1_0_test,
61
+ :toml_encoder_1_1_test,
62
+ ]
37
63
 
38
64
  task default: :test
39
65
 
data/lib/perfect_toml.rb CHANGED
@@ -1,17 +1,17 @@
1
1
  # MIT License
2
- #
2
+ #
3
3
  # Copyright (c) 2022 Yusuke Endoh
4
- #
4
+ #
5
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  # of this software and associated documentation files (the "Software"), to deal
7
7
  # in the Software without restriction, including without limitation the rights
8
8
  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
9
  # copies of the Software, and to permit persons to whom the Software is
10
10
  # furnished to do so, subject to the following conditions:
11
- #
11
+ #
12
12
  # The above copyright notice and this permission notice shall be included in all
13
13
  # copies or substantial portions of the Software.
14
- #
14
+ #
15
15
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
16
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
17
  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -23,7 +23,7 @@
23
23
  require "strscan"
24
24
 
25
25
  module PerfectTOML
26
- VERSION = "0.9.0"
26
+ VERSION = "0.9.1"
27
27
 
28
28
  class LocalDateTimeBase
29
29
  def to_inline_toml
@@ -62,16 +62,18 @@ module PerfectTOML
62
62
  #
63
63
  # See https://toml.io/en/v1.0.0#local-date-time
64
64
  class LocalDateTime < LocalDateTimeBase
65
- def initialize(year, month, day, hour, min, sec)
66
- @year = year.to_i
67
- @month = month.to_i
68
- @day = day.to_i
69
- @hour = hour.to_i
70
- @min = min.to_i
71
- @sec = Numeric === sec ? sec : sec.include?(".") ? Rational(sec) : sec.to_i
65
+ def initialize(*args)
66
+ @date = args[0].is_a?(LocalDate) ? args.shift : LocalDate.new(args.shift, args.shift, args.shift)
67
+ @time = args[0].is_a?(LocalTime) ? args.shift : LocalTime.new(args.shift, args.shift, args.shift)
68
+ raise ArgumentError, "wrong number of arguments" unless args.empty?
72
69
  end
73
70
 
74
- attr_reader :year, :month, :day, :hour, :min, :sec
71
+ def year = @date.year
72
+ def month = @date.month
73
+ def day = @date.day
74
+ def hour = @time.hour
75
+ def min = @time.min
76
+ def sec = @time.sec
75
77
 
76
78
  # Converts to a Time object with the local timezone.
77
79
  #
@@ -83,7 +85,7 @@ module PerfectTOML
83
85
  # ldt.to_time("UTC") #=> 1970-01-01 02:03:04 UTC
84
86
  # ldt.to_time("+00:00") #=> 1970-01-01 02:03:04 +0000
85
87
  def to_time(zone = nil)
86
- @time = Time.new(@year, @month, @day, @hour, @min, @sec, zone)
88
+ Time.new(@date.year, @date.month, @date.day, @time.hour, @time.min, @time.sec, zone)
87
89
  end
88
90
 
89
91
  # Returns a string representation in RFC 3339 format
@@ -91,9 +93,7 @@ module PerfectTOML
91
93
  # ldt = PerfectTOML::LocalDateTime.new(1970, 1, 1, 2, 3, 4)
92
94
  # ldt.to_s #=> 1970-01-01T02:03:04
93
95
  def to_s
94
- s = "%04d-%02d-%02dT%02d:%02d:%02d" % [@year, @month, @day, @hour, @min, @sec]
95
- s << ("%11.9f" % (@sec - @sec.floor))[1..] unless Integer === @sec
96
- s
96
+ "#{ @date }T#{ @time }"
97
97
  end
98
98
  end
99
99
 
@@ -101,10 +101,15 @@ module PerfectTOML
101
101
  #
102
102
  # See https://toml.io/en/v1.0.0#local-date
103
103
  class LocalDate < LocalDateTimeBase
104
+ DAYS_IN_MONTH = [31, nil, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
105
+
104
106
  def initialize(year, month, day)
105
107
  @year = year.to_i
106
108
  @month = month.to_i
109
+ raise ArgumentError, "month must be in 1..12" unless 1 <= @month && @month <= 12
107
110
  @day = day.to_i
111
+ max_day = DAYS_IN_MONTH[@month - 1] || (@year % 4 == 0 && (@year % 100 != 0 || @year % 400 == 0) ? 29 : 28)
112
+ raise ArgumentError, "day must be in 1..#{ max_day }" unless 1 <= @day && @day <= max_day
108
113
  end
109
114
 
110
115
  attr_reader :year, :month, :day
@@ -132,15 +137,17 @@ module PerfectTOML
132
137
  end
133
138
  end
134
139
 
135
-
136
140
  # Represents TOML's Local Time
137
141
  #
138
142
  # See https://toml.io/en/v1.0.0#local-time
139
143
  class LocalTime < LocalDateTimeBase
140
144
  def initialize(hour, min, sec)
141
145
  @hour = hour.to_i
146
+ raise ArgumentError, "hour must be in 0..23" unless 0 <= @hour && @hour <= 23
142
147
  @min = min.to_i
148
+ raise ArgumentError, "min must be in 0..59" unless 0 <= @min && @min <= 59
143
149
  @sec = Numeric === sec ? sec : sec.include?(".") ? Rational(sec) : sec.to_i
150
+ raise ArgumentError, "sec must be in 0..60" unless 0 <= @sec && @sec <= 60
144
151
  end
145
152
 
146
153
  attr_reader :hour, :min, :sec
@@ -172,7 +179,7 @@ module PerfectTOML
172
179
 
173
180
  # call-seq:
174
181
  #
175
- # parse(toml_src, symbolize_names: boolean) -> Object
182
+ # parse(toml_src, version: String, symbolize_names: boolean) -> Object
176
183
  #
177
184
  # Decodes a TOML string.
178
185
  #
@@ -184,6 +191,10 @@ module PerfectTOML
184
191
  # END
185
192
  # PerfectTOML.parse(src) #=> { "foo" => { "bar" => "baz" } }
186
193
  #
194
+ # The keyword `version` specifies the TOML version to parse.
195
+ # Supported versions are "1.0.0" and "1.1.0" (which isn't released yet and may change).
196
+ # Defaults to "1.0.0", but the default will change in future releases.
197
+ #
187
198
  # All keys in the Hash are String by default.
188
199
  # If a keyword `symbolize_names` is specficied as truthy,
189
200
  # all keys in the Hash will be Symbols.
@@ -195,8 +206,14 @@ module PerfectTOML
195
206
  # bar = "baz"
196
207
  # END
197
208
  # PerfectTOML.parse(src, symbolize_names: true) #=> { :key => { :bar => "baz" } }
198
- def self.parse(toml_src, **opts)
199
- Parser.new(toml_src, **opts).parse
209
+ def self.parse(toml_src, version: "1.0.0", symbolize_names: false)
210
+ case version
211
+ when "1.0", "1.0.0" then version = :TOML_1_0_0
212
+ when "1.1", "1.1.0" then version = :TOML_1_1_0
213
+ else
214
+ raise ArgumentError, "unsupported TOML version: #{ version.inspect }"
215
+ end
216
+ Parser.new(toml_src, version, symbolize_names).parse
200
217
  end
201
218
 
202
219
  # call-seq:
@@ -278,7 +295,7 @@ module PerfectTOML
278
295
  # # output:
279
296
  # # a.b.c.d = 42
280
297
  def self.generate(hash, **opts)
281
- out = ""
298
+ out = +""
282
299
  Generator.new(hash, out, **opts).generate
283
300
  out
284
301
  end
@@ -304,10 +321,22 @@ module PerfectTOML
304
321
  class ParseError < StandardError; end
305
322
 
306
323
  class Parser # :nodoc:
307
- def initialize(src, symbolize_names: false)
324
+ def initialize(src, version, symbolize_names)
308
325
  @s = StringScanner.new(src)
309
326
  @symbolize_names = symbolize_names
310
327
  @root_node = @topic_node = Node.new(1, nil)
328
+
329
+ @re_escape_char = version == :TOML_1_0_0 ?
330
+ /([btnmfr"\\])|u([0-9A-Fa-f]{4})|U([0-9A-Fa-f]{8})/ :
331
+ /([btnmfre"\\])|x([0-9A-Fa-f]{2})|u([0-9A-Fa-f]{4})|U([0-9A-Fa-f]{8})/
332
+ @re_multiline_basic_string = version == :TOML_1_0_0 ?
333
+ /[^\x00-\x08\x0b\x0c\x0e-\x1f\x7f"\\]*/ :
334
+ /([^\x00-\x08\x0b-\x1f\x7f"\\]|\r\n)*/
335
+ @re_multiline_literal_string = version == :TOML_1_0_0 ?
336
+ /[^\x00-\x08\x0b\x0c\x0e-\x1f\x7f']*/ :
337
+ /([^\x00-\x08\x0b-\x1f\x7f']|\r\n)*/
338
+ @omitted_seconds = version != :TOML_1_0_0 ? "0" : nil
339
+ @allow_newline_in_inline_table = version != :TOML_1_0_0
311
340
  end
312
341
 
313
342
  def parse
@@ -377,13 +406,13 @@ module PerfectTOML
377
406
  # parsing for strings
378
407
 
379
408
  ESCAPE_CHARS = {
380
- ?b => ?\b, ?t => ?\t, ?n => ?\n, ?f => ?\f, ?r => ?\r, ?" => ?", ?\\ => ?\\
409
+ ?b => ?\b, ?t => ?\t, ?n => ?\n, ?f => ?\f, ?r => ?\r, ?e => ?\e, ?" => ?", ?\\ => ?\\
381
410
  }
382
411
 
383
412
  def parse_escape_char
384
413
  if @s.skip(/\\/)
385
- if @s.skip(/([btnmfr"\\])|u([0-9A-Fa-f]{4})|U([0-9A-Fa-f]{8})/)
386
- @s[1] ? ESCAPE_CHARS[@s[1]] : (@s[2] || @s[3]).hex.chr("UTF-8")
414
+ if @s.skip(@re_escape_char)
415
+ @s[1] ? ESCAPE_CHARS[@s[1]] : (@s[2] || @s[3] || @s[4]).hex.chr("UTF-8")
387
416
  else
388
417
  unterminated_string_error if @s.eos?
389
418
  error "invalid escape character in string: #{ @s.peek(1).dump }"
@@ -395,7 +424,7 @@ module PerfectTOML
395
424
  end
396
425
 
397
426
  def parse_basic_string
398
- str = ""
427
+ str = +""
399
428
  while true
400
429
  str << @s.scan(/[^\x00-\x08\x0a-\x1f\x7f"\\]*/)
401
430
  return str if @s.skip(/"/)
@@ -407,9 +436,9 @@ module PerfectTOML
407
436
  # skip a newline
408
437
  @s.skip(/\n|\r\n/)
409
438
 
410
- str = ""
439
+ str = +""
411
440
  while true
412
- str << @s.scan(/[^\x00-\x08\x0b\x0c\x0e-\x1f\x7f"\\]*/)
441
+ str << @s.scan(@re_multiline_basic_string)
413
442
  delimiter = @s.skip(/"{1,5}/)
414
443
  if delimiter
415
444
  str << "\"" * (delimiter % 3)
@@ -432,9 +461,9 @@ module PerfectTOML
432
461
  # skip a newline
433
462
  @s.skip(/\n|\r\n/)
434
463
 
435
- str = ""
464
+ str = +""
436
465
  while true
437
- str << @s.scan(/[^\x00-\x08\x0b\x0c\x0e-\x1f\x7f']*/)
466
+ str << @s.scan(@re_multiline_literal_string)
438
467
  if delimiter = @s.skip(/'{1,5}/)
439
468
  str << "'" * (delimiter % 3)
440
469
  return str if delimiter >= 3
@@ -448,33 +477,23 @@ module PerfectTOML
448
477
  # parsing for date/time
449
478
 
450
479
  def parse_datetime(preread_len)
451
- str = @s[0]
452
- pos = @s.pos - preread_len
453
- year, month, day = @s[1], @s[2], @s[3]
454
- if @s.skip(/[T ](\d{2}):(\d{2}):(\d{2}(?:\.\d+)?)/i)
455
- str << @s[0]
456
- hour, min, sec = @s[1], @s[2], @s[3]
457
- raise ArgumentError unless (0..23).cover?(hour.to_i)
458
- zone = @s.scan(/(Z)|[-+]\d{2}:\d{2}/i)
459
- time = Time.new(year, month, day, hour, min, sec.to_r, @s[1] ? "UTC" : zone)
460
- if zone
461
- time
462
- else
463
- LocalDateTime.new(year, month, day, hour, min, sec)
464
- end
480
+ date = LocalDate.new(@s[1], @s[2], @s[3])
481
+ if @s[4]
482
+ time = LocalTime.new(@s[4], @s[5], @s[6] || @omitted_seconds || error("seconds field is required"))
483
+ datetime = LocalDateTime.new(date, time)
484
+ zone = @s[7]
485
+ datetime = datetime.to_time(zone == "z" ? "Z" : zone) if zone
486
+ datetime
465
487
  else
466
- Time.new(year, month, day, 0, 0, 0, "Z") # parse check
467
- LocalDate.new(year, month, day)
488
+ date
468
489
  end
469
490
  rescue ArgumentError
470
- @s.pos = pos
471
- error "failed to parse date or datetime \"#{ str }\""
491
+ @s.pos -= preread_len
492
+ error "failed to parse date or datetime \"#{ @s[0] }\""
472
493
  end
473
494
 
474
495
  def parse_time(preread_len)
475
- hour, min, sec = @s[1], @s[2], @s[3]
476
- Time.new(1970, 1, 1, hour, min, sec.to_r, "Z") # parse check
477
- LocalTime.new(hour, min, sec)
496
+ LocalTime.new(@s[1], @s[2], @s[3] || @omitted_seconds || error("seconds field is required"))
478
497
  rescue ArgumentError
479
498
  @s.pos -= preread_len
480
499
  error "failed to parse time \"#{ @s[0] }\""
@@ -496,8 +515,16 @@ module PerfectTOML
496
515
  ary
497
516
  end
498
517
 
518
+ def skip_inline_table_spaces
519
+ if @allow_newline_in_inline_table
520
+ skip_spaces
521
+ else
522
+ @s.skip(/[\t ]*/)
523
+ end
524
+ end
525
+
499
526
  def parse_inline_table
500
- @s.skip(/[\t ]*/)
527
+ skip_inline_table_spaces
501
528
  if @s.skip(/\}/)
502
529
  {}
503
530
  else
@@ -505,13 +532,14 @@ module PerfectTOML
505
532
  while true
506
533
  @keys_start_pos = @s.pos
507
534
  keys = parse_keys
508
- @s.skip(/[\t ]*/)
509
- unexpected_error unless @s.skip(/=[\t ]*/)
535
+ skip_inline_table_spaces
536
+ unexpected_error unless @s.skip(/=/)
537
+ skip_inline_table_spaces
510
538
  define_value(tmp_node, keys)
511
- @s.skip(/[\t ]*/)
512
- next if @s.skip(/,[\t ]*/)
513
- break if @s.skip(/\}/)
514
- unexpected_error
539
+ skip_inline_table_spaces
540
+ skip_inline_table_spaces if comma_seen = @s.skip(/,/)
541
+ break if (@allow_newline_in_inline_table || !comma_seen) && @s.skip(/\}/)
542
+ unexpected_error unless comma_seen
515
543
  end
516
544
  tmp_node.table
517
545
  end
@@ -547,9 +575,9 @@ module PerfectTOML
547
575
  @s.skip(/""/) ? parse_multiline_basic_string : parse_basic_string
548
576
  when @s.skip(/'/)
549
577
  @s.skip(/''/) ? parse_multiline_literal_string : parse_literal_string
550
- when len = @s.skip(/(-?\d{4})-(\d{2})-(\d{2})/)
578
+ when len = @s.skip(/(-?\d{4})-(\d{2})-(\d{2})(?:[tT ](\d{2}):(\d{2})(?::(\d{2}(?:\.\d+)?))?([zZ]|[-+]\d{2}:\d{2})?)?/)
551
579
  parse_datetime(len)
552
- when len = @s.skip(/(\d{2}):(\d{2}):(\d{2}(?:\.\d+)?)/)
580
+ when len = @s.skip(/(\d{2}):(\d{2})(?::(\d{2}(?:\.\d+)?))?/)
553
581
  parse_time(len)
554
582
  when val = @s.scan(/0x\h(?:_?\h)*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*/)
555
583
  Integer(val)
@@ -846,7 +874,7 @@ module PerfectTOML
846
874
  break unless v
847
875
  v
848
876
  end
849
- if ary
877
+ if ary && !ary.empty?
850
878
  children << [:array, k, ary]
851
879
  else
852
880
  values << [k, val]
data/perfect_toml.gemspec CHANGED
@@ -15,7 +15,7 @@ It is fully compliant with TOML v1.0.0, and faster than existing TOML parsers fo
15
15
  END
16
16
  spec.homepage = "https://github.com/mame/perfect_toml"
17
17
  spec.license = "MIT"
18
- spec.required_ruby_version = ">= 2.7.0"
18
+ spec.required_ruby_version = ">= 3.1.0"
19
19
 
20
20
  spec.metadata["homepage_uri"] = spec.homepage
21
21
  spec.metadata["source_code_uri"] = "https://github.com/mame/perfect_toml"
data/sig/perfect_toml.rbs CHANGED
@@ -4,9 +4,9 @@ module PerfectTOML
4
4
  def self.parse: (String toml_src, symbolize_names: bool) -> untyped
5
5
  def self.load_file: (String filename, symbolize_names: bool) -> untyped
6
6
  | (IO io, symbolize_names: bool) -> untyped
7
- def self.generate: (
8
- def self.save_file: (String filename, untyped data, sort_keys: boolean, use_literal_string: boolean, use_multiline_string: boolean, use_dot: boolean) -> String) -> void
9
- | (IO io, untyped data, sort_keys: boolean, use_literal_string: boolean, use_multiline_string: boolean, use_dot: boolean) -> String) -> void
7
+ def self.generate: (untyped data, sort_keys: boolean, use_literal_string: boolean, use_multiline_string: boolean, use_dot: boolean) -> String
8
+ def self.save_file: (String filename, untyped data, sort_keys: boolean, use_literal_string: boolean, use_multiline_string: boolean, use_dot: boolean) -> void
9
+ | (IO io, untyped data, sort_keys: boolean, use_literal_string: boolean, use_multiline_string: boolean, use_dot: boolean) -> void
10
10
 
11
11
  class LocalDateTime
12
12
  def initialize: (untyped year, untyped month, untyped day, untyped hour, untyped min, untyped sec) -> void
data/tool/decoder.rb CHANGED
@@ -1,12 +1,15 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # usage: TOML_DECODER_VERSION=1.0 /path/to/toml-test-v2.1.0-linux-amd64 test -toml 1.0 -decoder tool/decoder.rb
4
+ # usage: TOML_DECODER_VERSION=1.1 /path/to/toml-test-v2.1.0-linux-amd64 test -toml 1.1 -decoder tool/decoder.rb
5
+
3
6
  require_relative "../lib/perfect_toml"
4
7
  require "json"
5
8
 
6
- def convert(toml)
9
+ def toml_to_json(toml)
7
10
  case toml
8
- when Hash then toml.to_h {|k, v| [k, convert(v)] }
9
- when Array then toml.map {|v| convert(v) }
11
+ when Hash then toml.to_h {|k, v| [k, toml_to_json(v)] }
12
+ when Array then toml.map {|v| toml_to_json(v) }
10
13
  when String then { "type" => "string", "value" => toml }
11
14
  when Integer then { "type" => "integer", "value" => toml.to_s }
12
15
  when Float
@@ -32,4 +35,7 @@ def convert(toml)
32
35
  end
33
36
  end
34
37
 
35
- puts JSON.generate(convert(PerfectTOML.parse($stdin.read)))
38
+ puts JSON.generate(toml_to_json(PerfectTOML.parse(
39
+ $stdin.read.force_encoding("UTF-8"),
40
+ version: ENV.fetch("TOML_DECODER_VERSION", "1.0.0")
41
+ )))
data/tool/encoder.rb CHANGED
@@ -1,17 +1,20 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # usage: TOML_DECODER_VERSION=1.0 /path/to/toml-test-v2.1.0-linux-amd64 test -toml 1.0 -decoder tool/decoder.rb -encoder tool/encoder.rb
4
+ # usage: TOML_DECODER_VERSION=1.1 /path/to/toml-test-v2.1.0-linux-amd64 test -toml 1.1 -decoder tool/decoder.rb -encoder tool/encoder.rb
5
+
3
6
  require_relative "../lib/perfect_toml"
4
7
  require "json"
5
8
  require "time"
6
9
 
7
- def convert(json)
8
- return json.map {|v| convert(v) } if Array === json
10
+ def json_to_toml(json)
11
+ return json.map {|v| json_to_toml(v) } if Array === json
9
12
  if json.key?("type")
10
13
  type, val = json["type"], json["value"]
11
14
  case type
12
15
  when "integer" then val.to_i
13
16
  when "float"
14
- case val
17
+ case val.downcase
15
18
  when "inf", "+inf" then Float::INFINITY
16
19
  when "-inf" then -Float::INFINITY
17
20
  when "nan" then -Float::NAN
@@ -31,17 +34,17 @@ def convert(json)
31
34
  raise if val !~ /\A(\d{2}):(\d{2}):(\d{2}(?:\.\d+)?)\z/
32
35
  PerfectTOML::LocalTime.new($1, $2, $3)
33
36
  else
34
- raise "unknown type: %p" % type
37
+ json.to_h {|k, v| [k, json_to_toml(v)] }
35
38
  end
36
39
  else
37
- json.to_h {|k, v| [k, convert(v)] }
40
+ json.to_h {|k, v| [k, json_to_toml(v)] }
38
41
  end
39
42
  end
40
43
 
41
- opts = {
44
+ puts PerfectTOML.generate(
45
+ json_to_toml(JSON.parse($stdin.read.force_encoding("UTF-8"))),
42
46
  use_dot: ENV["TOML_ENCODER_USE_DOT"] == "1",
43
47
  sort_keys: ENV["TOML_ENCODER_SORT_KEYS"] == "1",
44
48
  use_literal_string: ENV["TOML_ENCODER_USE_LITERAL_STRING"] == "1",
45
49
  use_multiline_string: ENV["TOML_ENCODER_USE_MULTILINE_STRING"] == "1",
46
- }
47
- puts PerfectTOML.generate(convert(JSON.parse($stdin.read)), **opts)
50
+ )
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perfect_toml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yusuke Endoh
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2022-07-17 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: |
14
13
  PerfectTOML is yet another TOML parser.
@@ -37,7 +36,6 @@ metadata:
37
36
  homepage_uri: https://github.com/mame/perfect_toml
38
37
  source_code_uri: https://github.com/mame/perfect_toml
39
38
  changelog_uri: https://github.com/mame/perfect_toml/blob/main/CHANGELOG.md
40
- post_install_message:
41
39
  rdoc_options: []
42
40
  require_paths:
43
41
  - lib
@@ -45,15 +43,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
45
43
  requirements:
46
44
  - - ">="
47
45
  - !ruby/object:Gem::Version
48
- version: 2.7.0
46
+ version: 3.1.0
49
47
  required_rubygems_version: !ruby/object:Gem::Requirement
50
48
  requirements:
51
49
  - - ">="
52
50
  - !ruby/object:Gem::Version
53
51
  version: '0'
54
52
  requirements: []
55
- rubygems_version: 3.3.7
56
- signing_key:
53
+ rubygems_version: 4.1.0.dev
57
54
  specification_version: 4
58
55
  summary: A fast TOML parser gem fully compliant with TOML v1.0.0
59
56
  test_files: []