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.
Files changed (39) hide show
  1. data/LICENSE +202 -0
  2. data/README.md +19 -0
  3. data/lib/hocon.rb +2 -0
  4. data/lib/hocon/config_error.rb +12 -0
  5. data/lib/hocon/config_factory.rb +9 -0
  6. data/lib/hocon/config_object.rb +4 -0
  7. data/lib/hocon/config_parse_options.rb +53 -0
  8. data/lib/hocon/config_render_options.rb +46 -0
  9. data/lib/hocon/config_syntax.rb +7 -0
  10. data/lib/hocon/config_value_type.rb +26 -0
  11. data/lib/hocon/impl.rb +5 -0
  12. data/lib/hocon/impl/abstract_config_object.rb +64 -0
  13. data/lib/hocon/impl/abstract_config_value.rb +130 -0
  14. data/lib/hocon/impl/config_concatenation.rb +136 -0
  15. data/lib/hocon/impl/config_float.rb +9 -0
  16. data/lib/hocon/impl/config_impl.rb +10 -0
  17. data/lib/hocon/impl/config_impl_util.rb +78 -0
  18. data/lib/hocon/impl/config_int.rb +31 -0
  19. data/lib/hocon/impl/config_number.rb +27 -0
  20. data/lib/hocon/impl/config_string.rb +37 -0
  21. data/lib/hocon/impl/full_includer.rb +4 -0
  22. data/lib/hocon/impl/origin_type.rb +9 -0
  23. data/lib/hocon/impl/parseable.rb +151 -0
  24. data/lib/hocon/impl/parser.rb +882 -0
  25. data/lib/hocon/impl/path.rb +59 -0
  26. data/lib/hocon/impl/path_builder.rb +36 -0
  27. data/lib/hocon/impl/resolve_status.rb +18 -0
  28. data/lib/hocon/impl/simple_config.rb +11 -0
  29. data/lib/hocon/impl/simple_config_list.rb +70 -0
  30. data/lib/hocon/impl/simple_config_object.rb +178 -0
  31. data/lib/hocon/impl/simple_config_origin.rb +174 -0
  32. data/lib/hocon/impl/simple_include_context.rb +7 -0
  33. data/lib/hocon/impl/simple_includer.rb +19 -0
  34. data/lib/hocon/impl/token.rb +32 -0
  35. data/lib/hocon/impl/token_type.rb +42 -0
  36. data/lib/hocon/impl/tokenizer.rb +370 -0
  37. data/lib/hocon/impl/tokens.rb +157 -0
  38. data/lib/hocon/impl/unmergeable.rb +4 -0
  39. metadata +84 -0
@@ -0,0 +1,7 @@
1
+ require 'hocon/impl'
2
+
3
+ class Hocon::Impl::SimpleIncludeContext
4
+ def initialize(parseable)
5
+ @parseable = parseable
6
+ end
7
+ end
@@ -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