tomlrb 1.3.0 → 2.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/lib/tomlrb.rb +3 -0
- data/lib/tomlrb/generated_parser.rb +425 -248
- data/lib/tomlrb/handler.rb +155 -2
- data/lib/tomlrb/local_date.rb +33 -0
- data/lib/tomlrb/local_date_time.rb +40 -0
- data/lib/tomlrb/local_time.rb +38 -0
- data/lib/tomlrb/parser.y +83 -21
- data/lib/tomlrb/scanner.rb +49 -15
- data/lib/tomlrb/string_utils.rb +7 -1
- data/lib/tomlrb/version.rb +1 -1
- metadata +6 -3
data/lib/tomlrb/handler.rb
CHANGED
@@ -7,10 +7,14 @@ module Tomlrb
|
|
7
7
|
@current = @output
|
8
8
|
@stack = []
|
9
9
|
@array_names = []
|
10
|
+
@current_table = []
|
11
|
+
@keys = Keys.new
|
10
12
|
@symbolize_keys = options[:symbolize_keys]
|
11
13
|
end
|
12
14
|
|
13
15
|
def set_context(identifiers, is_array_of_tables: false)
|
16
|
+
@current_table = identifiers.dup
|
17
|
+
@keys.add_table_key identifiers, is_array_of_tables
|
14
18
|
@current = @output
|
15
19
|
|
16
20
|
deal_with_array_of_tables(identifiers, is_array_of_tables) do |identifierz|
|
@@ -43,14 +47,19 @@ module Tomlrb
|
|
43
47
|
if is_array_of_tables
|
44
48
|
last_identifier = last_identifier.to_sym if @symbolize_keys
|
45
49
|
@current[last_identifier] ||= []
|
50
|
+
raise ParseError, "Cannot use key #{last_identifier} for both table and array at once" unless @current[last_identifier].respond_to?(:<<)
|
46
51
|
@current[last_identifier] << {}
|
47
52
|
@current = @current[last_identifier].last
|
48
53
|
end
|
49
54
|
end
|
50
55
|
|
51
56
|
def assign(k)
|
52
|
-
|
53
|
-
|
57
|
+
@keys.add_pair_key k, @current_table
|
58
|
+
current = @current
|
59
|
+
while key = k.shift
|
60
|
+
key = key.to_sym if @symbolize_keys
|
61
|
+
current = assign_key_path(current, key, k.empty?)
|
62
|
+
end
|
54
63
|
end
|
55
64
|
|
56
65
|
def push(o)
|
@@ -69,5 +78,149 @@ module Tomlrb
|
|
69
78
|
end
|
70
79
|
array
|
71
80
|
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def assign_key_path(current, key, key_emptied)
|
85
|
+
if key_emptied
|
86
|
+
raise ParseError, "Cannot overwrite value with key #{key}" unless current.kind_of?(Hash)
|
87
|
+
current[key] = @stack.pop
|
88
|
+
return current
|
89
|
+
end
|
90
|
+
current[key] ||= {}
|
91
|
+
current = current[key]
|
92
|
+
current
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class Keys
|
97
|
+
def initialize
|
98
|
+
@keys = {}
|
99
|
+
end
|
100
|
+
|
101
|
+
def add_table_key(keys, is_array_of_tables = false)
|
102
|
+
self << [keys, [], is_array_of_tables]
|
103
|
+
end
|
104
|
+
|
105
|
+
def add_pair_key(keys, context)
|
106
|
+
self << [context, keys, false]
|
107
|
+
end
|
108
|
+
|
109
|
+
def <<(keys)
|
110
|
+
table_keys, pair_keys, is_array_of_tables = keys
|
111
|
+
current = @keys
|
112
|
+
current = append_table_keys(current, table_keys, pair_keys.empty?, is_array_of_tables)
|
113
|
+
append_pair_keys(current, pair_keys, table_keys.empty?, is_array_of_tables)
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def append_table_keys(current, table_keys, pair_keys_empty, is_array_of_tables)
|
119
|
+
table_keys.each_with_index do |key, index|
|
120
|
+
declared = (index == table_keys.length - 1) && pair_keys_empty
|
121
|
+
if index == 0
|
122
|
+
current = find_or_create_first_table_key(current, key, declared, is_array_of_tables)
|
123
|
+
else
|
124
|
+
current = current << [key, :table, declared, is_array_of_tables]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
current
|
129
|
+
end
|
130
|
+
|
131
|
+
def find_or_create_first_table_key(current, key, declared, is_array_of_tables)
|
132
|
+
existed = current[key]
|
133
|
+
if existed && existed.type == :pair
|
134
|
+
raise Key::KeyConflict, "Key #{key} is already used as #{existed.type} key"
|
135
|
+
end
|
136
|
+
if existed && existed.declared? && declared && ! is_array_of_tables
|
137
|
+
raise Key::KeyConflict, "Key #{key} is already used"
|
138
|
+
end
|
139
|
+
k = existed || Key.new(key, :table, declared)
|
140
|
+
current[key] = k
|
141
|
+
k
|
142
|
+
end
|
143
|
+
|
144
|
+
def append_pair_keys(current, pair_keys, table_keys_empty, is_array_of_tables)
|
145
|
+
pair_keys.each_with_index do |key, index|
|
146
|
+
declared = index == pair_keys.length - 1
|
147
|
+
if index == 0 && table_keys_empty
|
148
|
+
current = find_or_create_first_pair_key(current, key, declared, table_keys_empty)
|
149
|
+
else
|
150
|
+
key = current << [key, :pair, declared, is_array_of_tables]
|
151
|
+
current = key
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def find_or_create_first_pair_key(current, key, declared, table_keys_empty)
|
157
|
+
existed = current[key]
|
158
|
+
if existed && existed.declared? && (existed.type == :pair) && declared && table_keys_empty
|
159
|
+
raise Key::KeyConflict, "Key #{key} is already used"
|
160
|
+
end
|
161
|
+
k = Key.new(key, :pair, declared)
|
162
|
+
current[key] = k
|
163
|
+
k
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class Key
|
168
|
+
class KeyConflict < ParseError; end
|
169
|
+
|
170
|
+
attr_reader :key, :type
|
171
|
+
|
172
|
+
def initialize(key, type, declared = false)
|
173
|
+
@key = key
|
174
|
+
@type = type
|
175
|
+
@declared = declared
|
176
|
+
@children = {}
|
177
|
+
end
|
178
|
+
|
179
|
+
def declared?
|
180
|
+
@declared
|
181
|
+
end
|
182
|
+
|
183
|
+
def <<(key_type_declared)
|
184
|
+
key, type, declared, is_array_of_tables = key_type_declared
|
185
|
+
existed = @children[key]
|
186
|
+
validate_already_declared_as_different_key(type, declared, existed)
|
187
|
+
validate_already_declared_as_non_array_table(type, is_array_of_tables, declared, existed)
|
188
|
+
validate_path_already_created_as_different_type(type, declared, existed)
|
189
|
+
validate_path_already_declared_as_different_type(type, declared, existed)
|
190
|
+
validate_already_declared_as_same_key(declared, existed)
|
191
|
+
@children[key] = existed || self.class.new(key, type, declared)
|
192
|
+
end
|
193
|
+
|
194
|
+
private
|
195
|
+
|
196
|
+
def validate_already_declared_as_different_key(type, declared, existed)
|
197
|
+
if declared && existed && existed.declared? && existed.type != type
|
198
|
+
raise KeyConflict, "Key #{existed.key} is already used as #{existed.type} key"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def validate_already_declared_as_non_array_table(type, is_array_of_tables, declared, existed)
|
203
|
+
if declared && type == :table && existed && existed.declared? && ! is_array_of_tables
|
204
|
+
raise KeyConflict, "Key #{existed.key} is already used"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def validate_path_already_created_as_different_type(type, declared, existed)
|
209
|
+
if declared && (type == :table) && existed && (existed.type == :pair) && (! existed.declared?)
|
210
|
+
raise KeyConflict, "Key #{existed.key} is already used as #{existed.type} key"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def validate_path_already_declared_as_different_type(type, declared, existed)
|
215
|
+
if ! declared && (type == :pair) && existed && (existed.type == :pair) && existed.declared?
|
216
|
+
raise KeyConflict, "Key #{key} is already used as #{type} key"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def validate_already_declared_as_same_key(declared, existed)
|
221
|
+
if existed && ! existed.declared? && declared
|
222
|
+
raise KeyConflict, "Key #{existed.key} is already used as #{existed.type} key"
|
223
|
+
end
|
224
|
+
end
|
72
225
|
end
|
73
226
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Tomlrb
|
4
|
+
class LocalDate
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def_delegators :@time, :year, :month, :day
|
8
|
+
|
9
|
+
def initialize(year, month, day)
|
10
|
+
@time = Time.new(year, month, day, 0, 0, 0, '-00:00')
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param offset see {LocalDateTime#to_time}
|
14
|
+
# @return [Time] 00:00:00 of the date
|
15
|
+
def to_time(offset='-00:00')
|
16
|
+
return @time if offset == '-00:00'
|
17
|
+
Time.new(year, month, day, 0, 0, 0, offset)
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
@time.strftime('%F')
|
22
|
+
end
|
23
|
+
|
24
|
+
def ==(other)
|
25
|
+
other.respond_to?(:to_time) &&
|
26
|
+
to_time == other.to_time
|
27
|
+
end
|
28
|
+
|
29
|
+
def inspect
|
30
|
+
"#<#{self.class}: #{to_s}>"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Tomlrb
|
4
|
+
class LocalDateTime
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def_delegators :@time, :year, :month, :day, :hour, :min, :sec, :usec, :nsec
|
8
|
+
|
9
|
+
def initialize(year, month, day, hour, min, sec) # rubocop:disable Metrics/ParameterLists
|
10
|
+
@time = Time.new(year, month, day, hour, min, sec, '-00:00')
|
11
|
+
@sec = sec
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param offset [String, Symbol, Numeric, nil] time zone offset.
|
15
|
+
# * when +String+, must be '+HH:MM' format, '-HH:MM' format, 'UTC', 'A'..'I' or 'K'..'Z'. Arguments excluding '+-HH:MM' are supporeted at Ruby >= 2.7.0
|
16
|
+
# * when +Symbol+, must be +:dst+(for summar time for local) or +:std+(for standard time).
|
17
|
+
# * when +Numeric+, it is time zone offset in second.
|
18
|
+
# * when +nil+, local time zone offset is used.
|
19
|
+
# @return [Time]
|
20
|
+
def to_time(offset='-00:00')
|
21
|
+
return @time if offset == '-00:00'
|
22
|
+
Time.new(year, month, day, hour, min, @sec, offset)
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
frac = (@sec - sec)
|
27
|
+
frac_str = frac == 0 ? '' : "#{frac.to_s[1..-1]}"
|
28
|
+
@time.strftime("%FT%T") << frac_str
|
29
|
+
end
|
30
|
+
|
31
|
+
def ==(other)
|
32
|
+
other.respond_to?(:to_time) &&
|
33
|
+
to_time == other.to_time
|
34
|
+
end
|
35
|
+
|
36
|
+
def inspect
|
37
|
+
"#<#{self.class}: #{to_s}>"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Tomlrb
|
4
|
+
class LocalTime
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def_delegators :@time, :hour, :min, :sec, :usec, :nsec
|
8
|
+
|
9
|
+
def initialize(hour, min, sec)
|
10
|
+
@time = Time.new(0, 1, 1, hour, min, sec, '-00:00')
|
11
|
+
@sec = sec
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param year [Integer]
|
15
|
+
# @param month [Integer]
|
16
|
+
# @param day [Integer]
|
17
|
+
# @param offset see {LocalDateTime#to_time}
|
18
|
+
# @return [Time] the time of the date specified by params
|
19
|
+
def to_time(year, month, day, offset='-00:00')
|
20
|
+
Time.new(year, month, day, hour, min, @sec, offset)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
frac = (@sec - sec)
|
25
|
+
frac_str = frac == 0 ? '' : "#{frac.to_s[1..-1]}"
|
26
|
+
@time.strftime("%T") << frac_str
|
27
|
+
end
|
28
|
+
|
29
|
+
def ==(other)
|
30
|
+
other.respond_to?(:to_time) &&
|
31
|
+
@time == other.to_time(0, 1, 1)
|
32
|
+
end
|
33
|
+
|
34
|
+
def inspect
|
35
|
+
"#<#{self.class}: #{to_s}>"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/tomlrb/parser.y
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
class Tomlrb::GeneratedParser
|
2
|
-
token IDENTIFIER STRING_MULTI STRING_BASIC STRING_LITERAL_MULTI STRING_LITERAL DATETIME INTEGER FLOAT FLOAT_INF FLOAT_NAN TRUE FALSE
|
2
|
+
token IDENTIFIER STRING_MULTI STRING_BASIC STRING_LITERAL_MULTI STRING_LITERAL DATETIME LOCAL_DATETIME LOCAL_DATE LOCAL_TIME INTEGER HEX_INTEGER OCT_INTEGER BIN_INTEGER FLOAT FLOAT_INF FLOAT_NAN TRUE FALSE NEWLINE EOS
|
3
3
|
rule
|
4
4
|
expressions
|
5
5
|
| expressions expression
|
6
|
+
| expressions EOS
|
6
7
|
;
|
7
8
|
expression
|
8
9
|
: table
|
9
10
|
| assignment
|
10
11
|
| inline_table
|
12
|
+
| NEWLINE
|
11
13
|
;
|
12
14
|
table
|
13
15
|
: table_start table_continued
|
@@ -27,44 +29,95 @@ rule
|
|
27
29
|
| '.' table_continued
|
28
30
|
;
|
29
31
|
table_identifier
|
30
|
-
:
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
: table_identifier '.' table_identifier_component { @handler.push(val[2]) }
|
33
|
+
| table_identifier '.' FLOAT { val[2].split('.').each { |k| @handler.push(k) } }
|
34
|
+
| FLOAT {
|
35
|
+
keys = val[0].split('.')
|
36
|
+
@handler.start_(:table)
|
37
|
+
keys.each { |key| @handler.push(key) }
|
38
|
+
}
|
39
|
+
| table_identifier_component { @handler.push(val[0]) }
|
40
|
+
;
|
41
|
+
table_identifier_component
|
42
|
+
: IDENTIFIER
|
43
|
+
| STRING_BASIC
|
44
|
+
| STRING_LITERAL
|
45
|
+
| INTEGER
|
46
|
+
| HEX_INTEGER
|
47
|
+
| OCT_INTEGER
|
48
|
+
| BIN_INTEGER
|
49
|
+
| FLOAT_INF
|
50
|
+
| FLOAT_NAN
|
51
|
+
| TRUE
|
52
|
+
| FALSE
|
36
53
|
;
|
37
54
|
inline_table
|
38
|
-
: inline_table_start
|
55
|
+
: inline_table_start inline_table_end
|
56
|
+
| inline_table_start inline_continued inline_table_end
|
39
57
|
;
|
40
58
|
inline_table_start
|
41
59
|
: '{' { @handler.start_(:inline) }
|
42
60
|
;
|
43
|
-
|
44
|
-
: '}' { array = @handler.end_(:inline); @handler.push(Hash[*array]) }
|
45
|
-
| inline_assignment_key inline_assignment_value inline_next
|
46
|
-
;
|
47
|
-
inline_next
|
61
|
+
inline_table_end
|
48
62
|
: '}' {
|
49
63
|
array = @handler.end_(:inline)
|
50
64
|
array.map!.with_index{ |n,i| i.even? ? n.to_sym : n } if @handler.symbolize_keys
|
51
65
|
@handler.push(Hash[*array])
|
52
66
|
}
|
53
|
-
|
67
|
+
;
|
68
|
+
inline_continued
|
69
|
+
: inline_assignment_key inline_assignment_value
|
70
|
+
| inline_assignment_key inline_assignment_value inline_next
|
71
|
+
;
|
72
|
+
inline_next
|
73
|
+
: ',' inline_continued
|
54
74
|
;
|
55
75
|
inline_assignment_key
|
56
|
-
: IDENTIFIER {
|
76
|
+
: inline_assignment_key '.' IDENTIFIER {
|
77
|
+
array = @handler.end_(:inline)
|
78
|
+
array.each { |key| @handler.push(key) }
|
79
|
+
@handler.start_(:inline)
|
80
|
+
@handler.push(val[2])
|
81
|
+
}
|
82
|
+
| IDENTIFIER { @handler.push(val[0]) }
|
57
83
|
;
|
58
84
|
inline_assignment_value
|
59
85
|
: '=' value
|
60
86
|
;
|
61
87
|
assignment
|
62
|
-
:
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
|
88
|
+
: assignment_key '=' value EOS {
|
89
|
+
keys = @handler.end_(:keys)
|
90
|
+
@handler.push(keys.pop)
|
91
|
+
@handler.assign(keys)
|
92
|
+
}
|
93
|
+
| assignment_key '=' value NEWLINE {
|
94
|
+
keys = @handler.end_(:keys)
|
95
|
+
@handler.push(keys.pop)
|
96
|
+
@handler.assign(keys)
|
97
|
+
}
|
98
|
+
;
|
99
|
+
assignment_key
|
100
|
+
: assignment_key '.' assignment_key_component { @handler.push(val[2]) }
|
101
|
+
| assignment_key '.' FLOAT { val[2].split('.').each { |k| @handler.push(k) } }
|
102
|
+
| FLOAT {
|
103
|
+
keys = val[0].split('.')
|
104
|
+
@handler.start_(:keys)
|
105
|
+
keys.each { |key| @handler.push(key) }
|
106
|
+
}
|
107
|
+
| assignment_key_component { @handler.start_(:keys); @handler.push(val[0]) }
|
108
|
+
;
|
109
|
+
assignment_key_component
|
110
|
+
: IDENTIFIER
|
111
|
+
| STRING_BASIC
|
112
|
+
| STRING_LITERAL
|
113
|
+
| INTEGER
|
114
|
+
| HEX_INTEGER
|
115
|
+
| OCT_INTEGER
|
116
|
+
| BIN_INTEGER
|
117
|
+
| FLOAT_INF
|
118
|
+
| FLOAT_NAN
|
119
|
+
| TRUE
|
120
|
+
| FALSE
|
68
121
|
;
|
69
122
|
array
|
70
123
|
: start_array array_continued
|
@@ -72,10 +125,13 @@ rule
|
|
72
125
|
array_continued
|
73
126
|
: ']' { array = @handler.end_(:array); @handler.push(array) }
|
74
127
|
| value array_next
|
128
|
+
| NEWLINE array_continued
|
75
129
|
;
|
76
130
|
array_next
|
77
131
|
: ']' { array = @handler.end_(:array); @handler.push(array) }
|
78
132
|
| ',' array_continued
|
133
|
+
| NEWLINE array_continued
|
134
|
+
| ',' NEWLINE array_continued
|
79
135
|
;
|
80
136
|
start_array
|
81
137
|
: '[' { @handler.start_(:array) }
|
@@ -94,9 +150,15 @@ rule
|
|
94
150
|
| FLOAT_INF { result = (val[0][0] == '-' ? -1 : 1) * Float::INFINITY }
|
95
151
|
| FLOAT_NAN { result = Float::NAN }
|
96
152
|
| INTEGER { result = val[0].to_i }
|
153
|
+
| HEX_INTEGER { result = val[0].to_i(16) }
|
154
|
+
| OCT_INTEGER { result = val[0].to_i(8) }
|
155
|
+
| BIN_INTEGER { result = val[0].to_i(2) }
|
97
156
|
| TRUE { result = true }
|
98
157
|
| FALSE { result = false }
|
99
158
|
| DATETIME { result = Time.new(*val[0])}
|
159
|
+
| LOCAL_DATETIME { result = LocalDateTime.new(*val[0]) }
|
160
|
+
| LOCAL_DATE { result = LocalDate.new(*val[0]) }
|
161
|
+
| LOCAL_TIME { result = LocalTime.new(*val[0]) }
|
100
162
|
;
|
101
163
|
string
|
102
164
|
: STRING_MULTI { result = StringUtils.replace_escaped_chars(StringUtils.multiline_replacements(val[0])) }
|