toml-rb 2.1.1 → 4.2.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: ea53f790d0b22319d57007c11b9d585bce92d0cbd1f97a7340fec49d03804271
4
- data.tar.gz: 1e2e02260452b5dbd94eeeb9268116e64fed6d7f974b99f853d2cdd28cbd8fa7
3
+ metadata.gz: 6d198202b34da88436ce3d0e3cb8841bc1a6ff433a0d9397303c0870c6fed1d5
4
+ data.tar.gz: 916c15730aebc6974a1e7fd04e7c793a3b1321312657f370cae8c9c0a63e6f54
5
5
  SHA512:
6
- metadata.gz: aaad1b4cd6379c6b1a66ce385b442d3fa1c068b985ba374d6c526bbd441b0c1fd4014c12eec523b2ca745b00d43e943c470fef3fcd79c132cc76882f4c2f87c2
7
- data.tar.gz: e979b29743c71fe8ebb48afcbe1bc70129ed8a5f8df911bf812663af9983f4fee31388b2b4d030df0b8413b26832815d313c080b9c3e3d9e362414d47f9904bd
6
+ metadata.gz: acf22dc79ffa1f4b4650e1979eaf7b3d3a04bc894c68e752008e87dbdf695d5ab4ad2c4e835b8d7a5b0aab151c802cd6cfc403ecd361a2cad125c32cf2cb42b3
7
+ data.tar.gz: 1bdedac516138288dcb97ce4cf59153d03d5fdc8f48a93eca7afbd3ab2931f9ca1dd73eb104decca1e1a6bc7dbc3771d575cfe6a81429e3590e3f41d4048c959
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ![TOML Logo](https://github.com/toml-lang/toml/blob/master/logos/toml-100.png)
1
+ ![TOML Logo](https://github.com/toml-lang/toml/blob/main/logos/toml-100.png)
2
2
 
3
3
 
4
4
  toml-rb
@@ -11,7 +11,7 @@ toml-rb
11
11
 
12
12
  A [TOML](https://github.com/toml-lang/toml) parser using [Citrus](http://mjackson.github.io/citrus) library.
13
13
 
14
- TOML specs supported: `0.5.0`
14
+ TOML specs supported: `1.0.0`
15
15
 
16
16
  Installation
17
17
  ------------
@@ -68,6 +68,22 @@ TomlRB.dump(hash)
68
68
  # => "title = \"wow!\"\n[awesome]\nothers = false\nyou = true\n"
69
69
  ```
70
70
 
71
+ Conformance
72
+ -----------
73
+
74
+ `toml-rb` is exercised against the official
75
+ [toml-test](https://github.com/toml-lang/toml-test) conformance suite for both
76
+ TOML 1.0.0 and TOML 1.1.0. The harness lives in:
77
+
78
+ - `tool/toml-test-decoder` — toml-test decoder shim.
79
+ - `.github/workflows/conformance.yml` — CI workflow (per-version matrix).
80
+ - `.github/toml-test-skip-1.0.txt` and `.github/toml-test-skip-1.1.txt` —
81
+ skip-lists of currently-failing tests.
82
+
83
+ The skip-lists are a living gap report: every entry is a known limitation in
84
+ the current parser. The goal for v5.0.0 is **empty skip-lists** — full
85
+ conformance against both spec versions.
86
+
71
87
  Contributing
72
88
  ------------
73
89
 
@@ -4,6 +4,18 @@ require "date"
4
4
 
5
5
  module TomlRB
6
6
  class Dumper
7
+ # TOML basic strings only allow these short escapes; every other control
8
+ # character must be written as \uXXXX (TOML 1.0.0 spec).
9
+ BASIC_ESCAPES = {
10
+ "\\" => "\\\\",
11
+ "\"" => "\\\"",
12
+ "\b" => "\\b",
13
+ "\t" => "\\t",
14
+ "\n" => "\\n",
15
+ "\f" => "\\f",
16
+ "\r" => "\\r"
17
+ }.freeze
18
+
7
19
  attr_reader :toml_str
8
20
 
9
21
  def initialize(hash)
@@ -93,21 +105,43 @@ module TomlRB
93
105
  obj.strftime("%Y-%m-%dT%H:%M:%SZ")
94
106
  elsif obj.is_a?(Date)
95
107
  obj.strftime("%Y-%m-%d")
96
- elsif obj.is_a? Regexp
108
+ elsif obj.is_a?(Regexp)
97
109
  obj.inspect.inspect
98
- elsif obj.is_a? String
99
- obj.inspect.gsub(/\\(#[$@{])/, '\1')
110
+ elsif obj.is_a?(String)
111
+ escape_string(obj)
112
+ elsif obj.is_a?(Array)
113
+ "[" + obj.map(&method(:to_toml)).join(", ") + "]"
114
+ elsif obj.is_a?(Float) && (obj.nan? || obj.infinite?)
115
+ # Ruby renders these as Infinity/-Infinity/NaN, which are invalid TOML.
116
+ if obj.nan?
117
+ "nan"
118
+ elsif obj.negative?
119
+ "-inf"
120
+ else
121
+ "inf"
122
+ end
100
123
  else
101
124
  obj.inspect
102
125
  end
103
126
  end
104
127
 
105
128
  def bare_key?(key)
106
- !!key.to_s.match(/^[a-zA-Z0-9_-]*$/)
129
+ !!key.to_s.match(/\A[a-zA-Z0-9_-]+\z/)
107
130
  end
108
131
 
132
+ # Quote and escape a key as a TOML basic string.
109
133
  def quote_key(key)
110
- '"' + key.gsub('"', '\\"') + '"'
134
+ escape_string(key.to_s)
135
+ end
136
+
137
+ # Serialize a Ruby string as a TOML basic string. Ruby's String#inspect
138
+ # emits \a \v \e for 0x07/0x0B/0x1B, which TOML (and this parser) reject.
139
+ def escape_string(str)
140
+ escaped = str.gsub(/[\x00-\x1f\x7f"\\]/) do |char|
141
+ BASIC_ESCAPES[char] || format('\\u%04X', char.ord)
142
+ end
143
+
144
+ "\"#{escaped}\""
111
145
  end
112
146
  end
113
147
  end
@@ -11,7 +11,7 @@ module TomlRB
11
11
 
12
12
  def initialize(key)
13
13
  @key = key
14
- super "Key #{key.inspect} is defined more than once"
14
+ super("Key #{key.inspect} is defined more than once")
15
15
  end
16
16
  end
17
17
  end
@@ -1,6 +1,5 @@
1
1
  grammar TomlRB::Document
2
2
  include TomlRB::Primitive
3
- include TomlRB::Arrays
4
3
 
5
4
  rule document
6
5
  (comment | table_array | table | keyvalue | line_break)*
@@ -18,35 +17,41 @@ grammar TomlRB::Document
18
17
  (stripped_key '=' space? v:(toml_values) comment? space) <TomlRB::KeyvalueParser>
19
18
  end
20
19
 
21
- rule inline_table
22
- (space? '{' (keyvalue? (',' keyvalue)*)? space? '}' ) <TomlRB::InlineTableParser>
23
- end
20
+ ### Values
24
21
 
25
- rule inline_table_array
26
- (inline_table (space "," array_comments inline_table)*) {
27
- captures[:inline_table].map(&:value).map(&:value)
28
- }
22
+ rule inline_table
23
+ (space? '{' array_comments (keyvalue (array_comments ',' array_comments keyvalue)* (array_comments ',')?)? array_comments '}' ) <TomlRB::InlineTableParser>
29
24
  end
30
25
 
31
26
  rule array
32
27
  ("[" array_comments (array_elements)? space ","? array_comments "]" indent?) <TomlRB::ArrayParser>
33
28
  end
34
29
 
35
- rule array_elements
36
- inline_table_array | float_array | string_array | array_array | integer_array | datetime_array | bool_array
37
- end
38
-
39
30
  rule array_array
40
31
  (array (space "," array_comments array)*) {
41
32
  captures[:array].map(&:value)
42
33
  }
43
34
  end
44
35
 
45
- rule toml_values
46
- primitive | inline_table | array | inline_table_array
36
+ rule array_elements
37
+ (toml_values (indent? "," array_comments toml_values)*) {
38
+ captures[:toml_values].map(&:value).map do |v|
39
+ v.respond_to?(:value) ? v.value : v
40
+ end
41
+ }
47
42
  end
48
43
 
44
+ ### Helpers
45
+
49
46
  rule stripped_key
50
47
  (space? key space?) { captures[:key].first.value }
51
48
  end
49
+
50
+ rule array_comments
51
+ (indent? (comment indent?)*)
52
+ end
53
+
54
+ rule toml_values
55
+ primitive | inline_table | array
56
+ end
52
57
  end
@@ -99,33 +99,33 @@ grammar TomlRB::Primitive
99
99
  end
100
100
 
101
101
  rule exponential_float
102
- ((fractional_float | demical_integer) [eE] demical_integer) { to_str.to_f }
102
+ ((fractional_float | decimal_integer) [eE] decimal_integer) { to_str.to_f }
103
103
  end
104
104
 
105
105
  rule fractional_float
106
- (demical_integer '.' [0-9_]+) {
106
+ (decimal_integer '.' [0-9] ([0-9] | '_' [0-9])*) {
107
107
  to_str.to_f
108
108
  }
109
109
  end
110
110
 
111
111
  rule integer
112
- hexadecimal_integer | octal_integer | binary_integer | demical_integer
112
+ hexadecimal_integer | octal_integer | binary_integer | decimal_integer
113
113
  end
114
114
 
115
115
  rule hexadecimal_integer
116
- ('0x' [a-fA-F0-9_]+) { to_str.to_i(16) }
116
+ ('0x' [a-fA-F0-9] ([a-fA-F0-9] | '_' [a-fA-F0-9])*) { to_str.to_i(16) }
117
117
  end
118
118
 
119
119
  rule octal_integer
120
- ('0o' [0-7_]+) { to_str.to_i(8) }
120
+ ('0o' [0-7] ([0-7] | '_' [0-7])*) { to_str.to_i(8) }
121
121
  end
122
122
 
123
123
  rule binary_integer
124
- ('0b' [01_]+) { to_str.to_i(2) }
124
+ ('0b' [01] ([01] | '_' [01])*) { to_str.to_i(2) }
125
125
  end
126
126
 
127
- rule demical_integer
128
- (sign? [0-9_]+) { to_str.to_i }
127
+ rule decimal_integer
128
+ (sign? [0-9] ([0-9] | '_' [0-9])*) { to_str.to_i }
129
129
  end
130
130
 
131
131
  rule sign
@@ -161,7 +161,7 @@ grammar TomlRB::Primitive
161
161
  end
162
162
 
163
163
  rule quoted_key
164
- basic_string | literal_string
164
+ basic_string | literal_string
165
165
  end
166
166
 
167
167
  rule single_key
@@ -20,7 +20,7 @@ module TomlRB
20
20
 
21
21
  module InlineTableParser
22
22
  def value
23
- TomlRB::InlineTable.new captures[:keyvalue].map(&:value)
23
+ TomlRB::InlineTable.new(captures[:keyvalue].map(&:value))
24
24
  end
25
25
  end
26
26
  end
@@ -10,30 +10,36 @@ module TomlRB
10
10
  @symbolize_keys = false
11
11
  end
12
12
 
13
- def assign(hash, fully_defined_keys, symbolize_keys = false)
13
+ def assign(hash, fully_defined_paths, symbolize_keys = false)
14
14
  @symbolize_keys = symbolize_keys
15
- dotted_keys_str = @dotted_keys.join(".")
16
15
  keys = symbolize_keys ? @dotted_keys.map(&:to_sym) : @dotted_keys
16
+ depth = @dotted_keys.size
17
17
  update = keys.reverse.inject(visit_value(@value)) { |k1, k2| {k2 => k1} }
18
18
 
19
+ parent_inline_table = fully_defined_paths.find { |k| k.size < depth && @dotted_keys.first(k.size) == k }
20
+ fail ValueOverwriteError.new(@dotted_keys.first) if parent_inline_table
21
+
19
22
  if @value.is_a?(InlineTable)
20
- fully_defined_keys << dotted_keys_str
21
- hash.merge!(update) { |key, _, _| fail ValueOverwriteError.new(key) }
22
- elsif fully_defined_keys.find { |k| update.dig(*k) }
23
- hash.merge!(update) { |key, _, _| fail ValueOverwriteError.new(key) }
24
- else
25
- dotted_key_merge(hash, update)
23
+ child_keys_exist = fully_defined_paths.find { |k| k.size > depth && k.first(depth) == @dotted_keys }
24
+ fail ValueOverwriteError.new(@dotted_keys.first) if child_keys_exist
25
+
26
+ existing_hash = hash.dig(*keys)
27
+ fail ValueOverwriteError.new(@dotted_keys.first) if existing_hash.is_a?(Hash) && !existing_hash.empty?
28
+
29
+ fully_defined_paths << @dotted_keys
26
30
  end
31
+
32
+ dotted_key_merge(hash, update)
27
33
  end
28
34
 
29
35
  def dotted_key_merge(hash, update)
30
- hash.merge!(update) { |key, old, new|
36
+ hash.merge!(update) do |key, old, new|
31
37
  if old.is_a?(Hash) && new.is_a?(Hash)
32
38
  dotted_key_merge(old, new)
33
39
  else
34
40
  fail ValueOverwriteError.new(key)
35
41
  end
36
- }
42
+ end
37
43
  end
38
44
 
39
45
  def accept_visitor(parser)
@@ -5,7 +5,7 @@ module TomlRB
5
5
  def initialize(content, symbolize_keys: false)
6
6
  @hash = {}
7
7
  @visited_keys = []
8
- @fully_defined_keys = []
8
+ @fully_defined_paths = []
9
9
  @current = @hash
10
10
  @symbolize_keys = symbolize_keys
11
11
 
@@ -14,13 +14,21 @@ module TomlRB
14
14
  parsed.matches.map(&:value).compact.each { |m| m.accept_visitor(self) }
15
15
  rescue Citrus::ParseError => e
16
16
  raise TomlRB::ParseError.new(e.message)
17
+ rescue Encoding::CompatibilityError => e
18
+ raise TomlRB::ParseError.new("Encoding error: #{e.message}")
19
+ rescue ArgumentError => e
20
+ if e.message.include?("invalid byte sequence") || e.message.include?("encoding")
21
+ raise TomlRB::ParseError.new("Encoding error: #{e.message}")
22
+ else
23
+ raise
24
+ end
17
25
  end
18
26
  end
19
27
 
20
28
  # Read about the Visitor pattern
21
29
  # http://en.wikipedia.org/wiki/Visitor_pattern
22
30
  def visit_table_array(table_array)
23
- @fully_defined_keys = []
31
+ @fully_defined_paths = []
24
32
  table_array_key = table_array.full_key
25
33
  @visited_keys.reject! { |k| k.start_with? table_array_key }
26
34
 
@@ -28,12 +36,12 @@ module TomlRB
28
36
  end
29
37
 
30
38
  def visit_table(table)
31
- @fully_defined_keys = []
39
+ @fully_defined_paths = []
32
40
  @current = table.navigate_keys @hash, @visited_keys, @symbolize_keys
33
41
  end
34
42
 
35
43
  def visit_keyvalue(keyvalue)
36
- keyvalue.assign @current, @fully_defined_keys, @symbolize_keys
44
+ keyvalue.assign @current, @fully_defined_paths, @symbolize_keys
37
45
  end
38
46
  end
39
47
  end
data/lib/toml-rb/table.rb CHANGED
@@ -8,9 +8,16 @@ module TomlRB
8
8
  ensure_key_not_defined(visited_keys)
9
9
  current = hash
10
10
  keys = symbolize_keys ? @dotted_keys.map(&:to_sym) : @dotted_keys
11
- keys.each do |key|
11
+ keys.each_with_index do |key, index|
12
12
  current[key] = {} unless current.key?(key)
13
13
  element = current[key]
14
+
15
+ # If this is the final key and it's already an array (from [[key]]), that's invalid
16
+ is_final_key = (index == keys.length - 1)
17
+ if is_final_key && element.is_a?(Array)
18
+ fail ValueOverwriteError.new(key)
19
+ end
20
+
14
21
  current = element.is_a?(Array) ? element.last : element
15
22
  # check that key has not been defined before as a scalar value
16
23
  fail ValueOverwriteError.new(key) unless current.is_a?(Hash)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TomlRB
4
- VERSION = "2.1.1"
4
+ VERSION = "4.2.1"
5
5
  end
data/lib/toml-rb.rb CHANGED
@@ -14,7 +14,6 @@ require_relative "toml-rb/dumper"
14
14
  File.dirname(File.expand_path(__FILE__)).tap do |root|
15
15
  Citrus.load "#{root}/toml-rb/grammars/helper.citrus"
16
16
  Citrus.load "#{root}/toml-rb/grammars/primitive.citrus"
17
- Citrus.load "#{root}/toml-rb/grammars/array.citrus"
18
17
  Citrus.load "#{root}/toml-rb/grammars/document.citrus"
19
18
  end
20
19
 
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: toml-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.1
4
+ version: 4.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emiliano Mancuso
8
8
  - Lucas Tolchinsky
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2022-01-19 00:00:00.000000000 Z
11
+ date: 2026-07-31 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: citrus
@@ -31,6 +30,20 @@ dependencies:
31
30
  - - ">"
32
31
  - !ruby/object:Gem::Version
33
32
  version: '3.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: racc
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.7'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.7'
34
47
  - !ruby/object:Gem::Dependency
35
48
  name: minitest
36
49
  requirement: !ruby/object:Gem::Requirement
@@ -88,7 +101,6 @@ files:
88
101
  - lib/toml-rb/datetime.rb
89
102
  - lib/toml-rb/dumper.rb
90
103
  - lib/toml-rb/errors.rb
91
- - lib/toml-rb/grammars/array.citrus
92
104
  - lib/toml-rb/grammars/document.citrus
93
105
  - lib/toml-rb/grammars/helper.citrus
94
106
  - lib/toml-rb/grammars/primitive.citrus
@@ -103,7 +115,6 @@ homepage: https://github.com/emancu/toml-rb
103
115
  licenses:
104
116
  - MIT
105
117
  metadata: {}
106
- post_install_message:
107
118
  rdoc_options: []
108
119
  require_paths:
109
120
  - lib
@@ -111,15 +122,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
111
122
  requirements:
112
123
  - - ">="
113
124
  - !ruby/object:Gem::Version
114
- version: '2.3'
125
+ version: '2.5'
115
126
  required_rubygems_version: !ruby/object:Gem::Requirement
116
127
  requirements:
117
128
  - - ">="
118
129
  - !ruby/object:Gem::Version
119
130
  version: '0'
120
131
  requirements: []
121
- rubygems_version: 3.2.9
122
- signing_key:
132
+ rubygems_version: 3.6.2
123
133
  specification_version: 4
124
134
  summary: Toml parser in ruby, for ruby.
125
135
  test_files: []
@@ -1,37 +0,0 @@
1
- grammar TomlRB::Arrays
2
- include TomlRB::Primitive
3
-
4
- rule array_comments
5
- (indent? (comment indent?)*)
6
- end
7
-
8
- rule float_array
9
- (float (space "," array_comments float)*) {
10
- captures[:float].map(&:value)
11
- }
12
- end
13
-
14
- rule string_array
15
- (string (space "," array_comments string)*) {
16
- captures[:string].map(&:value)
17
- }
18
- end
19
-
20
- rule integer_array
21
- (integer (space "," array_comments integer)*) {
22
- captures[:integer].map(&:value)
23
- }
24
- end
25
-
26
- rule datetime_array
27
- (datetime (space "," array_comments datetime)*) {
28
- captures[:datetime].map(&:value)
29
- }
30
- end
31
-
32
- rule bool_array
33
- (bool (space "," array_comments bool)*) {
34
- captures[:bool].map(&:value)
35
- }
36
- end
37
- end