toml-rb 4.2.1 → 6.0.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.
- checksums.yaml +4 -4
- data/README.md +22 -6
- data/lib/toml-rb/datetime.rb +42 -26
- data/lib/toml-rb/dumper.rb +47 -12
- data/lib/toml-rb/inline_table.rb +2 -10
- data/lib/toml-rb/keyvalue.rb +13 -32
- data/lib/toml-rb/parser.rb +263 -14
- data/lib/toml-rb/string.rb +11 -38
- data/lib/toml-rb/table.rb +10 -27
- data/lib/toml-rb/table_array.rb +16 -23
- data/lib/toml-rb/version.rb +1 -1
- data/lib/toml-rb.rb +1 -10
- metadata +3 -41
- data/lib/toml-rb/array.rb +0 -8
- data/lib/toml-rb/grammars/document.citrus +0 -57
- data/lib/toml-rb/grammars/helper.citrus +0 -17
- data/lib/toml-rb/grammars/primitive.citrus +0 -176
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d7514edd12c127a9f4ff3343e7590d4e526a15ebf3b13594d4d9683aea9fa1d0
|
|
4
|
+
data.tar.gz: a689fab092b24d2aadcecd046462af6d0cc741cdec8d4461372c65f6d249411b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e58611d93fbb27112dd505c43edc135500661c088e33cae0b50d6419bf30f814c94d389cc3a71f85072996a61492e74b9b9e9fce00477df5c3cd8e14691980a4
|
|
7
|
+
data.tar.gz: 4c5dff3f4537f96ba315410482dee6100463538be2cefe8c19fd8a1fcbce0db614870c9b0e83d52bc269fce28ef9872e8d9b78317b25fcae1b92a12b28c20cca
|
data/README.md
CHANGED
|
@@ -9,15 +9,19 @@ toml-rb
|
|
|
9
9
|
[](https://codeclimate.com/github/emancu/toml-rb)
|
|
10
10
|
[](https://rubygems.org/gems/toml-rb)
|
|
11
11
|
|
|
12
|
-
A [TOML](https://github.com/toml-lang/toml) parser
|
|
12
|
+
A pure Ruby [TOML](https://github.com/toml-lang/toml) parser and dumper with no runtime dependencies.
|
|
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
|
------------
|
|
18
18
|
|
|
19
19
|
$ gem install toml-rb
|
|
20
20
|
|
|
21
|
+
Or add this line to your Gemfile:
|
|
22
|
+
|
|
23
|
+
gem "toml-rb", "~> 6.0"
|
|
24
|
+
|
|
21
25
|
Parser Usage
|
|
22
26
|
------------
|
|
23
27
|
|
|
@@ -42,6 +46,16 @@ TomlRB.parse(stream)
|
|
|
42
46
|
# You want symbols as your keys? No problem!
|
|
43
47
|
TomlRB.load_file(path, symbolize_keys: true)
|
|
44
48
|
# Works the same for TomlRB.parse
|
|
49
|
+
|
|
50
|
+
# Local datetimes, dates and times are subclasses of Time. Offset datetimes are Time.
|
|
51
|
+
times = <<-EOS
|
|
52
|
+
odt = 1979-05-27T07:32:00Z
|
|
53
|
+
ldt = 1979-05-27T07:32:00
|
|
54
|
+
ld = 1979-05-27
|
|
55
|
+
lt = 07:32:00
|
|
56
|
+
EOS
|
|
57
|
+
TomlRB.parse(times).transform_values(&:class)
|
|
58
|
+
# => {"odt"=>Time, "ldt"=>TomlRB::LocalDateTime, "ld"=>TomlRB::LocalDate, "lt"=>TomlRB::LocalTime}
|
|
45
59
|
```
|
|
46
60
|
|
|
47
61
|
Dumper Usage
|
|
@@ -78,11 +92,13 @@ TOML 1.0.0 and TOML 1.1.0. The harness lives in:
|
|
|
78
92
|
- `tool/toml-test-decoder` — toml-test decoder shim.
|
|
79
93
|
- `.github/workflows/conformance.yml` — CI workflow (per-version matrix).
|
|
80
94
|
- `.github/toml-test-skip-1.0.txt` and `.github/toml-test-skip-1.1.txt` —
|
|
81
|
-
skip-lists of
|
|
95
|
+
skip-lists of toml-test names for each spec version.
|
|
82
96
|
|
|
83
|
-
The skip-
|
|
84
|
-
|
|
85
|
-
|
|
97
|
+
The TOML 1.1.0 skip-list is empty. The TOML 1.0.0 skip-list has 9 tests with
|
|
98
|
+
documents that TOML 1.0.0 forbids and TOML 1.1.0 allows. `toml-rb` accepts
|
|
99
|
+
TOML 1.1.0 syntax and has no option to select the spec version, so these tests
|
|
100
|
+
fail. CI runs toml-test with `-skip-must-err`, so the run fails when a skipped
|
|
101
|
+
test passes.
|
|
86
102
|
|
|
87
103
|
Contributing
|
|
88
104
|
------------
|
data/lib/toml-rb/datetime.rb
CHANGED
|
@@ -1,38 +1,54 @@
|
|
|
1
|
+
require "date"
|
|
2
|
+
|
|
1
3
|
module TomlRB
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
LocalDateTime = Class.new(Time)
|
|
5
|
+
LocalDate = Class.new(Time)
|
|
6
|
+
LocalTime = Class.new(Time)
|
|
7
|
+
|
|
8
|
+
module DatetimeParser
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def offset_datetime(year, mon, day, hour, min, sec, sec_frac, offset)
|
|
12
|
+
validate_date(year, mon, day)
|
|
13
|
+
validate_time(hour, min, sec)
|
|
14
|
+
offset = "+00:00" if offset.casecmp?("Z")
|
|
15
|
+
unless offset[1, 2].to_i.between?(0, 23) && offset[4, 2].to_i.between?(0, 59)
|
|
16
|
+
raise ParseError, "Invalid UTC offset: #{offset}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Time.new(year, mon, day, hour, min, seconds(sec, sec_frac), offset)
|
|
10
20
|
end
|
|
11
|
-
end
|
|
12
21
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
hour, min, sec, sec_frac
|
|
17
|
-
|
|
22
|
+
def local_datetime(year, mon, day, hour, min, sec, sec_frac)
|
|
23
|
+
validate_date(year, mon, day)
|
|
24
|
+
validate_time(hour, min, sec)
|
|
25
|
+
LocalDateTime.utc(year, mon, day, hour, min, seconds(sec, sec_frac))
|
|
26
|
+
end
|
|
18
27
|
|
|
19
|
-
|
|
28
|
+
def local_date(year, mon, day)
|
|
29
|
+
validate_date(year, mon, day)
|
|
30
|
+
LocalDate.utc(year, mon, day)
|
|
20
31
|
end
|
|
21
|
-
end
|
|
22
32
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
Time.local(year, mon, day)
|
|
33
|
+
def local_time(hour, min, sec, sec_frac)
|
|
34
|
+
validate_time(hour, min, sec)
|
|
35
|
+
LocalTime.utc(1970, 1, 1, hour, min, seconds(sec, sec_frac))
|
|
27
36
|
end
|
|
28
|
-
end
|
|
29
37
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
def validate_date(year, mon, day)
|
|
39
|
+
unless Date.valid_date?(year.to_i, mon.to_i, day.to_i, Date::GREGORIAN)
|
|
40
|
+
raise ParseError, "Invalid date: #{year}-#{mon}-#{day}"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def validate_time(hour, min, sec)
|
|
45
|
+
unless hour.to_i.between?(0, 23) && min.to_i.between?(0, 59) && sec.to_i.between?(0, 60)
|
|
46
|
+
raise ParseError, "Invalid time: #{hour}:#{min}:#{sec || 0}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
34
49
|
|
|
35
|
-
|
|
50
|
+
def seconds(sec, sec_frac)
|
|
51
|
+
"#{sec || 0}.#{sec_frac || 0}".to_r
|
|
36
52
|
end
|
|
37
53
|
end
|
|
38
54
|
end
|
data/lib/toml-rb/dumper.rb
CHANGED
|
@@ -19,7 +19,7 @@ module TomlRB
|
|
|
19
19
|
attr_reader :toml_str
|
|
20
20
|
|
|
21
21
|
def initialize(hash)
|
|
22
|
-
@toml_str = ""
|
|
22
|
+
@toml_str = +""
|
|
23
23
|
|
|
24
24
|
visit(hash, [])
|
|
25
25
|
end
|
|
@@ -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
|
|
@@ -67,7 +78,7 @@ module TomlRB
|
|
|
67
78
|
def dump_simple_pairs(simple_pairs)
|
|
68
79
|
simple_pairs.each do |key, val|
|
|
69
80
|
key = quote_key(key) unless bare_key? key
|
|
70
|
-
@toml_str
|
|
81
|
+
@toml_str << "#{key} = #{to_toml(val)}\n"
|
|
71
82
|
end
|
|
72
83
|
end
|
|
73
84
|
|
|
@@ -97,20 +108,34 @@ module TomlRB
|
|
|
97
108
|
new_prefix = prefix.join(".")
|
|
98
109
|
new_prefix = "[" + new_prefix + "]" if extra_brackets
|
|
99
110
|
|
|
100
|
-
@toml_str
|
|
111
|
+
@toml_str << "[" + new_prefix + "]\n"
|
|
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
|
data/lib/toml-rb/inline_table.rb
CHANGED
|
@@ -10,17 +10,9 @@ 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
|
|
20
|
-
|
|
21
|
-
module InlineTableParser
|
|
22
|
-
def value
|
|
23
|
-
TomlRB::InlineTable.new(captures[:keyvalue].map(&:value))
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
18
|
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,16 +36,10 @@ 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
|
|
55
43
|
end
|
|
56
44
|
end
|
|
57
|
-
|
|
58
|
-
# Used in document.citrus
|
|
59
|
-
module KeyvalueParser
|
|
60
|
-
def value
|
|
61
|
-
TomlRB::Keyvalue.new(capture(:stripped_key).value, capture(:v).value)
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
45
|
end
|
data/lib/toml-rb/parser.rb
CHANGED
|
@@ -1,19 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "strscan"
|
|
4
|
+
|
|
1
5
|
module TomlRB
|
|
2
6
|
class Parser
|
|
7
|
+
BASIC_STRING = /(?:[^"\\\x00-\x08\x0a-\x1f\x7f]|\\.)*/
|
|
8
|
+
LITERAL_STRING = /[^'\x00-\x08\x0a-\x1f\x7f]*/
|
|
9
|
+
MULTILINE_STRING = /(?:[^"\\\x00-\x08\x0b-\x1f\x7f]|\r\n|\\(?:[^\r]|\r\n)|"{1,2}(?!"))*(?:"{0,2}(?="""))?/
|
|
10
|
+
MULTILINE_LITERAL = /(?:[^'\x00-\x08\x0b-\x1f\x7f]|\r\n|'{1,2}(?!'))*(?:'{0,2}(?='''))?/
|
|
11
|
+
# A UTF-8 regexp raises Encoding::CompatibilityError on binary input with high bytes.
|
|
12
|
+
COMMENT = /[^\x00-\x08\x0a-\x1f\x7f]*/u
|
|
13
|
+
DATETIME = /(\d{4})-(\d\d)-(\d\d)(?:[Tt ]([0-2]\d):([0-6]\d)(?::([0-6]\d)(?:\.(\d{1,9})\d*)?)?([Zz]|[+-]\d\d:\d\d)?)?/
|
|
14
|
+
TIME = /([0-2]\d):([0-6]\d)(?::([0-6]\d)(?:\.(\d{1,9})\d*)?)?/
|
|
15
|
+
PREFIXED_INTEGER = /0x[0-9a-fA-F](?:_?[0-9a-fA-F])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*/
|
|
16
|
+
DECIMAL = /[+-]?(?:0|[1-9](?:_?\d)*)(\.\d(?:_?\d)*(?:[eE][+-]?\d(?:_?\d)*)?|[eE][+-]?\d(?:_?\d)*)?/
|
|
17
|
+
MAX_NESTING = 100
|
|
18
|
+
|
|
3
19
|
attr_reader :hash
|
|
4
20
|
|
|
5
21
|
def initialize(content, symbolize_keys: false)
|
|
6
22
|
@hash = {}
|
|
7
|
-
|
|
8
|
-
|
|
23
|
+
# Tables and arrays of tables that headers or dotted keys made, by identity:
|
|
24
|
+
# :implicit (super-table of a header), :explicit (header) or :dotted (dotted key).
|
|
25
|
+
# Inline tables and static arrays are absent, so no header or dotted key can enter them.
|
|
26
|
+
@tables = {}.compare_by_identity
|
|
9
27
|
@current = @hash
|
|
10
28
|
@symbolize_keys = symbolize_keys
|
|
29
|
+
@depth = 0
|
|
11
30
|
|
|
12
31
|
begin
|
|
13
|
-
|
|
14
|
-
parsed
|
|
15
|
-
|
|
16
|
-
|
|
32
|
+
@scanner = StringScanner.new(source_text(content))
|
|
33
|
+
# The visitor runs after the whole document is parsed, so a syntax
|
|
34
|
+
# error wins over a key or table that the visitor rejects.
|
|
35
|
+
document.each { |statement| statement.accept_visitor(self) }
|
|
17
36
|
rescue Encoding::CompatibilityError => e
|
|
18
37
|
raise TomlRB::ParseError.new("Encoding error: #{e.message}")
|
|
19
38
|
rescue ArgumentError => e
|
|
@@ -28,20 +47,250 @@ module TomlRB
|
|
|
28
47
|
# Read about the Visitor pattern
|
|
29
48
|
# http://en.wikipedia.org/wiki/Visitor_pattern
|
|
30
49
|
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
|
|
50
|
+
@current = table_array.navigate_keys @hash, @tables, @symbolize_keys
|
|
36
51
|
end
|
|
37
52
|
|
|
38
53
|
def visit_table(table)
|
|
39
|
-
@
|
|
40
|
-
@current = table.navigate_keys @hash, @visited_keys, @symbolize_keys
|
|
54
|
+
@current = table.navigate_keys @hash, @tables, @symbolize_keys
|
|
41
55
|
end
|
|
42
56
|
|
|
43
57
|
def visit_keyvalue(keyvalue)
|
|
44
|
-
keyvalue.assign @current, @
|
|
58
|
+
keyvalue.assign @current, @tables, @symbolize_keys
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def source_text(source)
|
|
64
|
+
if source.respond_to?(:to_path)
|
|
65
|
+
File.read(source.to_path)
|
|
66
|
+
elsif source.respond_to?(:read)
|
|
67
|
+
source.read
|
|
68
|
+
elsif source.respond_to?(:to_str)
|
|
69
|
+
source.to_str
|
|
70
|
+
else
|
|
71
|
+
raise ArgumentError, "Unable to parse from #{source}"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def document
|
|
76
|
+
statements = []
|
|
77
|
+
loop do
|
|
78
|
+
@scanner.skip(/(?:[ \t]*\r?\n)*[ \t]*/)
|
|
79
|
+
return statements if @scanner.eos?
|
|
80
|
+
|
|
81
|
+
statements << statement unless @scanner.match?(/#/)
|
|
82
|
+
line_end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def statement
|
|
87
|
+
if @scanner.match?(/\[/)
|
|
88
|
+
@scanner.match?(/\[\[/) ? table_array : table
|
|
89
|
+
else
|
|
90
|
+
keyvalue
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def table_array
|
|
95
|
+
@scanner.skip(/\[\[[ \t]*/)
|
|
96
|
+
keys = key
|
|
97
|
+
expect(/[ \t]*\]\]/, "']]'")
|
|
98
|
+
TableArray.new(keys)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def table
|
|
102
|
+
@scanner.skip(/\[[ \t]*/)
|
|
103
|
+
keys = key
|
|
104
|
+
expect(/[ \t]*\]/, "']'")
|
|
105
|
+
Table.new(keys)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def keyvalue
|
|
109
|
+
keys = key
|
|
110
|
+
expect(/[ \t]*=[ \t]*/, "'='")
|
|
111
|
+
Keyvalue.new(keys, value)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def key
|
|
115
|
+
keys = [simple_key]
|
|
116
|
+
keys << simple_key while @scanner.skip(/[ \t]*\.[ \t]*/)
|
|
117
|
+
keys
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def simple_key
|
|
121
|
+
if (bare = @scanner.scan(/[A-Za-z0-9_-]+/))
|
|
122
|
+
bare
|
|
123
|
+
elsif @scanner.match?(/"/)
|
|
124
|
+
basic_string
|
|
125
|
+
elsif @scanner.match?(/'/)
|
|
126
|
+
literal_string
|
|
127
|
+
else
|
|
128
|
+
syntax_error("a key")
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def value
|
|
133
|
+
case @scanner.peek(1)
|
|
134
|
+
when '"'
|
|
135
|
+
@scanner.match?(/"""/) ? multiline_string : basic_string
|
|
136
|
+
when "'"
|
|
137
|
+
@scanner.match?(/'''/) ? multiline_literal : literal_string
|
|
138
|
+
when "["
|
|
139
|
+
nested { array }
|
|
140
|
+
when "{"
|
|
141
|
+
nested { inline_table }
|
|
142
|
+
when "t"
|
|
143
|
+
expect(/true/, "a value")
|
|
144
|
+
true
|
|
145
|
+
when "f"
|
|
146
|
+
expect(/false/, "a value")
|
|
147
|
+
false
|
|
148
|
+
else
|
|
149
|
+
datetime || local_time || number
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def basic_string
|
|
154
|
+
@scanner.skip(/"/)
|
|
155
|
+
text = @scanner.scan(BASIC_STRING)
|
|
156
|
+
expect(/"/, %('"'))
|
|
157
|
+
unescape(text)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def literal_string
|
|
161
|
+
@scanner.skip(/'/)
|
|
162
|
+
text = @scanner.scan(LITERAL_STRING)
|
|
163
|
+
expect(/'/, %("'"))
|
|
164
|
+
text
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def multiline_string
|
|
168
|
+
start = @scanner.pos
|
|
169
|
+
@scanner.skip(/"""(?:\r?\n)?/)
|
|
170
|
+
text = @scanner.scan(MULTILINE_STRING)
|
|
171
|
+
@scanner.skip(/"""/) || syntax_error(%('"""' to close the string at #{location(start)}))
|
|
172
|
+
unescape(text)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def multiline_literal
|
|
176
|
+
start = @scanner.pos
|
|
177
|
+
@scanner.skip(/'''(?:\r?\n)?/)
|
|
178
|
+
text = @scanner.scan(MULTILINE_LITERAL)
|
|
179
|
+
@scanner.skip(/'''/) || syntax_error(%("'''" to close the string at #{location(start)}))
|
|
180
|
+
text
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def unescape(text)
|
|
184
|
+
text.include?("\\") ? BasicString.transform_escaped_chars(text) : text
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def datetime
|
|
188
|
+
return unless @scanner.skip(DATETIME)
|
|
189
|
+
|
|
190
|
+
year, mon, day, hour, min, sec, sec_frac, offset = @scanner.values_at(1, 2, 3, 4, 5, 6, 7, 8)
|
|
191
|
+
if hour.nil?
|
|
192
|
+
DatetimeParser.local_date(year, mon, day)
|
|
193
|
+
elsif offset
|
|
194
|
+
DatetimeParser.offset_datetime(year, mon, day, hour, min, sec, sec_frac, offset)
|
|
195
|
+
else
|
|
196
|
+
DatetimeParser.local_datetime(year, mon, day, hour, min, sec, sec_frac)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def local_time
|
|
201
|
+
DatetimeParser.local_time(*@scanner.values_at(1, 2, 3, 4)) if @scanner.skip(TIME)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def number
|
|
205
|
+
if (text = @scanner.scan(PREFIXED_INTEGER))
|
|
206
|
+
Integer(text)
|
|
207
|
+
elsif (text = @scanner.scan(DECIMAL))
|
|
208
|
+
@scanner[1] ? text.to_f : text.to_i
|
|
209
|
+
elsif (text = @scanner.scan(/[+-]?inf/))
|
|
210
|
+
text.start_with?("-") ? -Float::INFINITY : Float::INFINITY
|
|
211
|
+
elsif @scanner.skip(/[+-]?nan/)
|
|
212
|
+
Float::NAN
|
|
213
|
+
else
|
|
214
|
+
syntax_error("a value")
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def array
|
|
219
|
+
@scanner.skip(/\[/)
|
|
220
|
+
values = []
|
|
221
|
+
loop do
|
|
222
|
+
skip_whitespace_and_comments
|
|
223
|
+
return values if @scanner.skip(/\]/)
|
|
224
|
+
|
|
225
|
+
values << value
|
|
226
|
+
skip_whitespace_and_comments
|
|
227
|
+
return values if @scanner.skip(/\]/)
|
|
228
|
+
|
|
229
|
+
expect(/,/, "',' or ']'")
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def inline_table
|
|
234
|
+
@scanner.skip(/\{/)
|
|
235
|
+
pairs = []
|
|
236
|
+
loop do
|
|
237
|
+
skip_whitespace_and_comments
|
|
238
|
+
return InlineTable.new(pairs) if @scanner.skip(/\}/)
|
|
239
|
+
|
|
240
|
+
pairs << keyvalue
|
|
241
|
+
skip_whitespace_and_comments
|
|
242
|
+
return InlineTable.new(pairs) if @scanner.skip(/\}/)
|
|
243
|
+
|
|
244
|
+
expect(/,/, "',' or '}'")
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Each nested array or inline table recurses, so the limit stops a deep
|
|
249
|
+
# document before it exhausts the stack.
|
|
250
|
+
def nested
|
|
251
|
+
@depth += 1
|
|
252
|
+
syntax_error("at most #{MAX_NESTING} nested arrays and inline tables") if @depth > MAX_NESTING
|
|
253
|
+
yield
|
|
254
|
+
ensure
|
|
255
|
+
@depth -= 1
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def line_end
|
|
259
|
+
return if @scanner.skip(/[ \t]*\r?\n/)
|
|
260
|
+
|
|
261
|
+
@scanner.skip(/[ \t]*/)
|
|
262
|
+
@scanner.skip(COMMENT) if @scanner.skip(/#/)
|
|
263
|
+
new_line
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def skip_whitespace_and_comments
|
|
267
|
+
@scanner.skip(/[ \t\r\n]*/)
|
|
268
|
+
while @scanner.skip(/#/)
|
|
269
|
+
@scanner.skip(COMMENT)
|
|
270
|
+
new_line
|
|
271
|
+
@scanner.skip(/[ \t\r\n]*/)
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def new_line
|
|
276
|
+
@scanner.skip(/\r?\n/) || @scanner.eos? || syntax_error("a new line")
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def expect(pattern, expected)
|
|
280
|
+
@scanner.skip(pattern) || syntax_error(expected)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def syntax_error(expected)
|
|
284
|
+
@scanner.skip(/[ \t]*/)
|
|
285
|
+
found = @scanner.eos? ? "end of input" : @scanner.check(/./m).inspect
|
|
286
|
+
raise ParseError, "Unexpected #{found} at #{location(@scanner.pos)}: expected #{expected}"
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def location(pos)
|
|
290
|
+
consumed = @scanner.string.byteslice(0, pos)
|
|
291
|
+
line = consumed.count("\n") + 1
|
|
292
|
+
column = consumed.length - (consumed.rindex("\n") || -1)
|
|
293
|
+
"line #{line}, column #{column}"
|
|
45
294
|
end
|
|
46
295
|
end
|
|
47
296
|
end
|
data/lib/toml-rb/string.rb
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
module TomlRB
|
|
2
|
-
# Used in primitive.citrus
|
|
3
2
|
module BasicString
|
|
4
3
|
SPECIAL_CHARS = {
|
|
5
|
-
"\\0" => "\0",
|
|
6
4
|
"\\t" => "\t",
|
|
7
5
|
"\\b" => "\b",
|
|
6
|
+
"\\e" => "\e",
|
|
8
7
|
"\\f" => "\f",
|
|
9
8
|
"\\n" => "\n",
|
|
10
9
|
"\\r" => "\r",
|
|
@@ -12,21 +11,22 @@ module TomlRB
|
|
|
12
11
|
"\\\\" => "\\"
|
|
13
12
|
}.freeze
|
|
14
13
|
|
|
15
|
-
def value
|
|
16
|
-
aux = TomlRB::BasicString.transform_escaped_chars first.value
|
|
17
|
-
|
|
18
|
-
aux[1...-1]
|
|
19
|
-
end
|
|
20
|
-
|
|
21
14
|
# Replace the unicode escaped characters with the corresponding character
|
|
22
15
|
# e.g. \u03B4 => ?
|
|
23
16
|
def self.decode_unicode(str)
|
|
24
|
-
|
|
17
|
+
code = str[2..-1].to_i(16)
|
|
18
|
+
if code.between?(0xD800, 0xDFFF) || code > 0x10FFFF
|
|
19
|
+
fail ParseError.new "Escape sequence #{str} is not a Unicode scalar value"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
[code].pack("U")
|
|
25
23
|
end
|
|
26
24
|
|
|
27
25
|
def self.transform_escaped_chars(str)
|
|
28
|
-
str.gsub(/\\(u[\da-fA-F]{4}|U[\da-fA-F]{8}
|
|
29
|
-
if m.
|
|
26
|
+
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|
|
|
27
|
+
if m.include?("\n")
|
|
28
|
+
""
|
|
29
|
+
elsif m.size == 2
|
|
30
30
|
SPECIAL_CHARS[m] || parse_error(m)
|
|
31
31
|
else
|
|
32
32
|
decode_unicode(m).force_encoding("UTF-8")
|
|
@@ -38,31 +38,4 @@ module TomlRB
|
|
|
38
38
|
fail ParseError.new "Escape sequence #{m} is reserved"
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
|
-
|
|
42
|
-
module LiteralString
|
|
43
|
-
def value
|
|
44
|
-
first.value[1...-1]
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
module MultilineString
|
|
49
|
-
def value
|
|
50
|
-
return "" if captures[:text].empty?
|
|
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
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
module MultilineLiteral
|
|
61
|
-
def value
|
|
62
|
-
return "" if captures[:text].empty?
|
|
63
|
-
aux = captures[:text].first.value
|
|
64
|
-
|
|
65
|
-
aux.gsub(/\\\r?\n[\n\t\r ]*/, "")
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
41
|
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,20 +30,5 @@ 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
|
-
end
|
|
44
|
-
|
|
45
|
-
# Used in document.citrus
|
|
46
|
-
module TableParser
|
|
47
|
-
def value
|
|
48
|
-
TomlRB::Table.new(captures[:stripped_key].map(&:value).first)
|
|
49
|
-
end
|
|
50
33
|
end
|
|
51
34
|
end
|
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,25 +25,19 @@ 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
|
-
end
|
|
43
|
-
|
|
44
|
-
# Used in document.citrus
|
|
45
|
-
module TableArrayParser
|
|
46
|
-
def value
|
|
47
|
-
TomlRB::TableArray.new(captures[:stripped_key].map(&:value).first)
|
|
48
|
-
end
|
|
49
42
|
end
|
|
50
43
|
end
|
data/lib/toml-rb/version.rb
CHANGED
data/lib/toml-rb.rb
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
require "citrus"
|
|
2
|
-
|
|
3
1
|
require_relative "toml-rb/errors"
|
|
4
|
-
require_relative "toml-rb/array"
|
|
5
2
|
require_relative "toml-rb/string"
|
|
6
3
|
require_relative "toml-rb/datetime"
|
|
7
4
|
require_relative "toml-rb/table"
|
|
@@ -11,12 +8,6 @@ require_relative "toml-rb/keyvalue"
|
|
|
11
8
|
require_relative "toml-rb/parser"
|
|
12
9
|
require_relative "toml-rb/dumper"
|
|
13
10
|
|
|
14
|
-
File.dirname(File.expand_path(__FILE__)).tap do |root|
|
|
15
|
-
Citrus.load "#{root}/toml-rb/grammars/helper.citrus"
|
|
16
|
-
Citrus.load "#{root}/toml-rb/grammars/primitive.citrus"
|
|
17
|
-
Citrus.load "#{root}/toml-rb/grammars/document.citrus"
|
|
18
|
-
end
|
|
19
|
-
|
|
20
11
|
module TomlRB
|
|
21
12
|
# Public: Returns a hash from *TomlRB* content.
|
|
22
13
|
#
|
|
@@ -67,7 +58,7 @@ module TomlRB
|
|
|
67
58
|
# Raises Errno::ENOENT if the file cannot be found.
|
|
68
59
|
# Raises Errno::EACCES if the file cannot be accessed.
|
|
69
60
|
def self.load_file(path, symbolize_keys: false)
|
|
70
|
-
TomlRB.parse(File.read(path), symbolize_keys: symbolize_keys)
|
|
61
|
+
TomlRB.parse(File.read(path, encoding: "UTF-8"), symbolize_keys: symbolize_keys)
|
|
71
62
|
end
|
|
72
63
|
|
|
73
64
|
# Public: Returns a *TomlRB* string from a Ruby Hash.
|
metadata
CHANGED
|
@@ -1,49 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: toml-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 6.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Emiliano Mancuso
|
|
8
8
|
- Lucas Tolchinsky
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: citrus
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - "~>"
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '3.0'
|
|
20
|
-
- - ">"
|
|
21
|
-
- !ruby/object:Gem::Version
|
|
22
|
-
version: '3.0'
|
|
23
|
-
type: :runtime
|
|
24
|
-
prerelease: false
|
|
25
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
-
requirements:
|
|
27
|
-
- - "~>"
|
|
28
|
-
- !ruby/object:Gem::Version
|
|
29
|
-
version: '3.0'
|
|
30
|
-
- - ">"
|
|
31
|
-
- !ruby/object:Gem::Version
|
|
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
13
|
- !ruby/object:Gem::Dependency
|
|
48
14
|
name: minitest
|
|
49
15
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -86,7 +52,7 @@ dependencies:
|
|
|
86
52
|
- - ">="
|
|
87
53
|
- !ruby/object:Gem::Version
|
|
88
54
|
version: '0'
|
|
89
|
-
description:
|
|
55
|
+
description: A TOML 1.1.0 parser and dumper.
|
|
90
56
|
email:
|
|
91
57
|
- emiliano.mancuso@gmail.com
|
|
92
58
|
- lucas.tolchinsky@gmail.com
|
|
@@ -97,13 +63,9 @@ files:
|
|
|
97
63
|
- LICENSE
|
|
98
64
|
- README.md
|
|
99
65
|
- lib/toml-rb.rb
|
|
100
|
-
- lib/toml-rb/array.rb
|
|
101
66
|
- lib/toml-rb/datetime.rb
|
|
102
67
|
- lib/toml-rb/dumper.rb
|
|
103
68
|
- lib/toml-rb/errors.rb
|
|
104
|
-
- lib/toml-rb/grammars/document.citrus
|
|
105
|
-
- lib/toml-rb/grammars/helper.citrus
|
|
106
|
-
- lib/toml-rb/grammars/primitive.citrus
|
|
107
69
|
- lib/toml-rb/inline_table.rb
|
|
108
70
|
- lib/toml-rb/keyvalue.rb
|
|
109
71
|
- lib/toml-rb/parser.rb
|
data/lib/toml-rb/array.rb
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
grammar TomlRB::Document
|
|
2
|
-
include TomlRB::Primitive
|
|
3
|
-
|
|
4
|
-
rule document
|
|
5
|
-
(comment | table_array | table | keyvalue | line_break)*
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
rule table_array
|
|
9
|
-
(space? '[[' stripped_key ']]' comment?) <TomlRB::TableArrayParser>
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
rule table
|
|
13
|
-
(space? '[' stripped_key ']' comment?) <TomlRB::TableParser>
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
rule keyvalue
|
|
17
|
-
(stripped_key '=' space? v:(toml_values) comment? space) <TomlRB::KeyvalueParser>
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
### Values
|
|
21
|
-
|
|
22
|
-
rule inline_table
|
|
23
|
-
(space? '{' array_comments (keyvalue (array_comments ',' array_comments keyvalue)* (array_comments ',')?)? array_comments '}' ) <TomlRB::InlineTableParser>
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
rule array
|
|
27
|
-
("[" array_comments (array_elements)? space ","? array_comments "]" indent?) <TomlRB::ArrayParser>
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
rule array_array
|
|
31
|
-
(array (space "," array_comments array)*) {
|
|
32
|
-
captures[:array].map(&:value)
|
|
33
|
-
}
|
|
34
|
-
end
|
|
35
|
-
|
|
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
|
-
}
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
### Helpers
|
|
45
|
-
|
|
46
|
-
rule stripped_key
|
|
47
|
-
(space? key space?) { captures[:key].first.value }
|
|
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
|
|
57
|
-
end
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
grammar TomlRB::Helper
|
|
2
|
-
rule comment
|
|
3
|
-
(space? "#" (~line_break)* line_break?) { nil }
|
|
4
|
-
end
|
|
5
|
-
|
|
6
|
-
rule space
|
|
7
|
-
[ \t]*
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
rule indent
|
|
11
|
-
[ \t\r\n]*
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
rule line_break
|
|
15
|
-
(space "\n" space | space "\r\n" space) { nil }
|
|
16
|
-
end
|
|
17
|
-
end
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
grammar TomlRB::Primitive
|
|
2
|
-
include TomlRB::Helper
|
|
3
|
-
|
|
4
|
-
rule primitive
|
|
5
|
-
datetime | bool | number | string
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
##
|
|
9
|
-
# String rules
|
|
10
|
-
##
|
|
11
|
-
|
|
12
|
-
rule string
|
|
13
|
-
multiline_string | multiline_literal | basic_string | literal_string
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
rule basic_string
|
|
17
|
-
(/(["])(?:\\?.)*?\1/ space) <TomlRB::BasicString>
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
rule literal_string
|
|
21
|
-
(/(['])(?:\\?.)*?\1/ space) <TomlRB::LiteralString>
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
rule multiline_string
|
|
25
|
-
('"""' line_break* (text:~('"""' !'"')|'') '"""' space) <TomlRB::MultilineString>
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
rule multiline_literal
|
|
29
|
-
("'''" line_break* (text:~("'''" !"'")|'') "'''" space) <TomlRB::MultilineLiteral>
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
##
|
|
33
|
-
# Date time rules
|
|
34
|
-
##
|
|
35
|
-
|
|
36
|
-
rule datetime
|
|
37
|
-
offset_datetime | local_datetime | local_date | local_time
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
rule offset_datetime
|
|
41
|
-
( skeleton:datetime_skeleton ("Z" | date_offset) ) <TomlRB::OffsetDateTimeParser>
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
rule local_datetime
|
|
45
|
-
datetime_skeleton <TomlRB::LocalDateTimeParser>
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
rule local_date
|
|
49
|
-
(date_skeleton space) <TomlRB::LocalDateParser>
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
rule local_time
|
|
53
|
-
(time_skeleton space) <TomlRB::LocalTimeParser>
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
rule datetime_skeleton
|
|
57
|
-
(date_skeleton ("T"|" ") time_skeleton) {
|
|
58
|
-
capture(:date_skeleton).value + capture(:time_skeleton).value
|
|
59
|
-
}
|
|
60
|
-
end
|
|
61
|
-
|
|
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
|
-
}
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
rule time_skeleton
|
|
69
|
-
( hour:([0-2][0-9]) ":" mim:([0-6][0-9]) ":" sec:([0-6][0-9]) ([,\.] sec_frac:(/\d/1*6))? ) {
|
|
70
|
-
[:hour,:mim,:sec].map{ |s| capture(s).value } + [capture(:sec_frac) || '0']
|
|
71
|
-
}
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
rule date_offset
|
|
75
|
-
offset:(sign /\d\d/ ":" /\d\d/)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
##
|
|
79
|
-
# Number rules
|
|
80
|
-
##
|
|
81
|
-
|
|
82
|
-
rule number
|
|
83
|
-
float | integer
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
rule float
|
|
87
|
-
inf | nan | exponential_float | fractional_float
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
rule inf
|
|
91
|
-
(s:sign? 'inf') {
|
|
92
|
-
sign = (capture(:s).value != '-') ? 1 : -1
|
|
93
|
-
sign * Float::INFINITY
|
|
94
|
-
}
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
rule nan
|
|
98
|
-
(sign? 'nan') { Float::NAN }
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
rule exponential_float
|
|
102
|
-
((fractional_float | decimal_integer) [eE] decimal_integer) { to_str.to_f }
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
rule fractional_float
|
|
106
|
-
(decimal_integer '.' [0-9] ([0-9] | '_' [0-9])*) {
|
|
107
|
-
to_str.to_f
|
|
108
|
-
}
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
rule integer
|
|
112
|
-
hexadecimal_integer | octal_integer | binary_integer | decimal_integer
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
rule hexadecimal_integer
|
|
116
|
-
('0x' [a-fA-F0-9] ([a-fA-F0-9] | '_' [a-fA-F0-9])*) { to_str.to_i(16) }
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
rule octal_integer
|
|
120
|
-
('0o' [0-7] ([0-7] | '_' [0-7])*) { to_str.to_i(8) }
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
rule binary_integer
|
|
124
|
-
('0b' [01] ([01] | '_' [01])*) { to_str.to_i(2) }
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
rule decimal_integer
|
|
128
|
-
(sign? [0-9] ([0-9] | '_' [0-9])*) { to_str.to_i }
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
rule sign
|
|
132
|
-
'+' | '-'
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
##
|
|
136
|
-
# Boolean rules
|
|
137
|
-
##
|
|
138
|
-
|
|
139
|
-
rule bool
|
|
140
|
-
true | false
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
rule true
|
|
144
|
-
'true' { true }
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
rule false
|
|
148
|
-
'false' { false }
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
##
|
|
152
|
-
# Key rules
|
|
153
|
-
##
|
|
154
|
-
|
|
155
|
-
rule key
|
|
156
|
-
dotted_key
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
rule bare_key
|
|
160
|
-
[a-zA-Z0-9_-]+
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
rule quoted_key
|
|
164
|
-
basic_string | literal_string
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
rule single_key
|
|
168
|
-
quoted_key | bare_key
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
rule dotted_key
|
|
172
|
-
(space? single_key space? ("." space? single_key space?)* ) {
|
|
173
|
-
captures[:single_key].map(&:value)
|
|
174
|
-
}
|
|
175
|
-
end
|
|
176
|
-
end
|