tomlrb 1.2.7 → 2.0.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.
@@ -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
- k = k.to_sym if @symbolize_keys
53
- @current[k] = @stack.pop
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)
@@ -64,10 +73,159 @@ module Tomlrb
64
73
  def end_(type)
65
74
  array = []
66
75
  while (value = @stack.pop) != [type]
67
- raise ParseError, 'Unclosed table' unless value
76
+ raise ParseError, 'Unclosed table' if value.nil?
68
77
  array.unshift(value)
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.clear_children if is_array_of_tables
129
+ current
130
+ end
131
+
132
+ def find_or_create_first_table_key(current, key, declared, is_array_of_tables)
133
+ existed = current[key]
134
+ if existed && existed.type == :pair
135
+ raise Key::KeyConflict, "Key #{key} is already used as #{existed.type} key"
136
+ end
137
+ if existed && existed.declared? && declared && ! is_array_of_tables
138
+ raise Key::KeyConflict, "Key #{key} is already used"
139
+ end
140
+ k = existed || Key.new(key, :table, declared)
141
+ current[key] = k
142
+ k
143
+ end
144
+
145
+ def append_pair_keys(current, pair_keys, table_keys_empty, is_array_of_tables)
146
+ pair_keys.each_with_index do |key, index|
147
+ declared = index == pair_keys.length - 1
148
+ if index == 0 && table_keys_empty
149
+ current = find_or_create_first_pair_key(current, key, declared, table_keys_empty)
150
+ else
151
+ key = current << [key, :pair, declared, is_array_of_tables]
152
+ current = key
153
+ end
154
+ end
155
+ end
156
+
157
+ def find_or_create_first_pair_key(current, key, declared, table_keys_empty)
158
+ existed = current[key]
159
+ if existed && existed.declared? && (existed.type == :pair) && declared && table_keys_empty
160
+ raise Key::KeyConflict, "Key #{key} is already used"
161
+ end
162
+ k = Key.new(key, :pair, declared)
163
+ current[key] = k
164
+ k
165
+ end
166
+ end
167
+
168
+ class Key
169
+ class KeyConflict < ParseError; end
170
+
171
+ attr_reader :key, :type
172
+
173
+ def initialize(key, type, declared = false)
174
+ @key = key
175
+ @type = type
176
+ @declared = declared
177
+ @children = {}
178
+ end
179
+
180
+ def declared?
181
+ @declared
182
+ end
183
+
184
+ def <<(key_type_declared)
185
+ key, type, declared, is_array_of_tables = key_type_declared
186
+ existed = @children[key]
187
+ validate_already_declared_as_different_key(type, declared, existed)
188
+ validate_already_declared_as_non_array_table(type, is_array_of_tables, declared, existed)
189
+ validate_path_already_created_as_different_type(type, declared, existed)
190
+ validate_path_already_declared_as_different_type(type, declared, existed)
191
+ validate_already_declared_as_same_key(declared, existed)
192
+ @children[key] = existed || self.class.new(key, type, declared)
193
+ end
194
+
195
+ def clear_children
196
+ @children.clear
197
+ end
198
+
199
+ private
200
+
201
+ def validate_already_declared_as_different_key(type, declared, existed)
202
+ if declared && existed && existed.declared? && existed.type != type
203
+ raise KeyConflict, "Key #{existed.key} is already used as #{existed.type} key"
204
+ end
205
+ end
206
+
207
+ def validate_already_declared_as_non_array_table(type, is_array_of_tables, declared, existed)
208
+ if declared && type == :table && existed && existed.declared? && ! is_array_of_tables
209
+ raise KeyConflict, "Key #{existed.key} is already used"
210
+ end
211
+ end
212
+
213
+ def validate_path_already_created_as_different_type(type, declared, existed)
214
+ if declared && (type == :table) && existed && (existed.type == :pair) && (! existed.declared?)
215
+ raise KeyConflict, "Key #{existed.key} is already used as #{existed.type} key"
216
+ end
217
+ end
218
+
219
+ def validate_path_already_declared_as_different_type(type, declared, existed)
220
+ if ! declared && (type == :pair) && existed && (existed.type == :pair) && existed.declared?
221
+ raise KeyConflict, "Key #{key} is already used as #{type} key"
222
+ end
223
+ end
224
+
225
+ def validate_already_declared_as_same_key(declared, existed)
226
+ if existed && ! existed.declared? && declared
227
+ raise KeyConflict, "Key #{existed.key} is already used as #{existed.type} key"
228
+ end
229
+ end
72
230
  end
73
231
  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.kind_of?(self.class) &&
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.kind_of?(self.class) &&
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.kind_of?(self.class) &&
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
@@ -4,7 +4,7 @@ class Tomlrb::Parser < Tomlrb::GeneratedParser
4
4
 
5
5
  def initialize(tokenizer, **options)
6
6
  @tokenizer = tokenizer
7
- @handler = Tomlrb::Handler.new(options)
7
+ @handler = Tomlrb::Handler.new(**options)
8
8
  super()
9
9
  end
10
10
 
@@ -1,16 +1,19 @@
1
1
  class Tomlrb::GeneratedParser
2
- token IDENTIFIER STRING_MULTI STRING_BASIC STRING_LITERAL_MULTI STRING_LITERAL DATETIME INTEGER FLOAT TRUE FALSE
2
+ token IDENTIFIER STRING_MULTI STRING_BASIC STRING_LITERAL_MULTI STRING_LITERAL DATETIME LOCAL_TIME INTEGER NON_DEC_INTEGER FLOAT FLOAT_KEYWORD BOOLEAN 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
- : table_start table_continued
15
+ : table_start table_continued NEWLINE
16
+ | table_start table_continued EOS
14
17
  ;
15
18
  table_start
16
19
  : '[' '[' { @handler.start_(:array_of_tables) }
@@ -27,44 +30,87 @@ rule
27
30
  | '.' table_continued
28
31
  ;
29
32
  table_identifier
30
- : IDENTIFIER { @handler.push(val[0]) }
31
- | STRING_BASIC { @handler.push(val[0]) }
32
- | STRING_LITERAL { @handler.push(val[0]) }
33
- | INTEGER { @handler.push(val[0]) }
34
- | TRUE { @handler.push(val[0]) }
35
- | FALSE { @handler.push(val[0]) }
33
+ : table_identifier '.' table_identifier_component { @handler.push(val[2]) }
34
+ | table_identifier '.' FLOAT { val[2].split('.').each { |k| @handler.push(k) } }
35
+ | FLOAT {
36
+ keys = val[0].split('.')
37
+ @handler.start_(:table)
38
+ keys.each { |key| @handler.push(key) }
39
+ }
40
+ | table_identifier_component { @handler.push(val[0]) }
41
+ ;
42
+ table_identifier_component
43
+ : IDENTIFIER
44
+ | STRING_BASIC
45
+ | STRING_LITERAL
46
+ | INTEGER
47
+ | NON_DEC_INTEGER
48
+ | FLOAT_KEYWORD
49
+ | BOOLEAN
36
50
  ;
37
51
  inline_table
38
- : inline_table_start inline_continued
52
+ : inline_table_start inline_table_end
53
+ | inline_table_start inline_continued inline_table_end
39
54
  ;
40
55
  inline_table_start
41
56
  : '{' { @handler.start_(:inline) }
42
57
  ;
43
- inline_continued
44
- : '}' { array = @handler.end_(:inline); @handler.push(Hash[*array]) }
45
- | inline_assignment_key inline_assignment_value inline_next
46
- ;
47
- inline_next
58
+ inline_table_end
48
59
  : '}' {
49
60
  array = @handler.end_(:inline)
50
61
  array.map!.with_index{ |n,i| i.even? ? n.to_sym : n } if @handler.symbolize_keys
51
62
  @handler.push(Hash[*array])
52
63
  }
53
- | ',' inline_continued
64
+ ;
65
+ inline_continued
66
+ : inline_assignment_key inline_assignment_value
67
+ | inline_assignment_key inline_assignment_value inline_next
68
+ ;
69
+ inline_next
70
+ : ',' inline_continued
54
71
  ;
55
72
  inline_assignment_key
56
- : IDENTIFIER { @handler.push(val[0]) }
73
+ : inline_assignment_key '.' IDENTIFIER {
74
+ array = @handler.end_(:inline)
75
+ array.each { |key| @handler.push(key) }
76
+ @handler.start_(:inline)
77
+ @handler.push(val[2])
78
+ }
79
+ | IDENTIFIER { @handler.push(val[0]) }
57
80
  ;
58
81
  inline_assignment_value
59
82
  : '=' value
60
83
  ;
61
84
  assignment
62
- : IDENTIFIER '=' value { @handler.assign(val[0]) }
63
- | STRING_BASIC '=' value { @handler.assign(val[0]) }
64
- | STRING_LITERAL '=' value { @handler.assign(val[0]) }
65
- | INTEGER '=' value { @handler.assign(val[0]) }
66
- | TRUE '=' value { @handler.assign(val[0]) }
67
- | FALSE '=' value { @handler.assign(val[0]) }
85
+ : assignment_key '=' value EOS {
86
+ keys = @handler.end_(:keys)
87
+ @handler.push(keys.pop)
88
+ @handler.assign(keys)
89
+ }
90
+ | assignment_key '=' value NEWLINE {
91
+ keys = @handler.end_(:keys)
92
+ @handler.push(keys.pop)
93
+ @handler.assign(keys)
94
+ }
95
+ ;
96
+ assignment_key
97
+ : assignment_key '.' assignment_key_component { @handler.push(val[2]) }
98
+ | assignment_key '.' FLOAT { val[2].split('.').each { |k| @handler.push(k) } }
99
+ | FLOAT {
100
+ keys = val[0].split('.')
101
+ @handler.start_(:keys)
102
+ keys.each { |key| @handler.push(key) }
103
+ }
104
+ | assignment_key_component { @handler.start_(:keys); @handler.push(val[0]) }
105
+ ;
106
+ assignment_key_component
107
+ : IDENTIFIER
108
+ | STRING_BASIC
109
+ | STRING_LITERAL
110
+ | INTEGER
111
+ | NON_DEC_INTEGER
112
+ | FLOAT_KEYWORD
113
+ | BOOLEAN
68
114
  ;
69
115
  array
70
116
  : start_array array_continued
@@ -72,10 +118,12 @@ rule
72
118
  array_continued
73
119
  : ']' { array = @handler.end_(:array); @handler.push(array) }
74
120
  | value array_next
121
+ | NEWLINE array_continued
75
122
  ;
76
123
  array_next
77
124
  : ']' { array = @handler.end_(:array); @handler.push(array) }
78
125
  | ',' array_continued
126
+ | NEWLINE array_continued
79
127
  ;
80
128
  start_array
81
129
  : '[' { @handler.start_(:array) }
@@ -91,10 +139,37 @@ rule
91
139
  ;
92
140
  literal
93
141
  | FLOAT { result = val[0].to_f }
142
+ | FLOAT_KEYWORD {
143
+ v = val[0]
144
+ result = if v.end_with?('nan')
145
+ Float::NAN
146
+ else
147
+ (v[0] == '-' ? -1 : 1) * Float::INFINITY
148
+ end
149
+ }
94
150
  | INTEGER { result = val[0].to_i }
95
- | TRUE { result = true }
96
- | FALSE { result = false }
97
- | DATETIME { result = Time.new(*val[0])}
151
+ | NON_DEC_INTEGER {
152
+ base = case val[0][1]
153
+ when "x" then 16
154
+ when "o" then 8
155
+ when "b" then 2
156
+ end
157
+ result = val[0].to_i(base)
158
+ }
159
+ | BOOLEAN { result = val[0] == 'true' ? true : false }
160
+ | DATETIME {
161
+ v = val[0]
162
+ result = if v[6].nil?
163
+ if v[4].nil?
164
+ LocalDate.new(v[0], v[1], v[2])
165
+ else
166
+ LocalDateTime.new(v[0], v[1], v[2], v[3] || 0, v[4] || 0, v[5].to_f)
167
+ end
168
+ else
169
+ Time.new(v[0], v[1], v[2], v[3] || 0, v[4] || 0, v[5].to_f, v[6])
170
+ end
171
+ }
172
+ | LOCAL_TIME { result = LocalTime.new(*val[0]) }
98
173
  ;
99
174
  string
100
175
  : STRING_MULTI { result = StringUtils.replace_escaped_chars(StringUtils.multiline_replacements(val[0])) }