tomlrb 1.3.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)
@@ -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
@@ -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
- : 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) }
@@ -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])) }