toml-rb 4.2.2 → 5.0.0.rc.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/README.md +17 -5
- data/lib/toml-rb/datetime.rb +37 -8
- data/lib/toml-rb/dumper.rb +44 -9
- data/lib/toml-rb/grammars/document.citrus +19 -9
- data/lib/toml-rb/grammars/helper.citrus +1 -1
- data/lib/toml-rb/grammars/primitive.citrus +14 -14
- data/lib/toml-rb/inline_table.rb +2 -4
- data/lib/toml-rb/keyvalue.rb +13 -25
- data/lib/toml-rb/parser.rb +7 -10
- data/lib/toml-rb/string.rb +13 -15
- data/lib/toml-rb/table.rb +10 -20
- data/lib/toml-rb/table_array.rb +16 -16
- data/lib/toml-rb/version.rb +1 -1
- data/lib/toml-rb.rb +1 -1
- metadata +1 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: deb05c1f85b54d08dda1640e125ecdbbeff043acbe1b753b7c537ca9260a8caf
|
|
4
|
+
data.tar.gz: 5c135ebaab7f60c0c88c3283f5c6a4db5ba6b79e672e362adf81c11becdd2ca7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a33c28b6a6ec07a22260bce3d517374e9f09121e527a36e4319da46f888ab6ed4d8f5b48aebb6c7e3705009b4729e9255764c0fd3c2b4869771263e84ce5f9d9
|
|
7
|
+
data.tar.gz: b3e67a5f28e98de500e67eac01c921c8e5eb6e2ced829b46942eadf48f79a45f1881dc12a80f48b514efed52b2fdce707f705a2adc3d76d9f332ea30e90c15e1
|
data/README.md
CHANGED
|
@@ -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
|
-
|
|
14
|
+
toml-rb implements [TOML 1.1.0](https://toml.io/en/v1.1.0) and passes all [toml-test](https://github.com/toml-lang/toml-test) v2.2.0 decoder tests for that version.
|
|
15
15
|
|
|
16
16
|
Installation
|
|
17
17
|
------------
|
|
@@ -42,6 +42,16 @@ TomlRB.parse(stream)
|
|
|
42
42
|
# You want symbols as your keys? No problem!
|
|
43
43
|
TomlRB.load_file(path, symbolize_keys: true)
|
|
44
44
|
# Works the same for TomlRB.parse
|
|
45
|
+
|
|
46
|
+
# Local datetimes, dates and times are subclasses of Time. Offset datetimes are Time.
|
|
47
|
+
times = <<-EOS
|
|
48
|
+
odt = 1979-05-27T07:32:00Z
|
|
49
|
+
ldt = 1979-05-27T07:32:00
|
|
50
|
+
ld = 1979-05-27
|
|
51
|
+
lt = 07:32:00
|
|
52
|
+
EOS
|
|
53
|
+
TomlRB.parse(times).transform_values(&:class)
|
|
54
|
+
# => {"odt"=>Time, "ldt"=>TomlRB::LocalDateTime, "ld"=>TomlRB::LocalDate, "lt"=>TomlRB::LocalTime}
|
|
45
55
|
```
|
|
46
56
|
|
|
47
57
|
Dumper Usage
|
|
@@ -78,11 +88,13 @@ TOML 1.0.0 and TOML 1.1.0. The harness lives in:
|
|
|
78
88
|
- `tool/toml-test-decoder` — toml-test decoder shim.
|
|
79
89
|
- `.github/workflows/conformance.yml` — CI workflow (per-version matrix).
|
|
80
90
|
- `.github/toml-test-skip-1.0.txt` and `.github/toml-test-skip-1.1.txt` —
|
|
81
|
-
skip-lists of
|
|
91
|
+
skip-lists of toml-test names for each spec version.
|
|
82
92
|
|
|
83
|
-
The skip-
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
The TOML 1.1.0 skip-list is empty. The TOML 1.0.0 skip-list has 9 tests with
|
|
94
|
+
documents that TOML 1.0.0 forbids and TOML 1.1.0 allows. `toml-rb` accepts
|
|
95
|
+
TOML 1.1.0 syntax and has no option to select the spec version, so these tests
|
|
96
|
+
fail. CI runs toml-test with `-skip-must-err`, so the run fails when a skipped
|
|
97
|
+
test passes.
|
|
86
98
|
|
|
87
99
|
Contributing
|
|
88
100
|
------------
|
data/lib/toml-rb/datetime.rb
CHANGED
|
@@ -1,12 +1,41 @@
|
|
|
1
|
+
require "date"
|
|
2
|
+
|
|
1
3
|
module TomlRB
|
|
4
|
+
LocalDateTime = Class.new(Time)
|
|
5
|
+
LocalDate = Class.new(Time)
|
|
6
|
+
LocalTime = Class.new(Time)
|
|
7
|
+
|
|
8
|
+
module DateSkeletonParser
|
|
9
|
+
def value
|
|
10
|
+
year, mon, day = [:year, :mon, :day].map { |s| capture(s).value }
|
|
11
|
+
unless Date.valid_date?(year.to_i, mon.to_i, day.to_i, Date::GREGORIAN)
|
|
12
|
+
raise ParseError, "Invalid date: #{year}-#{mon}-#{day}"
|
|
13
|
+
end
|
|
14
|
+
[year, mon, day]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module TimeSkeletonParser
|
|
19
|
+
def value
|
|
20
|
+
hour, min, sec = [:hour, :mim, :sec].map { |s| capture(s)&.value || "0" }
|
|
21
|
+
unless hour.to_i.between?(0, 23) && min.to_i.between?(0, 59) && sec.to_i.between?(0, 60)
|
|
22
|
+
raise ParseError, "Invalid time: #{hour}:#{min}:#{sec}"
|
|
23
|
+
end
|
|
24
|
+
[hour, min, sec, capture(:sec_frac) || "0"]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
2
28
|
module OffsetDateTimeParser
|
|
3
29
|
def value
|
|
4
30
|
skeleton = captures[:datetime_skeleton].first
|
|
5
31
|
year, mon, day, hour, min, sec, sec_frac = skeleton.value
|
|
6
|
-
offset = captures[:date_offset].first || "+00:00"
|
|
7
|
-
|
|
32
|
+
offset = (captures[:date_offset].first || "+00:00").to_s
|
|
33
|
+
unless offset[1, 2].to_i.between?(0, 23) && offset[4, 2].to_i.between?(0, 59)
|
|
34
|
+
raise ParseError, "Invalid UTC offset: #{offset}"
|
|
35
|
+
end
|
|
36
|
+
sec = "#{sec}.#{sec_frac}".to_r
|
|
8
37
|
|
|
9
|
-
Time.new(year, mon, day, hour, min, sec, offset
|
|
38
|
+
Time.new(year, mon, day, hour, min, sec, offset)
|
|
10
39
|
end
|
|
11
40
|
end
|
|
12
41
|
|
|
@@ -14,25 +43,25 @@ module TomlRB
|
|
|
14
43
|
def value
|
|
15
44
|
year, mon, day = captures[:date_skeleton].first.value
|
|
16
45
|
hour, min, sec, sec_frac = captures[:time_skeleton].first.value
|
|
17
|
-
|
|
46
|
+
sec = "#{sec}.#{sec_frac}".to_r
|
|
18
47
|
|
|
19
|
-
|
|
48
|
+
LocalDateTime.local(year, mon, day, hour, min, sec)
|
|
20
49
|
end
|
|
21
50
|
end
|
|
22
51
|
|
|
23
52
|
module LocalDateParser
|
|
24
53
|
def value
|
|
25
54
|
year, mon, day = captures[:date_skeleton].first.value
|
|
26
|
-
|
|
55
|
+
LocalDate.local(year, mon, day)
|
|
27
56
|
end
|
|
28
57
|
end
|
|
29
58
|
|
|
30
59
|
module LocalTimeParser
|
|
31
60
|
def value
|
|
32
61
|
hour, min, sec, sec_frac = captures[:time_skeleton].first.value
|
|
33
|
-
|
|
62
|
+
sec = "#{sec}.#{sec_frac}".to_r
|
|
34
63
|
|
|
35
|
-
|
|
64
|
+
LocalTime.utc(1970, 1, 1, hour, min, sec)
|
|
36
65
|
end
|
|
37
66
|
end
|
|
38
67
|
end
|
data/lib/toml-rb/dumper.rb
CHANGED
|
@@ -41,13 +41,13 @@ module TomlRB
|
|
|
41
41
|
simple_pairs = []
|
|
42
42
|
table_array_pairs = []
|
|
43
43
|
|
|
44
|
-
hash.
|
|
44
|
+
sorted_keys(hash).each do |key|
|
|
45
45
|
val = hash[key]
|
|
46
46
|
element = [key, val]
|
|
47
47
|
|
|
48
48
|
if val.is_a? Hash
|
|
49
49
|
nested_pairs << element
|
|
50
|
-
elsif val.is_a?(Array) && val.
|
|
50
|
+
elsif val.is_a?(Array) && !val.empty? && val.all?(Hash)
|
|
51
51
|
table_array_pairs << element
|
|
52
52
|
else
|
|
53
53
|
simple_pairs << element
|
|
@@ -57,6 +57,17 @@ module TomlRB
|
|
|
57
57
|
[simple_pairs, nested_pairs, table_array_pairs]
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
+
def sorted_keys(hash)
|
|
61
|
+
keys = hash.keys
|
|
62
|
+
raise Error, "Cannot dump keys with duplicate names: #{keys.inspect}" if keys.map(&:to_s).uniq!
|
|
63
|
+
|
|
64
|
+
begin
|
|
65
|
+
keys.sort
|
|
66
|
+
rescue ArgumentError
|
|
67
|
+
keys.sort_by(&:to_s)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
60
71
|
def dump_pairs(simple, nested, table_array, prefix = [])
|
|
61
72
|
# First add simple pairs, under the prefix
|
|
62
73
|
dump_simple_pairs simple
|
|
@@ -101,16 +112,30 @@ module TomlRB
|
|
|
101
112
|
end
|
|
102
113
|
|
|
103
114
|
def to_toml(obj)
|
|
104
|
-
if obj.is_a?(
|
|
105
|
-
obj.strftime("%Y-%m-%
|
|
115
|
+
if obj.is_a?(LocalDate)
|
|
116
|
+
obj.strftime("%Y-%m-%d")
|
|
117
|
+
elsif obj.is_a?(LocalTime)
|
|
118
|
+
obj.strftime("%H:%M:%S") + sec_fraction(obj)
|
|
119
|
+
elsif obj.is_a?(LocalDateTime)
|
|
120
|
+
obj.strftime("%Y-%m-%dT%H:%M:%S") + sec_fraction(obj)
|
|
121
|
+
elsif obj.is_a?(Time) || obj.is_a?(DateTime)
|
|
122
|
+
zone = obj.strftime("%:z").sub("+00:00", "Z")
|
|
123
|
+
obj.strftime("%Y-%m-%dT%H:%M:%S") + sec_fraction(obj) + zone
|
|
106
124
|
elsif obj.is_a?(Date)
|
|
107
125
|
obj.strftime("%Y-%m-%d")
|
|
108
126
|
elsif obj.is_a?(Regexp)
|
|
109
|
-
obj.inspect
|
|
110
|
-
elsif obj.is_a?(String)
|
|
111
|
-
escape_string(obj)
|
|
127
|
+
escape_string(obj.inspect)
|
|
128
|
+
elsif obj.is_a?(String) || obj.is_a?(Symbol)
|
|
129
|
+
escape_string(obj.to_s)
|
|
112
130
|
elsif obj.is_a?(Array)
|
|
113
131
|
"[" + obj.map(&method(:to_toml)).join(", ") + "]"
|
|
132
|
+
elsif obj.is_a?(Hash)
|
|
133
|
+
pairs = sorted_keys(obj).map do |key|
|
|
134
|
+
val = obj[key]
|
|
135
|
+
key = quote_key(key) unless bare_key? key
|
|
136
|
+
"#{key} = #{to_toml(val)}"
|
|
137
|
+
end
|
|
138
|
+
"{" + pairs.join(", ") + "}"
|
|
114
139
|
elsif obj.is_a?(Float) && (obj.nan? || obj.infinite?)
|
|
115
140
|
# Ruby renders these as Infinity/-Infinity/NaN, which are invalid TOML.
|
|
116
141
|
if obj.nan?
|
|
@@ -120,8 +145,13 @@ module TomlRB
|
|
|
120
145
|
else
|
|
121
146
|
"inf"
|
|
122
147
|
end
|
|
123
|
-
|
|
148
|
+
elsif obj.is_a?(Integer) || obj.is_a?(Float) || obj == true || obj == false
|
|
149
|
+
obj.inspect
|
|
150
|
+
elsif defined?(BigDecimal) && obj.is_a?(BigDecimal) && obj.finite?
|
|
151
|
+
# BigDecimal#inspect is a valid TOML float, for example 0.15e1.
|
|
124
152
|
obj.inspect
|
|
153
|
+
else
|
|
154
|
+
raise Error, "Cannot dump #{obj.class} to TOML"
|
|
125
155
|
end
|
|
126
156
|
end
|
|
127
157
|
|
|
@@ -135,7 +165,8 @@ module TomlRB
|
|
|
135
165
|
end
|
|
136
166
|
|
|
137
167
|
# Serialize a Ruby string as a TOML basic string. Ruby's String#inspect
|
|
138
|
-
# emits \a \v
|
|
168
|
+
# emits \a and \v, which are not TOML escapes, and \e, which is not a TOML 1.0
|
|
169
|
+
# escape. Write ESC as \u001B to keep the output valid TOML 1.0.
|
|
139
170
|
def escape_string(str)
|
|
140
171
|
escaped = str.gsub(/[\x00-\x1f\x7f"\\]/) do |char|
|
|
141
172
|
BASIC_ESCAPES[char] || format('\\u%04X', char.ord)
|
|
@@ -143,5 +174,9 @@ module TomlRB
|
|
|
143
174
|
|
|
144
175
|
"\"#{escaped}\""
|
|
145
176
|
end
|
|
177
|
+
|
|
178
|
+
def sec_fraction(time)
|
|
179
|
+
time.strftime(".%9N").sub(/\.?0+\z/, "")
|
|
180
|
+
end
|
|
146
181
|
end
|
|
147
182
|
end
|
|
@@ -2,19 +2,23 @@ grammar TomlRB::Document
|
|
|
2
2
|
include TomlRB::Primitive
|
|
3
3
|
|
|
4
4
|
rule document
|
|
5
|
-
(comment | table_array | table |
|
|
5
|
+
(comment | table_array | table | keyvalue_line | line_break | trailing_space)*
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
rule table_array
|
|
9
|
-
(space? '[[' stripped_key ']]'
|
|
9
|
+
(space? '[[' stripped_key ']]' line_end) <TomlRB::TableArrayParser>
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
rule table
|
|
13
|
-
(space? '[' stripped_key ']'
|
|
13
|
+
(space? '[' stripped_key ']' line_end) <TomlRB::TableParser>
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
rule keyvalue_line
|
|
17
|
+
(keyvalue line_end) { capture(:keyvalue).value }
|
|
14
18
|
end
|
|
15
19
|
|
|
16
20
|
rule keyvalue
|
|
17
|
-
(stripped_key '=' space? v:(toml_values)
|
|
21
|
+
(stripped_key '=' space? v:(toml_values) space) <TomlRB::KeyvalueParser>
|
|
18
22
|
end
|
|
19
23
|
|
|
20
24
|
### Values
|
|
@@ -24,7 +28,7 @@ grammar TomlRB::Document
|
|
|
24
28
|
end
|
|
25
29
|
|
|
26
30
|
rule array
|
|
27
|
-
("[" array_comments (array_elements
|
|
31
|
+
("[" array_comments (array_elements (array_comments ",")?)? array_comments "]") <TomlRB::ArrayParser>
|
|
28
32
|
end
|
|
29
33
|
|
|
30
34
|
rule array_array
|
|
@@ -34,15 +38,21 @@ grammar TomlRB::Document
|
|
|
34
38
|
end
|
|
35
39
|
|
|
36
40
|
rule array_elements
|
|
37
|
-
(toml_values (
|
|
38
|
-
captures[:toml_values].map(&:value)
|
|
39
|
-
v.respond_to?(:value) ? v.value : v
|
|
40
|
-
end
|
|
41
|
+
(toml_values (array_comments "," array_comments toml_values)*) {
|
|
42
|
+
captures[:toml_values].map(&:value)
|
|
41
43
|
}
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
### Helpers
|
|
45
47
|
|
|
48
|
+
rule line_end
|
|
49
|
+
(space (comment | line_break | !.))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
rule trailing_space
|
|
53
|
+
([ \t]+ !.) { nil }
|
|
54
|
+
end
|
|
55
|
+
|
|
46
56
|
rule stripped_key
|
|
47
57
|
(space? key space?) { captures[:key].first.value }
|
|
48
58
|
end
|
|
@@ -14,19 +14,19 @@ grammar TomlRB::Primitive
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
rule basic_string
|
|
17
|
-
(/"(?:[^"\\\
|
|
17
|
+
(/"(?:[^"\\\x00-\x08\x0a-\x1f\x7f]|\\.)*"/ space) <TomlRB::BasicString>
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
rule literal_string
|
|
21
|
-
(/'[^'\
|
|
21
|
+
(/'[^'\x00-\x08\x0a-\x1f\x7f]*'/ space) <TomlRB::LiteralString>
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
rule multiline_string
|
|
25
|
-
('"""'
|
|
25
|
+
('"""' /\r?\n/? text:/(?:[^"\\\x00-\x08\x0b-\x1f\x7f]|\r\n|\\(?:[^\r]|\r\n)|"{1,2}(?!"))*"{0,2}(?=""")/ '"""' space) <TomlRB::MultilineString>
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
rule multiline_literal
|
|
29
|
-
("'''"
|
|
29
|
+
("'''" /\r?\n/? text:/(?:[^'\x00-\x08\x0b-\x1f\x7f]|\r\n|'{1,2}(?!'))*'{0,2}(?=''')/ "'''" space) <TomlRB::MultilineLiteral>
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
##
|
|
@@ -38,7 +38,7 @@ grammar TomlRB::Primitive
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
rule offset_datetime
|
|
41
|
-
( skeleton:datetime_skeleton (
|
|
41
|
+
( skeleton:datetime_skeleton ([Zz] | date_offset) ) <TomlRB::OffsetDateTimeParser>
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
rule local_datetime
|
|
@@ -54,21 +54,17 @@ grammar TomlRB::Primitive
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
rule datetime_skeleton
|
|
57
|
-
(date_skeleton (
|
|
57
|
+
(date_skeleton ([Tt]|" ") time_skeleton) {
|
|
58
58
|
capture(:date_skeleton).value + capture(:time_skeleton).value
|
|
59
59
|
}
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
rule date_skeleton
|
|
63
|
-
(year:/\d\d\d\d/ "-" mon:/\d\d/ "-" day:/\d\d/)
|
|
64
|
-
[:year,:mon,:day].map{ |s| capture(s).value }
|
|
65
|
-
}
|
|
63
|
+
(year:/\d\d\d\d/ "-" mon:/\d\d/ "-" day:/\d\d/) <TomlRB::DateSkeletonParser>
|
|
66
64
|
end
|
|
67
65
|
|
|
68
66
|
rule time_skeleton
|
|
69
|
-
( hour:([0-2][0-9]) ":" mim:([0-6][0-9]) ":" sec:([0-6][0-9]) (
|
|
70
|
-
[:hour,:mim,:sec].map{ |s| capture(s).value } + [capture(:sec_frac) || '0']
|
|
71
|
-
}
|
|
67
|
+
( hour:([0-2][0-9]) ":" mim:([0-6][0-9]) (":" sec:([0-6][0-9]) ("." sec_frac:(/\d/1*9) /\d/*)?)? ) <TomlRB::TimeSkeletonParser>
|
|
72
68
|
end
|
|
73
69
|
|
|
74
70
|
rule date_offset
|
|
@@ -99,7 +95,11 @@ grammar TomlRB::Primitive
|
|
|
99
95
|
end
|
|
100
96
|
|
|
101
97
|
rule exponential_float
|
|
102
|
-
((fractional_float | decimal_integer) [eE]
|
|
98
|
+
((fractional_float | decimal_integer) [eE] exponent) { to_str.to_f }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
rule exponent
|
|
102
|
+
sign? [0-9] ([0-9] | '_' [0-9])*
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
rule fractional_float
|
|
@@ -125,7 +125,7 @@ grammar TomlRB::Primitive
|
|
|
125
125
|
end
|
|
126
126
|
|
|
127
127
|
rule decimal_integer
|
|
128
|
-
(sign? [
|
|
128
|
+
(sign? ('0' | [1-9] ([0-9] | '_' [0-9])*)) { to_str.to_i }
|
|
129
129
|
end
|
|
130
130
|
|
|
131
131
|
rule sign
|
data/lib/toml-rb/inline_table.rb
CHANGED
|
@@ -10,10 +10,8 @@ module TomlRB
|
|
|
10
10
|
|
|
11
11
|
def value(symbolize_keys = false)
|
|
12
12
|
result = {}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
result.merge!(update) { |key, _, _| fail ValueOverwriteError.new(key) }
|
|
16
|
-
end
|
|
13
|
+
tables = {}.compare_by_identity
|
|
14
|
+
@pairs.each { |kv| kv.assign(result, tables, symbolize_keys) }
|
|
17
15
|
result
|
|
18
16
|
end
|
|
19
17
|
end
|
data/lib/toml-rb/keyvalue.rb
CHANGED
|
@@ -10,36 +10,23 @@ module TomlRB
|
|
|
10
10
|
@symbolize_keys = false
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def assign(hash,
|
|
13
|
+
def assign(hash, tables, symbolize_keys = false)
|
|
14
14
|
@symbolize_keys = symbolize_keys
|
|
15
|
-
keys = symbolize_keys ? @dotted_keys.map(&:to_sym) : @dotted_keys
|
|
16
|
-
depth = @dotted_keys.size
|
|
17
|
-
update = keys.reverse.inject(visit_value(@value)) { |k1, k2| {k2 => k1} }
|
|
15
|
+
*keys, last_key = symbolize_keys ? @dotted_keys.map(&:to_sym) : @dotted_keys
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
existing_hash = hash.dig(*keys)
|
|
27
|
-
fail ValueOverwriteError.new(@dotted_keys.first) if existing_hash.is_a?(Hash) && !existing_hash.empty?
|
|
17
|
+
keys.each do |key|
|
|
18
|
+
unless hash.key?(key)
|
|
19
|
+
hash[key] = {}
|
|
20
|
+
tables[hash[key]] = :dotted
|
|
21
|
+
end
|
|
22
|
+
hash = hash[key]
|
|
23
|
+
fail ValueOverwriteError.new(key) unless [:implicit, :dotted].include?(tables[hash])
|
|
28
24
|
|
|
29
|
-
|
|
25
|
+
tables[hash] = :dotted
|
|
30
26
|
end
|
|
27
|
+
fail ValueOverwriteError.new(last_key) if hash.key?(last_key)
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def dotted_key_merge(hash, update)
|
|
36
|
-
hash.merge!(update) do |key, old, new|
|
|
37
|
-
if old.is_a?(Hash) && new.is_a?(Hash)
|
|
38
|
-
dotted_key_merge(old, new)
|
|
39
|
-
else
|
|
40
|
-
fail ValueOverwriteError.new(key)
|
|
41
|
-
end
|
|
42
|
-
end
|
|
29
|
+
hash[last_key] = visit_value(@value)
|
|
43
30
|
end
|
|
44
31
|
|
|
45
32
|
def accept_visitor(parser)
|
|
@@ -49,6 +36,7 @@ module TomlRB
|
|
|
49
36
|
private
|
|
50
37
|
|
|
51
38
|
def visit_value(a_value)
|
|
39
|
+
return a_value.map { |v| visit_value(v) } if a_value.is_a?(Array)
|
|
52
40
|
return a_value unless a_value.respond_to? :accept_visitor
|
|
53
41
|
|
|
54
42
|
a_value.accept_visitor self
|
data/lib/toml-rb/parser.rb
CHANGED
|
@@ -4,8 +4,10 @@ module TomlRB
|
|
|
4
4
|
|
|
5
5
|
def initialize(content, symbolize_keys: false)
|
|
6
6
|
@hash = {}
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
# Tables and arrays of tables that headers or dotted keys made, by identity:
|
|
8
|
+
# :implicit (super-table of a header), :explicit (header) or :dotted (dotted key).
|
|
9
|
+
# Inline tables and static arrays are absent, so no header or dotted key can enter them.
|
|
10
|
+
@tables = {}.compare_by_identity
|
|
9
11
|
@current = @hash
|
|
10
12
|
@symbolize_keys = symbolize_keys
|
|
11
13
|
|
|
@@ -28,20 +30,15 @@ module TomlRB
|
|
|
28
30
|
# Read about the Visitor pattern
|
|
29
31
|
# http://en.wikipedia.org/wiki/Visitor_pattern
|
|
30
32
|
def visit_table_array(table_array)
|
|
31
|
-
@
|
|
32
|
-
table_array_key = table_array.full_key
|
|
33
|
-
@visited_keys.reject! { |k| k.start_with? table_array_key }
|
|
34
|
-
|
|
35
|
-
@current = table_array.navigate_keys @hash, @symbolize_keys
|
|
33
|
+
@current = table_array.navigate_keys @hash, @tables, @symbolize_keys
|
|
36
34
|
end
|
|
37
35
|
|
|
38
36
|
def visit_table(table)
|
|
39
|
-
@
|
|
40
|
-
@current = table.navigate_keys @hash, @visited_keys, @symbolize_keys
|
|
37
|
+
@current = table.navigate_keys @hash, @tables, @symbolize_keys
|
|
41
38
|
end
|
|
42
39
|
|
|
43
40
|
def visit_keyvalue(keyvalue)
|
|
44
|
-
keyvalue.assign @current, @
|
|
41
|
+
keyvalue.assign @current, @tables, @symbolize_keys
|
|
45
42
|
end
|
|
46
43
|
end
|
|
47
44
|
end
|
data/lib/toml-rb/string.rb
CHANGED
|
@@ -2,9 +2,9 @@ module TomlRB
|
|
|
2
2
|
# Used in primitive.citrus
|
|
3
3
|
module BasicString
|
|
4
4
|
SPECIAL_CHARS = {
|
|
5
|
-
"\\0" => "\0",
|
|
6
5
|
"\\t" => "\t",
|
|
7
6
|
"\\b" => "\b",
|
|
7
|
+
"\\e" => "\e",
|
|
8
8
|
"\\f" => "\f",
|
|
9
9
|
"\\n" => "\n",
|
|
10
10
|
"\\r" => "\r",
|
|
@@ -21,12 +21,19 @@ module TomlRB
|
|
|
21
21
|
# Replace the unicode escaped characters with the corresponding character
|
|
22
22
|
# e.g. \u03B4 => ?
|
|
23
23
|
def self.decode_unicode(str)
|
|
24
|
-
|
|
24
|
+
code = str[2..-1].to_i(16)
|
|
25
|
+
if code.between?(0xD800, 0xDFFF) || code > 0x10FFFF
|
|
26
|
+
fail ParseError.new "Escape sequence #{str} is not a Unicode scalar value"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
[code].pack("U")
|
|
25
30
|
end
|
|
26
31
|
|
|
27
32
|
def self.transform_escaped_chars(str)
|
|
28
|
-
str.gsub(/\\(u[\da-fA-F]{4}|U[\da-fA-F]{8}
|
|
29
|
-
if m.
|
|
33
|
+
str.gsub(/\\(x[\da-fA-F]{2}|u[\da-fA-F]{4}|U[\da-fA-F]{8}|[ \t]*\r?\n[ \t\r\n]*|.)/) do |m|
|
|
34
|
+
if m.include?("\n")
|
|
35
|
+
""
|
|
36
|
+
elsif m.size == 2
|
|
30
37
|
SPECIAL_CHARS[m] || parse_error(m)
|
|
31
38
|
else
|
|
32
39
|
decode_unicode(m).force_encoding("UTF-8")
|
|
@@ -47,22 +54,13 @@ module TomlRB
|
|
|
47
54
|
|
|
48
55
|
module MultilineString
|
|
49
56
|
def value
|
|
50
|
-
|
|
51
|
-
aux = captures[:text].first.value
|
|
52
|
-
|
|
53
|
-
# Remove spaces on multilined Singleline strings
|
|
54
|
-
aux.gsub!(/\\\r?\n[\n\t\r ]*/, "")
|
|
55
|
-
|
|
56
|
-
TomlRB::BasicString.transform_escaped_chars aux
|
|
57
|
+
TomlRB::BasicString.transform_escaped_chars captures[:text].first.value
|
|
57
58
|
end
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
module MultilineLiteral
|
|
61
62
|
def value
|
|
62
|
-
|
|
63
|
-
aux = captures[:text].first.value
|
|
64
|
-
|
|
65
|
-
aux.gsub(/\\\r?\n[\n\t\r ]*/, "")
|
|
63
|
+
captures[:text].first.value
|
|
66
64
|
end
|
|
67
65
|
end
|
|
68
66
|
end
|
data/lib/toml-rb/table.rb
CHANGED
|
@@ -4,24 +4,22 @@ module TomlRB
|
|
|
4
4
|
@dotted_keys = dotted_keys
|
|
5
5
|
end
|
|
6
6
|
|
|
7
|
-
def navigate_keys(hash,
|
|
8
|
-
ensure_key_not_defined(visited_keys)
|
|
7
|
+
def navigate_keys(hash, tables, symbolize_keys = false)
|
|
9
8
|
current = hash
|
|
10
9
|
keys = symbolize_keys ? @dotted_keys.map(&:to_sym) : @dotted_keys
|
|
11
|
-
keys.
|
|
12
|
-
|
|
13
|
-
|
|
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)
|
|
10
|
+
keys.each do |key|
|
|
11
|
+
unless current.key?(key)
|
|
12
|
+
current[key] = {}
|
|
13
|
+
tables[current[key]] = :implicit
|
|
19
14
|
end
|
|
15
|
+
element = current[key]
|
|
16
|
+
fail ValueOverwriteError.new(key) unless tables.key?(element)
|
|
20
17
|
|
|
21
18
|
current = element.is_a?(Array) ? element.last : element
|
|
22
|
-
# check that key has not been defined before as a scalar value
|
|
23
|
-
fail ValueOverwriteError.new(key) unless current.is_a?(Hash)
|
|
24
19
|
end
|
|
20
|
+
fail ValueOverwriteError.new(full_key) unless tables[current] == :implicit
|
|
21
|
+
|
|
22
|
+
tables[current] = :explicit
|
|
25
23
|
current
|
|
26
24
|
end
|
|
27
25
|
|
|
@@ -32,14 +30,6 @@ module TomlRB
|
|
|
32
30
|
def full_key
|
|
33
31
|
@dotted_keys.join(".")
|
|
34
32
|
end
|
|
35
|
-
|
|
36
|
-
private
|
|
37
|
-
|
|
38
|
-
# Fail if the key was already defined with a ValueOverwriteError
|
|
39
|
-
def ensure_key_not_defined(visited_keys)
|
|
40
|
-
fail ValueOverwriteError.new(full_key) if visited_keys.include?(full_key)
|
|
41
|
-
visited_keys << full_key
|
|
42
|
-
end
|
|
43
33
|
end
|
|
44
34
|
|
|
45
35
|
# Used in document.citrus
|
data/lib/toml-rb/table_array.rb
CHANGED
|
@@ -4,21 +4,20 @@ module TomlRB
|
|
|
4
4
|
@dotted_keys = dotted_keys
|
|
5
5
|
end
|
|
6
6
|
|
|
7
|
-
def navigate_keys(hash, symbolize_keys = false)
|
|
7
|
+
def navigate_keys(hash, tables, symbolize_keys = false)
|
|
8
8
|
current = hash
|
|
9
|
-
keys = symbolize_keys ? @dotted_keys.map(&:to_sym) : @dotted_keys
|
|
10
|
-
last_key = keys.pop
|
|
9
|
+
*keys, last_key = symbolize_keys ? @dotted_keys.map(&:to_sym) : @dotted_keys
|
|
11
10
|
|
|
12
11
|
# Go over the parent keys
|
|
13
12
|
keys.each do |key|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
current[key] << {} if current[key].empty?
|
|
18
|
-
current = current[key].last
|
|
19
|
-
else
|
|
20
|
-
current = current[key]
|
|
13
|
+
unless current.key?(key)
|
|
14
|
+
current[key] = {}
|
|
15
|
+
tables[current[key]] = :implicit
|
|
21
16
|
end
|
|
17
|
+
element = current[key]
|
|
18
|
+
fail ValueOverwriteError.new(key) unless tables.key?(element)
|
|
19
|
+
|
|
20
|
+
current = element.is_a?(Array) ? element.last : element
|
|
22
21
|
end
|
|
23
22
|
|
|
24
23
|
# Define Table Array
|
|
@@ -26,19 +25,20 @@ module TomlRB
|
|
|
26
25
|
fail TomlRB::ParseError,
|
|
27
26
|
"#{last_key} was defined as hash but is now redefined as a table!"
|
|
28
27
|
end
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
unless current.key?(last_key)
|
|
29
|
+
current[last_key] = []
|
|
30
|
+
tables[current[last_key]] = :explicit
|
|
31
|
+
end
|
|
32
|
+
fail ValueOverwriteError.new(last_key) unless tables.key?(current[last_key])
|
|
31
33
|
|
|
34
|
+
current[last_key] << {}
|
|
35
|
+
tables[current[last_key].last] = :explicit
|
|
32
36
|
current[last_key].last
|
|
33
37
|
end
|
|
34
38
|
|
|
35
39
|
def accept_visitor(parser)
|
|
36
40
|
parser.visit_table_array self
|
|
37
41
|
end
|
|
38
|
-
|
|
39
|
-
def full_key
|
|
40
|
-
@dotted_keys.join(".")
|
|
41
|
-
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
# Used in document.citrus
|
data/lib/toml-rb/version.rb
CHANGED
data/lib/toml-rb.rb
CHANGED
|
@@ -67,7 +67,7 @@ module TomlRB
|
|
|
67
67
|
# Raises Errno::ENOENT if the file cannot be found.
|
|
68
68
|
# Raises Errno::EACCES if the file cannot be accessed.
|
|
69
69
|
def self.load_file(path, symbolize_keys: false)
|
|
70
|
-
TomlRB.parse(File.read(path), symbolize_keys: symbolize_keys)
|
|
70
|
+
TomlRB.parse(File.read(path, encoding: "UTF-8"), symbolize_keys: symbolize_keys)
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
# Public: Returns a *TomlRB* string from a Ruby Hash.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: toml-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 5.0.0.rc.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Emiliano Mancuso
|
|
@@ -30,20 +30,6 @@ dependencies:
|
|
|
30
30
|
- - ">"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
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'
|
|
47
33
|
- !ruby/object:Gem::Dependency
|
|
48
34
|
name: minitest
|
|
49
35
|
requirement: !ruby/object:Gem::Requirement
|