tomlrb 1.2.6 → 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.
@@ -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,154 @@ 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
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
@@ -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,13 +1,15 @@
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_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
- : 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]) }
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 inline_continued
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
- inline_continued
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
- | ',' inline_continued
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 { @handler.push(val[0]) }
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
- : 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]) }
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) }
@@ -91,10 +147,18 @@ rule
91
147
  ;
92
148
  literal
93
149
  | FLOAT { result = val[0].to_f }
150
+ | FLOAT_INF { result = (val[0][0] == '-' ? -1 : 1) * Float::INFINITY }
151
+ | FLOAT_NAN { result = Float::NAN }
94
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) }
95
156
  | TRUE { result = true }
96
157
  | FALSE { result = false }
97
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]) }
98
162
  ;
99
163
  string
100
164
  : STRING_MULTI { result = StringUtils.replace_escaped_chars(StringUtils.multiline_replacements(val[0])) }