hocon 0.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.
- data/LICENSE +202 -0
- data/README.md +19 -0
- data/lib/hocon.rb +2 -0
- data/lib/hocon/config_error.rb +12 -0
- data/lib/hocon/config_factory.rb +9 -0
- data/lib/hocon/config_object.rb +4 -0
- data/lib/hocon/config_parse_options.rb +53 -0
- data/lib/hocon/config_render_options.rb +46 -0
- data/lib/hocon/config_syntax.rb +7 -0
- data/lib/hocon/config_value_type.rb +26 -0
- data/lib/hocon/impl.rb +5 -0
- data/lib/hocon/impl/abstract_config_object.rb +64 -0
- data/lib/hocon/impl/abstract_config_value.rb +130 -0
- data/lib/hocon/impl/config_concatenation.rb +136 -0
- data/lib/hocon/impl/config_float.rb +9 -0
- data/lib/hocon/impl/config_impl.rb +10 -0
- data/lib/hocon/impl/config_impl_util.rb +78 -0
- data/lib/hocon/impl/config_int.rb +31 -0
- data/lib/hocon/impl/config_number.rb +27 -0
- data/lib/hocon/impl/config_string.rb +37 -0
- data/lib/hocon/impl/full_includer.rb +4 -0
- data/lib/hocon/impl/origin_type.rb +9 -0
- data/lib/hocon/impl/parseable.rb +151 -0
- data/lib/hocon/impl/parser.rb +882 -0
- data/lib/hocon/impl/path.rb +59 -0
- data/lib/hocon/impl/path_builder.rb +36 -0
- data/lib/hocon/impl/resolve_status.rb +18 -0
- data/lib/hocon/impl/simple_config.rb +11 -0
- data/lib/hocon/impl/simple_config_list.rb +70 -0
- data/lib/hocon/impl/simple_config_object.rb +178 -0
- data/lib/hocon/impl/simple_config_origin.rb +174 -0
- data/lib/hocon/impl/simple_include_context.rb +7 -0
- data/lib/hocon/impl/simple_includer.rb +19 -0
- data/lib/hocon/impl/token.rb +32 -0
- data/lib/hocon/impl/token_type.rb +42 -0
- data/lib/hocon/impl/tokenizer.rb +370 -0
- data/lib/hocon/impl/tokens.rb +157 -0
- data/lib/hocon/impl/unmergeable.rb +4 -0
- metadata +84 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'hocon/impl'
|
2
|
+
require 'hocon/impl/full_includer'
|
3
|
+
|
4
|
+
class Hocon::Impl::SimpleIncluder < Hocon::Impl::FullIncluder
|
5
|
+
class Proxy < Hocon::Impl::FullIncluder
|
6
|
+
def initialize(delegate)
|
7
|
+
@delegate = delegate
|
8
|
+
end
|
9
|
+
## TODO: port remaining implementation when needed
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.make_full(includer)
|
13
|
+
if includer.is_a?(Hocon::Impl::FullIncluder)
|
14
|
+
includer
|
15
|
+
else
|
16
|
+
Proxy.new(includer)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'hocon/impl'
|
2
|
+
require 'hocon/impl/token_type'
|
3
|
+
|
4
|
+
class Hocon::Impl::Token
|
5
|
+
def self.new_without_origin(token_type, debug_string)
|
6
|
+
Hocon::Impl::Token.new(token_type, nil, debug_string)
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(token_type, origin, debug_string = nil)
|
10
|
+
@token_type = token_type
|
11
|
+
@origin = origin
|
12
|
+
@debug_string = debug_string
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :origin
|
16
|
+
|
17
|
+
def line_number
|
18
|
+
if @origin
|
19
|
+
@origin.line_number
|
20
|
+
else
|
21
|
+
-1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
if !@debug_string.nil?
|
27
|
+
@debug_string
|
28
|
+
else
|
29
|
+
Hocon::Impl::TokenType.name(@token_type)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'hocon/impl'
|
2
|
+
|
3
|
+
class Hocon::Impl::TokenType
|
4
|
+
START = 0
|
5
|
+
EOF = 1
|
6
|
+
COMMA = 2
|
7
|
+
EQUALS = 3
|
8
|
+
COLON = 4
|
9
|
+
OPEN_CURLY = 5
|
10
|
+
CLOSE_CURLY = 6
|
11
|
+
OPEN_SQUARE = 7
|
12
|
+
CLOSE_SQUARE = 8
|
13
|
+
VALUE = 9
|
14
|
+
NEWLINE = 10
|
15
|
+
UNQUOTED_TEXT = 11
|
16
|
+
SUBSTITUTION = 12
|
17
|
+
PROBLEM = 13
|
18
|
+
COMMENT = 14
|
19
|
+
PLUS_EQUALS = 15
|
20
|
+
|
21
|
+
def self.name(token_type)
|
22
|
+
case token_type
|
23
|
+
when START then "START"
|
24
|
+
when EOF then "EOF"
|
25
|
+
when COMMA then "COMMA"
|
26
|
+
when EQUALS then "EQUALS"
|
27
|
+
when COLON then "COLON"
|
28
|
+
when OPEN_CURLY then "OPEN_CURLY"
|
29
|
+
when CLOSE_CURLY then "CLOSE_CURLY"
|
30
|
+
when OPEN_SQUARE then "OPEN_SQUARE"
|
31
|
+
when CLOSE_SQUARE then "CLOSE_SQUARE"
|
32
|
+
when VALUE then "VALUE"
|
33
|
+
when NEWLINE then "NEWLINE"
|
34
|
+
when UNQUOTED_TEXT then "UNQUOTED_TEXT"
|
35
|
+
when SUBSTITUTION then "SUBSTITUTION"
|
36
|
+
when PROBLEM then "PROBLEM"
|
37
|
+
when COMMENT then "COMMENT"
|
38
|
+
when PLUS_EQUALS then "PLUS_EQUALS"
|
39
|
+
else raise ConfigBugError, "Unrecognized token type #{token_type}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,370 @@
|
|
1
|
+
require 'hocon/impl'
|
2
|
+
require 'hocon/impl/config_impl_util'
|
3
|
+
require 'hocon/impl/tokens'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
class Hocon::Impl::Tokenizer
|
7
|
+
Tokens = Hocon::Impl::Tokens
|
8
|
+
|
9
|
+
class TokenizerProblemError < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
class TokenIterator
|
13
|
+
class WhitespaceSaver
|
14
|
+
def initialize
|
15
|
+
@whitespace = StringIO.new
|
16
|
+
@last_token_was_simple_value = false
|
17
|
+
end
|
18
|
+
|
19
|
+
def add(c)
|
20
|
+
if @last_token_was_simple_value
|
21
|
+
@whitespace << c
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def check(t, base_origin, line_number)
|
26
|
+
if Hocon::Impl::Tokenizer::TokenIterator.simple_value?(t)
|
27
|
+
next_is_a_simple_value(base_origin, line_number)
|
28
|
+
else
|
29
|
+
next_is_not_a_simple_value
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
# called if the next token is not a simple value;
|
36
|
+
# discards any whitespace we were saving between
|
37
|
+
# simple values.
|
38
|
+
def next_is_not_a_simple_value
|
39
|
+
@last_token_was_simple_value = false
|
40
|
+
@whitespace.reopen("")
|
41
|
+
end
|
42
|
+
|
43
|
+
# called if the next token IS a simple value,
|
44
|
+
# so creates a whitespace token if the previous
|
45
|
+
# token also was.
|
46
|
+
def next_is_a_simple_value(base_origin, line_number)
|
47
|
+
if @last_token_was_simple_value
|
48
|
+
# need to save whitespace between the two so
|
49
|
+
# the parser has the option to concatenate it.
|
50
|
+
if @whitespace.length > 0
|
51
|
+
Tokens.new_unquoted_text(
|
52
|
+
line_origin(base_origin, line_number),
|
53
|
+
@whitespace.string = ""
|
54
|
+
)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
# chars JSON allows a number to start with
|
62
|
+
FIRST_NUMBER_CHARS = "0123456789-"
|
63
|
+
# chars JSON allows to be part of a number
|
64
|
+
NUMBER_CHARS = "0123456789eE+-."
|
65
|
+
# chars that stop an unquoted string
|
66
|
+
NOT_IN_UNQUOTED_TEXT = "$\"{}[]:=,+#`^?!@*&\\"
|
67
|
+
|
68
|
+
def self.simple_value?(t)
|
69
|
+
Tokens.substitution?(t) ||
|
70
|
+
Tokens.unquoted_text?(t) ||
|
71
|
+
Tokens.value?(t)
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.whitespace?(c)
|
75
|
+
Hocon::Impl::ConfigImplUtil.whitespace?(c)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.whitespace_not_newline?(c)
|
79
|
+
(c != "\n") and (Hocon::Impl::ConfigImplUtil.whitespace?(c))
|
80
|
+
end
|
81
|
+
|
82
|
+
def initialize(origin, input, allow_comments)
|
83
|
+
@origin = origin
|
84
|
+
@input = input
|
85
|
+
@allow_comments = allow_comments
|
86
|
+
@buffer = []
|
87
|
+
@line_number = 1
|
88
|
+
@line_origin = @origin.set_line_number(@line_number)
|
89
|
+
@tokens = []
|
90
|
+
@tokens << Tokens::START
|
91
|
+
@whitespace_saver = WhitespaceSaver.new
|
92
|
+
end
|
93
|
+
|
94
|
+
def start_of_comment?(c)
|
95
|
+
if c == -1
|
96
|
+
false
|
97
|
+
else
|
98
|
+
if @allow_comments
|
99
|
+
if c == '#'
|
100
|
+
true
|
101
|
+
elsif c == '/'
|
102
|
+
maybe_second_slash = next_char_raw
|
103
|
+
# we want to predictably NOT consume any chars
|
104
|
+
put_back(maybe_second_slash)
|
105
|
+
if maybe_second_slash == '/'
|
106
|
+
true
|
107
|
+
else
|
108
|
+
false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
else
|
112
|
+
false
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def put_back(c)
|
118
|
+
if @buffer.length > 2
|
119
|
+
raise ConfigBugError, "bug: putBack() three times, undesirable look-ahead"
|
120
|
+
end
|
121
|
+
@buffer.push(c)
|
122
|
+
end
|
123
|
+
|
124
|
+
def next_char_raw
|
125
|
+
if @buffer.empty?
|
126
|
+
begin
|
127
|
+
@input.readchar
|
128
|
+
rescue EOFError
|
129
|
+
-1
|
130
|
+
end
|
131
|
+
else
|
132
|
+
@buffer.pop
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def next_char_after_whitespace(saver)
|
137
|
+
while true
|
138
|
+
c = next_char_raw
|
139
|
+
if c == -1
|
140
|
+
return -1
|
141
|
+
else
|
142
|
+
if self.class.whitespace_not_newline?(c)
|
143
|
+
saver.add(c)
|
144
|
+
else
|
145
|
+
return c
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# The rules here are intended to maximize convenience while
|
152
|
+
# avoiding confusion with real valid JSON. Basically anything
|
153
|
+
# that parses as JSON is treated the JSON way and otherwise
|
154
|
+
# we assume it's a string and let the parser sort it out.
|
155
|
+
def pull_unquoted_text
|
156
|
+
origin = @line_origin
|
157
|
+
io = StringIO.new
|
158
|
+
c = next_char_raw
|
159
|
+
while true
|
160
|
+
if (c == -1) or
|
161
|
+
(NOT_IN_UNQUOTED_TEXT.index(c)) or
|
162
|
+
(self.class.whitespace?(c)) or
|
163
|
+
(start_of_comment?(c))
|
164
|
+
break
|
165
|
+
else
|
166
|
+
io << c
|
167
|
+
end
|
168
|
+
|
169
|
+
# we parse true/false/null tokens as such no matter
|
170
|
+
# what is after them, as long as they are at the
|
171
|
+
# start of the unquoted token.
|
172
|
+
if io.length == 4
|
173
|
+
if io.string == "true"
|
174
|
+
return Tokens.new_boolean(origin, true)
|
175
|
+
elsif io.string == "null"
|
176
|
+
return Tokens.new_null(origin)
|
177
|
+
end
|
178
|
+
elsif io.length == 5
|
179
|
+
if io.string == "false"
|
180
|
+
return Tokens.new_boolean(origin, false)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
c = next_char_raw
|
185
|
+
end
|
186
|
+
|
187
|
+
# put back the char that ended the unquoted text
|
188
|
+
put_back(c)
|
189
|
+
|
190
|
+
Tokens.new_unquoted_text(origin, io.string)
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
def pull_comment(first_char)
|
195
|
+
if first_char == '/'
|
196
|
+
discard = next_char_raw
|
197
|
+
if discard != '/'
|
198
|
+
raise ConfigBugError, "called pullComment but // not seen"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
io = StringIO.new
|
203
|
+
while true
|
204
|
+
c = next_char_raw
|
205
|
+
if (c == -1) || (c == "\n")
|
206
|
+
put_back(c)
|
207
|
+
return Tokens.new_comment(@line_origin, io.string)
|
208
|
+
else
|
209
|
+
io << c
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def pull_number(first_char)
|
215
|
+
sb = StringIO.new
|
216
|
+
sb << first_char
|
217
|
+
contained_decimal_or_e = false
|
218
|
+
c = next_char_raw
|
219
|
+
while (c != -1) && (NUMBER_CHARS.index(c))
|
220
|
+
if (c == '.') ||
|
221
|
+
(c == 'e') ||
|
222
|
+
(c == 'E')
|
223
|
+
contained_decimal_or_e = true
|
224
|
+
end
|
225
|
+
sb << c
|
226
|
+
c = next_char_raw
|
227
|
+
end
|
228
|
+
# the last character we looked at wasn't part of the number, put it
|
229
|
+
# back
|
230
|
+
put_back(c)
|
231
|
+
s = sb.string
|
232
|
+
begin
|
233
|
+
if contained_decimal_or_e
|
234
|
+
# force floating point representation
|
235
|
+
Tokens.new_double(@line_origin, s.to_f, s)
|
236
|
+
else
|
237
|
+
Tokens.new_long(@line_origin, s.to_i, s)
|
238
|
+
end
|
239
|
+
rescue ArgumentError => e
|
240
|
+
if e.message =~ /^invalid value for (Float|Integer)\(\)/
|
241
|
+
# not a number after all, see if it's an unquoted string.
|
242
|
+
s.each do |u|
|
243
|
+
if NOT_IN_UNQUOTED_TEXT.index
|
244
|
+
raise problem(u, "Reserved character '#{u}'" +
|
245
|
+
"is not allowed outside quotes", true)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
# no evil chars so we just decide this was a string and
|
249
|
+
# not a number.
|
250
|
+
Tokens.new_unquoted_text(@line_origin, s)
|
251
|
+
else
|
252
|
+
raise e
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def pull_quoted_string
|
258
|
+
# the open quote has already been consumed
|
259
|
+
sb = StringIO.new
|
260
|
+
c = ""
|
261
|
+
while c != '"'
|
262
|
+
c = next_char_raw
|
263
|
+
if c == -1
|
264
|
+
raise problem("End of input but string quote was still open")
|
265
|
+
end
|
266
|
+
|
267
|
+
if c == "\\"
|
268
|
+
pull_escape_sequence(sb)
|
269
|
+
elsif c == '"'
|
270
|
+
# done!
|
271
|
+
elsif c =~ /[[:cntrl:]]/
|
272
|
+
raise problem(c, "JSON does not allow unescaped #{c}" +
|
273
|
+
" in quoted strings, use a backslash escape")
|
274
|
+
else
|
275
|
+
sb << c
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
# maybe switch to triple-quoted string, sort of hacky...
|
280
|
+
if sb.length == 0
|
281
|
+
third = next_char_raw
|
282
|
+
if third == '"'
|
283
|
+
append_triple_quoted_string(sb)
|
284
|
+
else
|
285
|
+
put_back(third)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
Tokens.new_string(@line_origin, sb.string)
|
290
|
+
end
|
291
|
+
|
292
|
+
def pull_next_token(saver)
|
293
|
+
c = next_char_after_whitespace(saver)
|
294
|
+
if c == -1
|
295
|
+
Tokens::EOF
|
296
|
+
elsif c == "\n"
|
297
|
+
# newline tokens have the just-ended line number
|
298
|
+
line = Tokens.new_line(@line_origin)
|
299
|
+
@line_number += 1
|
300
|
+
@line_origin = @origin.set_line_number(@line_number)
|
301
|
+
line
|
302
|
+
else
|
303
|
+
t = nil
|
304
|
+
if start_of_comment?(c)
|
305
|
+
t = pull_comment(c)
|
306
|
+
else
|
307
|
+
t = case c
|
308
|
+
when '"' then pull_quoted_string
|
309
|
+
when '$' then pull_substitution
|
310
|
+
when ':' then Tokens::COLON
|
311
|
+
when ',' then Tokens::COMMA
|
312
|
+
when '=' then Tokens::EQUALS
|
313
|
+
when '{' then Tokens::OPEN_CURLY
|
314
|
+
when '}' then Tokens::CLOSE_CURLY
|
315
|
+
when '[' then Tokens::OPEN_SQUARE
|
316
|
+
when ']' then Tokens::CLOSE_SQUARE
|
317
|
+
when '+' then pull_plus_equals
|
318
|
+
else nil
|
319
|
+
end
|
320
|
+
|
321
|
+
if t.nil?
|
322
|
+
if FIRST_NUMBER_CHARS.index(c)
|
323
|
+
t = pull_number(c)
|
324
|
+
elsif NOT_IN_UNQUOTED_TEXT.index(c)
|
325
|
+
raise problem(c, "Reserved character '#{c}' is not allowed outside quotes", true)
|
326
|
+
else
|
327
|
+
put_back(c)
|
328
|
+
t = pull_unquoted_text
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
if t.nil?
|
334
|
+
raise ConfigBugError, "bug: failed to generate next token"
|
335
|
+
end
|
336
|
+
|
337
|
+
t
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def queue_next_token
|
342
|
+
t = pull_next_token(@whitespace_saver)
|
343
|
+
whitespace = @whitespace_saver.check(t, @origin, @line_number)
|
344
|
+
if whitespace
|
345
|
+
@tokens.push(whitespace)
|
346
|
+
end
|
347
|
+
@tokens.push(t)
|
348
|
+
end
|
349
|
+
|
350
|
+
def next
|
351
|
+
t = @tokens.shift
|
352
|
+
if (@tokens.empty?) and (t != Tokens::EOF)
|
353
|
+
begin
|
354
|
+
queue_next_token
|
355
|
+
rescue TokenizerProblemError => e
|
356
|
+
@tokens.push(e.problem)
|
357
|
+
end
|
358
|
+
if @tokens.empty?
|
359
|
+
raise ConfigBugError, "bug: tokens queue should not be empty here"
|
360
|
+
end
|
361
|
+
end
|
362
|
+
t
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
|
367
|
+
def self.tokenize(origin, input, syntax)
|
368
|
+
TokenIterator.new(origin, input, syntax != Hocon::ConfigSyntax::JSON)
|
369
|
+
end
|
370
|
+
end
|