kdl 1.0.6 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +8 -1
- data/.gitignore +1 -0
- data/.gitmodules +4 -0
- data/Gemfile +6 -1
- data/README.md +67 -7
- data/Rakefile +6 -1
- data/bin/kdl +1 -1
- data/kdl.gemspec +2 -2
- data/lib/kdl/document.rb +60 -2
- data/lib/kdl/error.rb +24 -0
- data/lib/kdl/kdl.tab.rb +305 -231
- data/lib/kdl/kdl.yy +57 -49
- data/lib/kdl/node.rb +116 -13
- data/lib/kdl/parser_common.rb +28 -0
- data/lib/kdl/string_dumper.rb +32 -33
- data/lib/kdl/tokenizer.rb +387 -136
- data/lib/kdl/types/base64.rb +3 -1
- data/lib/kdl/types/country/iso3166_countries.rb +3 -1
- data/lib/kdl/types/country/iso3166_subdivisions.rb +3 -1
- data/lib/kdl/types/country.rb +4 -2
- data/lib/kdl/types/currency/iso4217_currencies.rb +3 -1
- data/lib/kdl/types/currency.rb +3 -1
- data/lib/kdl/types/date_time.rb +5 -3
- data/lib/kdl/types/decimal.rb +3 -1
- data/lib/kdl/types/duration/iso8601_parser.rb +3 -1
- data/lib/kdl/types/duration.rb +3 -1
- data/lib/kdl/types/email/parser.rb +10 -8
- data/lib/kdl/types/email.rb +3 -1
- data/lib/kdl/types/hostname/validator.rb +3 -1
- data/lib/kdl/types/hostname.rb +3 -1
- data/lib/kdl/types/ip.rb +3 -1
- data/lib/kdl/types/irl/parser.rb +10 -8
- data/lib/kdl/types/irl.rb +3 -1
- data/lib/kdl/types/regex.rb +3 -1
- data/lib/kdl/types/url.rb +3 -1
- data/lib/kdl/types/url_template.rb +6 -4
- data/lib/kdl/types/uuid.rb +3 -1
- data/lib/kdl/types.rb +2 -0
- data/lib/kdl/v1/document.rb +19 -0
- data/lib/kdl/v1/kdl.tab.rb +594 -0
- data/lib/kdl/v1/kdl.yy +89 -0
- data/lib/kdl/v1/node.rb +32 -0
- data/lib/kdl/v1/string_dumper.rb +30 -0
- data/lib/kdl/v1/tokenizer.rb +298 -0
- data/lib/kdl/v1/value.rb +91 -0
- data/lib/kdl/v1.rb +13 -0
- data/lib/kdl/value.rb +87 -15
- data/lib/kdl/version.rb +3 -1
- data/lib/kdl.rb +47 -1
- metadata +14 -7
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KDL
|
4
|
+
module V1
|
5
|
+
module StringDumper
|
6
|
+
include ::KDL::StringDumper
|
7
|
+
|
8
|
+
def call(string)
|
9
|
+
%("#{string.each_char.map { |char| escape(char) }.join}")
|
10
|
+
end
|
11
|
+
|
12
|
+
def stringify_identifier(ident)
|
13
|
+
if bare_identifier?(ident)
|
14
|
+
ident
|
15
|
+
else
|
16
|
+
call(ident)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def bare_identifier?(name)
|
23
|
+
escape_chars = '\\\/(){}<>;\[\]=,"'
|
24
|
+
name =~ /^([^0-9\-+\s#{escape_chars}][^\s#{escape_chars}]*|[\-+](?!true|false|null)[^0-9\s#{escape_chars}][^\s#{escape_chars}]*)$/
|
25
|
+
end
|
26
|
+
|
27
|
+
extend self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,298 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KDL
|
4
|
+
module V1
|
5
|
+
class Tokenizer < KDL::Tokenizer
|
6
|
+
NON_IDENTIFIER_CHARS = Regexp.escape "#{SYMBOLS.keys.join}()/\\<>[]\",#{WHITESPACE.join}#{OTHER_NON_IDENTIFIER_CHARS.join}"
|
7
|
+
IDENTIFIER_CHARS = /[^#{NON_IDENTIFIER_CHARS}]/
|
8
|
+
INITIAL_IDENTIFIER_CHARS = /[^#{NON_IDENTIFIER_CHARS}0-9]/
|
9
|
+
|
10
|
+
def next_token
|
11
|
+
@context = nil
|
12
|
+
@previous_context = nil
|
13
|
+
@line_at_start = @line
|
14
|
+
@column_at_start = @column
|
15
|
+
loop do
|
16
|
+
c = self[@index]
|
17
|
+
case @context
|
18
|
+
when nil
|
19
|
+
case c
|
20
|
+
when '"'
|
21
|
+
self.context = :string
|
22
|
+
@buffer = +''
|
23
|
+
traverse(1)
|
24
|
+
when 'r'
|
25
|
+
if @str[@index + 1] == '"'
|
26
|
+
self.context = :rawstring
|
27
|
+
traverse(2)
|
28
|
+
@rawstring_hashes = 0
|
29
|
+
@buffer = +''
|
30
|
+
next
|
31
|
+
elsif @str[@index + 1] == '#'
|
32
|
+
i = @index + 1
|
33
|
+
@rawstring_hashes = 0
|
34
|
+
while @str[i] == '#'
|
35
|
+
@rawstring_hashes += 1
|
36
|
+
i += 1
|
37
|
+
end
|
38
|
+
if @str[i] == '"'
|
39
|
+
self.context = :rawstring
|
40
|
+
@index = i + 1
|
41
|
+
@buffer = +''
|
42
|
+
next
|
43
|
+
end
|
44
|
+
end
|
45
|
+
self.context = :ident
|
46
|
+
@buffer = +c
|
47
|
+
traverse(1)
|
48
|
+
when '-'
|
49
|
+
n = self[@index + 1]
|
50
|
+
if n =~ /[0-9]/
|
51
|
+
n2 = self[@index + 2]
|
52
|
+
if n == '0' && n2 =~ /[box]/
|
53
|
+
self.context = integer_context(n2)
|
54
|
+
traverse(3)
|
55
|
+
else
|
56
|
+
self.context = :decimal
|
57
|
+
traverse(1)
|
58
|
+
end
|
59
|
+
else
|
60
|
+
self.context = :ident
|
61
|
+
traverse(1)
|
62
|
+
end
|
63
|
+
@buffer = +c
|
64
|
+
when /[0-9+]/
|
65
|
+
n = self[@index + 1]
|
66
|
+
if c == '0' && n =~ /[box]/
|
67
|
+
traverse(2)
|
68
|
+
@buffer = +''
|
69
|
+
self.context = integer_context(n)
|
70
|
+
else
|
71
|
+
self.context = :decimal
|
72
|
+
@buffer = +c
|
73
|
+
traverse(1)
|
74
|
+
end
|
75
|
+
when '\\'
|
76
|
+
t = Tokenizer.new(@str, @index + 1)
|
77
|
+
la = t.next_token
|
78
|
+
if la[0] == :NEWLINE || la[0] == :EOF || (la[0] == :WS && (lan = t.next_token[0]) == :NEWLINE || lan == :EOF)
|
79
|
+
traverse_to(t.index)
|
80
|
+
@buffer = "#{c}#{la[1].value}"
|
81
|
+
@buffer << "\n" if lan == :NEWLINE
|
82
|
+
self.context = :whitespace
|
83
|
+
else
|
84
|
+
raise_error "Unexpected '\\' (#{la[0]})"
|
85
|
+
end
|
86
|
+
when *SYMBOLS.keys
|
87
|
+
return token(SYMBOLS[c], -c).tap { traverse(1) }
|
88
|
+
when *NEWLINES, "\r"
|
89
|
+
nl = expect_newline
|
90
|
+
return token(:NEWLINE, -nl).tap do
|
91
|
+
traverse(nl.length)
|
92
|
+
end
|
93
|
+
when "/"
|
94
|
+
if self[@index + 1] == '/'
|
95
|
+
self.context = :single_line_comment
|
96
|
+
traverse(2)
|
97
|
+
elsif self[@index + 1] == '*'
|
98
|
+
self.context = :multi_line_comment
|
99
|
+
@comment_nesting = 1
|
100
|
+
traverse(2)
|
101
|
+
elsif self[@index + 1] == '-'
|
102
|
+
return token(:SLASHDASH, '/-').tap { traverse(2) }
|
103
|
+
else
|
104
|
+
self.context = :ident
|
105
|
+
@buffer = +c
|
106
|
+
traverse(1)
|
107
|
+
end
|
108
|
+
when *WHITESPACE
|
109
|
+
self.context = :whitespace
|
110
|
+
@buffer = +c
|
111
|
+
traverse(1)
|
112
|
+
when nil
|
113
|
+
return [false, token(:EOF, :EOF)[1]] if @done
|
114
|
+
|
115
|
+
@done = true
|
116
|
+
return token(:EOF, :EOF)
|
117
|
+
when INITIAL_IDENTIFIER_CHARS
|
118
|
+
self.context = :ident
|
119
|
+
@buffer = +c
|
120
|
+
traverse(1)
|
121
|
+
when '('
|
122
|
+
@type_context = true
|
123
|
+
return token(:LPAREN, -c).tap { traverse(1) }
|
124
|
+
when ')'
|
125
|
+
@type_context = false
|
126
|
+
return token(:RPAREN, -c).tap { traverse(1) }
|
127
|
+
else
|
128
|
+
raise_error "Unexpected character #{c.inspect}"
|
129
|
+
end
|
130
|
+
when :ident
|
131
|
+
case c
|
132
|
+
when IDENTIFIER_CHARS
|
133
|
+
traverse(1)
|
134
|
+
@buffer << c
|
135
|
+
else
|
136
|
+
case @buffer
|
137
|
+
when 'true' then return token(:TRUE, true)
|
138
|
+
when 'false' then return token(:FALSE, false)
|
139
|
+
when 'null' then return token(:NULL, nil)
|
140
|
+
else return token(:IDENT, -@buffer)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
when :string
|
144
|
+
case c
|
145
|
+
when '\\'
|
146
|
+
c2 = self[@index + 1]
|
147
|
+
if c2.match?(NEWLINES_PATTERN)
|
148
|
+
i = 2
|
149
|
+
while self[@index + i].match?(NEWLINES_PATTERN)
|
150
|
+
i+=1
|
151
|
+
end
|
152
|
+
traverse(i)
|
153
|
+
else
|
154
|
+
@buffer << c
|
155
|
+
@buffer << c2
|
156
|
+
traverse(2)
|
157
|
+
end
|
158
|
+
when '"'
|
159
|
+
return token(:STRING, -unescape(@buffer)).tap { traverse(1) }
|
160
|
+
when nil
|
161
|
+
raise_error "Unterminated string literal"
|
162
|
+
else
|
163
|
+
@buffer << c
|
164
|
+
traverse(1)
|
165
|
+
end
|
166
|
+
when :rawstring
|
167
|
+
raise_error "Unterminated rawstring literal" if c.nil?
|
168
|
+
|
169
|
+
if c == '"'
|
170
|
+
h = 0
|
171
|
+
h += 1 while self[@index + 1 + h] == '#' && h < @rawstring_hashes
|
172
|
+
if h == @rawstring_hashes
|
173
|
+
return token(:RAWSTRING, -@buffer).tap { traverse(1 + h) }
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
@buffer << c
|
178
|
+
traverse(1)
|
179
|
+
when :decimal
|
180
|
+
case c
|
181
|
+
when /[0-9.\-+_eE]/
|
182
|
+
traverse(1)
|
183
|
+
@buffer << c
|
184
|
+
else
|
185
|
+
return parse_decimal(@buffer)
|
186
|
+
end
|
187
|
+
when :hexadecimal
|
188
|
+
case c
|
189
|
+
when /[0-9a-fA-F_]/
|
190
|
+
traverse(1)
|
191
|
+
@buffer << c
|
192
|
+
else
|
193
|
+
return parse_hexadecimal(@buffer)
|
194
|
+
end
|
195
|
+
when :octal
|
196
|
+
case c
|
197
|
+
when /[0-7_]/
|
198
|
+
traverse(1)
|
199
|
+
@buffer << c
|
200
|
+
else
|
201
|
+
return parse_octal(@buffer)
|
202
|
+
end
|
203
|
+
when :binary
|
204
|
+
case c
|
205
|
+
when /[01_]/
|
206
|
+
traverse(1)
|
207
|
+
@buffer << c
|
208
|
+
else
|
209
|
+
return parse_binary(@buffer)
|
210
|
+
end
|
211
|
+
when :single_line_comment
|
212
|
+
case c
|
213
|
+
when *NEWLINES, "\r"
|
214
|
+
self.context = nil
|
215
|
+
@column_at_start = @column
|
216
|
+
next
|
217
|
+
when nil
|
218
|
+
@done = true
|
219
|
+
return token(:EOF, :EOF)
|
220
|
+
else
|
221
|
+
traverse(1)
|
222
|
+
end
|
223
|
+
when :multi_line_comment
|
224
|
+
if c == '/' && self[@index + 1] == '*'
|
225
|
+
@comment_nesting += 1
|
226
|
+
traverse(2)
|
227
|
+
elsif c == '*' && self[@index + 1] == '/'
|
228
|
+
@comment_nesting -= 1
|
229
|
+
traverse(2)
|
230
|
+
if @comment_nesting == 0
|
231
|
+
revert_context
|
232
|
+
end
|
233
|
+
else
|
234
|
+
traverse(1)
|
235
|
+
end
|
236
|
+
when :whitespace
|
237
|
+
if WHITESPACE.include?(c)
|
238
|
+
traverse(1)
|
239
|
+
@buffer << c
|
240
|
+
elsif c == "/" && self[@index + 1] == '*'
|
241
|
+
self.context = :multi_line_comment
|
242
|
+
@comment_nesting = 1
|
243
|
+
traverse(2)
|
244
|
+
elsif c == "\\"
|
245
|
+
t = Tokenizer.new(@str, @index + 1)
|
246
|
+
la = t.next_token
|
247
|
+
if la[0] == :NEWLINE || la[0] == :EOF || (la[0] == :WS && (lan = t.next_token[0]) == :NEWLINE || lan == :EOF)
|
248
|
+
traverse_to(t.index)
|
249
|
+
@buffer << "#{c}#{la[1].value}"
|
250
|
+
@buffer << "\n" if lan == :NEWLINE
|
251
|
+
else
|
252
|
+
raise_error "Unexpected '\\' (#{la[0]})"
|
253
|
+
end
|
254
|
+
else
|
255
|
+
return token(:WS, -@buffer)
|
256
|
+
end
|
257
|
+
else
|
258
|
+
# :nocov:
|
259
|
+
raise_error "Unknown context `#{@context}'"
|
260
|
+
# :nocov:
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
private
|
266
|
+
|
267
|
+
def allowed_in_type?(val)
|
268
|
+
%i[ident string rawstring].include?(val)
|
269
|
+
end
|
270
|
+
|
271
|
+
def allowed_after_type?(val)
|
272
|
+
!%i[single_line_comment multi_line_comment].include?(val)
|
273
|
+
end
|
274
|
+
|
275
|
+
def unescape(string)
|
276
|
+
string.gsub(/\\[^u]/) do |m|
|
277
|
+
case m
|
278
|
+
when '\n' then "\n"
|
279
|
+
when '\r' then "\r"
|
280
|
+
when '\t' then "\t"
|
281
|
+
when '\\\\' then "\\"
|
282
|
+
when '\"' then "\""
|
283
|
+
when '\b' then "\b"
|
284
|
+
when '\f' then "\f"
|
285
|
+
when '\/' then "/"
|
286
|
+
else raise_error "Unexpected escape #{m.inspect}"
|
287
|
+
end
|
288
|
+
end.gsub(/\\u\{[0-9a-fA-F]{0,6}\}/) do |m|
|
289
|
+
i = Integer(m[3..-2], 16)
|
290
|
+
if i < 0 || i > 0x10FFFF
|
291
|
+
raise_error "Invalid code point #{u}"
|
292
|
+
end
|
293
|
+
i.chr(Encoding::UTF_8)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
data/lib/kdl/v1/value.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KDL
|
4
|
+
module V1
|
5
|
+
class Value < ::KDL::Value
|
6
|
+
module Methods
|
7
|
+
def to_s
|
8
|
+
return stringify_value unless type
|
9
|
+
|
10
|
+
"(#{StringDumper.stringify_identifier type})#{stringify_value}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
return self == other.value if other.is_a?(self.class.superclass)
|
15
|
+
|
16
|
+
value == other
|
17
|
+
end
|
18
|
+
|
19
|
+
def version
|
20
|
+
1
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_v1
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_v2
|
28
|
+
self.class.superclass.new(value, format:, type:)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
include Methods
|
33
|
+
|
34
|
+
class Int < ::KDL::Value::Int
|
35
|
+
include Methods
|
36
|
+
end
|
37
|
+
|
38
|
+
class Float < ::KDL::Value::Float
|
39
|
+
include Methods
|
40
|
+
|
41
|
+
def stringify_value
|
42
|
+
if value.nan? || value.infinite?
|
43
|
+
warn "[WARNING] Attempting to serialize non-finite Float using KDL v1"
|
44
|
+
return Null.stringify_value
|
45
|
+
end
|
46
|
+
super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Boolean < ::KDL::Value::Boolean
|
51
|
+
include Methods
|
52
|
+
|
53
|
+
def stringify_value
|
54
|
+
value.to_s
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class String < ::KDL::Value::String
|
59
|
+
include Methods
|
60
|
+
|
61
|
+
def stringify_value
|
62
|
+
StringDumper.call(value)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class NullImpl < ::KDL::Value::NullImpl
|
67
|
+
include Methods
|
68
|
+
|
69
|
+
def stringify_value
|
70
|
+
"null"
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_v2
|
74
|
+
type ? ::KDL::Value::NullImpl.new(type:) : ::KDL::Value::Null
|
75
|
+
end
|
76
|
+
end
|
77
|
+
Null = NullImpl.new
|
78
|
+
|
79
|
+
def self.from(value)
|
80
|
+
case value
|
81
|
+
when ::String then String.new(value)
|
82
|
+
when Integer then Int.new(value)
|
83
|
+
when ::Float then Float.new(value)
|
84
|
+
when TrueClass, FalseClass then Boolean.new(value)
|
85
|
+
when NilClass then Null
|
86
|
+
else raise Error("Unsupported value type: #{value.class}")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/kdl/v1.rb
ADDED
data/lib/kdl/value.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module KDL
|
2
4
|
class Value
|
3
5
|
attr_reader :value, :format, :type
|
@@ -15,18 +17,30 @@ module KDL
|
|
15
17
|
result = parser.call(self, type)
|
16
18
|
return self.as_type(type) if result.nil?
|
17
19
|
|
18
|
-
unless result.is_a?(::KDL::Value)
|
19
|
-
raise ArgumentError, "expected parser to return an instance of ::KDL::Value, got `#{result.class}'"
|
20
|
+
unless result.is_a?(::KDL::Value::Custom)
|
21
|
+
raise ArgumentError, "expected parser to return an instance of ::KDL::Value::Custom, got `#{result.class}'"
|
20
22
|
end
|
21
23
|
|
22
24
|
result
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
28
|
+
def ==(other)
|
29
|
+
return self == other.value if other.is_a?(self.class)
|
30
|
+
|
31
|
+
value == other
|
32
|
+
end
|
33
|
+
|
26
34
|
def to_s
|
27
35
|
return stringify_value unless type
|
28
36
|
|
29
|
-
"(#{StringDumper.
|
37
|
+
"(#{StringDumper.call type})#{stringify_value}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def inspect
|
41
|
+
return value.inspect unless type
|
42
|
+
|
43
|
+
"(#{type.inspect})#{value.inspect}"
|
30
44
|
end
|
31
45
|
|
32
46
|
def stringify_value
|
@@ -35,31 +49,65 @@ module KDL
|
|
35
49
|
value.to_s
|
36
50
|
end
|
37
51
|
|
52
|
+
def version
|
53
|
+
2
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_v2
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def method_missing(name, *args, **kwargs, &block)
|
61
|
+
value.public_send(name, *args, **kwargs, &block)
|
62
|
+
end
|
63
|
+
|
64
|
+
def respond_to_missing?(name, include_all = false)
|
65
|
+
value.respond_to?(name, include_all)
|
66
|
+
end
|
67
|
+
|
38
68
|
class Int < Value
|
39
|
-
def
|
40
|
-
|
69
|
+
def to_v1
|
70
|
+
V1::Value::Int.new(value, format:, type:)
|
41
71
|
end
|
42
72
|
end
|
43
73
|
|
44
74
|
class Float < Value
|
45
75
|
def ==(other)
|
46
|
-
other.is_a?(Float)
|
76
|
+
return self == other.value if other.is_a?(Float)
|
77
|
+
return other.nan? if value.nan?
|
78
|
+
|
79
|
+
value == other
|
47
80
|
end
|
48
81
|
|
49
82
|
def stringify_value
|
83
|
+
return '#nan' if value.nan?
|
84
|
+
return '#inf' if value == ::Float::INFINITY
|
85
|
+
return '#-inf' if value == -::Float::INFINITY
|
50
86
|
return super.upcase unless value.is_a?(BigDecimal)
|
51
87
|
|
52
88
|
sign, digits, _, exponent = value.split
|
53
|
-
s =
|
54
|
-
s
|
55
|
-
s
|
89
|
+
s = +''
|
90
|
+
s << '-' if sign.negative?
|
91
|
+
s << "#{digits[0]}.#{digits[1..-1]}"
|
92
|
+
s << "E#{exponent.negative? ? '' : '+'}#{exponent - 1}"
|
56
93
|
s
|
57
94
|
end
|
95
|
+
|
96
|
+
def to_v1
|
97
|
+
if value.nan? || value.infinite?
|
98
|
+
warn "[WARNING] Converting non-finite Float to KDL v1"
|
99
|
+
end
|
100
|
+
V1::Value::Float.new(value, format:, type:)
|
101
|
+
end
|
58
102
|
end
|
59
103
|
|
60
104
|
class Boolean < Value
|
61
|
-
def
|
62
|
-
|
105
|
+
def stringify_value
|
106
|
+
"##{value}"
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_v1
|
110
|
+
V1::Value::Boolean.new(value, format:, type:)
|
63
111
|
end
|
64
112
|
end
|
65
113
|
|
@@ -68,8 +116,8 @@ module KDL
|
|
68
116
|
StringDumper.call(value)
|
69
117
|
end
|
70
118
|
|
71
|
-
def
|
72
|
-
|
119
|
+
def to_v1
|
120
|
+
V1::Value::String.new(value, format:, type:)
|
73
121
|
end
|
74
122
|
end
|
75
123
|
|
@@ -79,15 +127,39 @@ module KDL
|
|
79
127
|
end
|
80
128
|
|
81
129
|
def stringify_value
|
82
|
-
"null"
|
130
|
+
"#null"
|
83
131
|
end
|
84
132
|
|
85
133
|
def ==(other)
|
86
|
-
other.is_a?(NullImpl)
|
134
|
+
other.is_a?(NullImpl) || other.nil?
|
135
|
+
end
|
136
|
+
|
137
|
+
def to_v1
|
138
|
+
type ? V1::Value::NullImpl.new(type:) : V1::Value::Null
|
87
139
|
end
|
88
140
|
end
|
89
141
|
Null = NullImpl.new
|
90
142
|
|
143
|
+
class Custom < Value
|
144
|
+
attr_reader :oriinal_value
|
145
|
+
|
146
|
+
def self.call(value, type)
|
147
|
+
new(value, type:)
|
148
|
+
end
|
149
|
+
|
150
|
+
def version
|
151
|
+
nil
|
152
|
+
end
|
153
|
+
|
154
|
+
def to_v1
|
155
|
+
self
|
156
|
+
end
|
157
|
+
|
158
|
+
def to_v2
|
159
|
+
self
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
91
163
|
def self.from(value)
|
92
164
|
case value
|
93
165
|
when ::String then String.new(value)
|
data/lib/kdl/version.rb
CHANGED
data/lib/kdl.rb
CHANGED
@@ -1,14 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "kdl/version"
|
4
|
+
require "kdl/error"
|
2
5
|
require "kdl/tokenizer"
|
3
6
|
require "kdl/document"
|
4
7
|
require "kdl/value"
|
5
8
|
require "kdl/node"
|
6
9
|
require "kdl/string_dumper"
|
7
10
|
require "kdl/types"
|
11
|
+
require "kdl/parser_common"
|
8
12
|
require "kdl/kdl.tab"
|
13
|
+
require "kdl/v1"
|
9
14
|
|
10
15
|
module KDL
|
16
|
+
class << self
|
17
|
+
attr_accessor :default_version
|
18
|
+
attr_accessor :default_output_version
|
19
|
+
end
|
20
|
+
|
11
21
|
def self.parse_document(input, options = {})
|
12
|
-
|
22
|
+
warn "[DEPRECATION] `KDL.parse_document' is deprecated. Please use `KDL.parse' instead."
|
23
|
+
parse(input, **options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.parse(input, version: default_version, output_version: default_output_version, **options)
|
27
|
+
case version
|
28
|
+
when 2
|
29
|
+
Parser.new(output_module: output_module(output_version || 2), **options).parse(input)
|
30
|
+
when 1
|
31
|
+
V1::Parser.new.parse(input, output_module: output_module(output_version || 1), **options)
|
32
|
+
when nil
|
33
|
+
auto_parse(input, output_version:, **options)
|
34
|
+
else
|
35
|
+
raise UnsupportedVersionError.new("Unsupported version '#{version}'", version)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.load_file(filespec, **options)
|
40
|
+
parse(File.read(filespec, encoding: Encoding::UTF_8), **options)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.auto_parse(input, output_version: default_output_version, **options)
|
44
|
+
parse(input, version: 2, output_version: output_version || 2, **options)
|
45
|
+
rescue VersionMismatchError => e
|
46
|
+
parse(input, version: e.version, output_version: output_version || e.version, **options)
|
47
|
+
rescue Tokenizer::Error, Racc::ParseError => e
|
48
|
+
parse(input, version: 1, output_version: output_version || 1, **options) rescue raise e
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.output_module(version)
|
52
|
+
case version
|
53
|
+
when 1 then KDL::V1
|
54
|
+
when 2 then KDL
|
55
|
+
else
|
56
|
+
warn "Unknown output_version `#{version}', defaulting to v2"
|
57
|
+
KDL
|
58
|
+
end
|
13
59
|
end
|
14
60
|
end
|