hocon 0.9.5 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -2
  3. data/README.md +22 -10
  4. data/lib/hocon.rb +9 -3
  5. data/lib/hocon/config_factory.rb +4 -0
  6. data/lib/hocon/config_value_factory.rb +13 -2
  7. data/lib/hocon/impl/config_reference.rb +5 -2
  8. data/lib/hocon/impl/simple_config_origin.rb +1 -1
  9. data/spec/fixtures/parse_render/example1/input.conf +21 -0
  10. data/spec/fixtures/parse_render/example1/output.conf +26 -0
  11. data/spec/fixtures/parse_render/example1/output_nocomments.conf +17 -0
  12. data/spec/fixtures/parse_render/example2/input.conf +10 -0
  13. data/spec/fixtures/parse_render/example2/output.conf +17 -0
  14. data/spec/fixtures/parse_render/example2/output_nocomments.conf +17 -0
  15. data/spec/fixtures/parse_render/example3/input.conf +2 -0
  16. data/spec/fixtures/parse_render/example3/output.conf +2 -0
  17. data/spec/fixtures/parse_render/example4/input.json +6 -0
  18. data/spec/fixtures/parse_render/example4/output.conf +6 -0
  19. data/spec/fixtures/test_utils/resources/bom.conf +2 -0
  20. data/spec/fixtures/test_utils/resources/cycle.conf +1 -0
  21. data/spec/fixtures/test_utils/resources/file-include.conf +5 -0
  22. data/spec/fixtures/test_utils/resources/include-from-list.conf +4 -0
  23. data/spec/fixtures/test_utils/resources/subdir/bar.conf +1 -0
  24. data/spec/fixtures/test_utils/resources/subdir/baz.conf +1 -0
  25. data/spec/fixtures/test_utils/resources/subdir/foo.conf +5 -0
  26. data/spec/fixtures/test_utils/resources/test01.conf +80 -0
  27. data/spec/fixtures/test_utils/resources/test01.json +4 -0
  28. data/spec/fixtures/test_utils/resources/test03.conf +36 -0
  29. data/spec/spec_helper.rb +43 -0
  30. data/spec/test_utils.rb +757 -0
  31. data/spec/unit/typesafe/config/concatenation_spec.rb +417 -0
  32. data/spec/unit/typesafe/config/conf_parser_spec.rb +822 -0
  33. data/spec/unit/typesafe/config/config_document_parser_spec.rb +494 -0
  34. data/spec/unit/typesafe/config/config_document_spec.rb +576 -0
  35. data/spec/unit/typesafe/config/config_factory_spec.rb +120 -0
  36. data/spec/unit/typesafe/config/config_node_spec.rb +552 -0
  37. data/spec/unit/typesafe/config/config_value_factory_spec.rb +85 -0
  38. data/spec/unit/typesafe/config/config_value_spec.rb +935 -0
  39. data/spec/unit/typesafe/config/hocon_spec.rb +54 -0
  40. data/spec/unit/typesafe/config/path_spec.rb +261 -0
  41. data/spec/unit/typesafe/config/public_api_spec.rb +520 -0
  42. data/spec/unit/typesafe/config/simple_config_spec.rb +112 -0
  43. data/spec/unit/typesafe/config/token_spec.rb +188 -0
  44. data/spec/unit/typesafe/config/tokenizer_spec.rb +801 -0
  45. metadata +39 -3
@@ -0,0 +1,417 @@
1
+ # encoding: utf-8
2
+
3
+ require 'test_utils'
4
+
5
+ describe "concatenation" do
6
+
7
+ it "string concat, no substitutions" do
8
+ conf = TestUtils.parse_config(' a : true "xyz" 123 foo ').resolve
9
+ expect(conf.get_string("a")).to eq("true xyz 123 foo")
10
+ end
11
+
12
+ it "trivial string concat" do
13
+ conf = TestUtils.parse_config(" a : ${x}foo, x = 1 ").resolve
14
+ expect(conf.get_string("a")).to eq("1foo")
15
+ end
16
+
17
+ it "two substitutions and string concat" do
18
+ conf = TestUtils.parse_config(" a : ${x}foo${x}, x = 1 ").resolve
19
+ expect(conf.get_string("a")).to eq("1foo1")
20
+ end
21
+
22
+ it "string concat cannot span lines" do
23
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) {
24
+ TestUtils.parse_config(" a : ${x}
25
+ foo, x = 1 ")
26
+ }
27
+ expect(e.message).to include("not be followed")
28
+ expect(e.message).to include("','")
29
+ end
30
+
31
+ it "no objects in string concat" do
32
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) {
33
+ TestUtils.parse_config(" a : abc { x : y } ")
34
+ }
35
+ expect(e.message).to include("Cannot concatenate")
36
+ expect(e.message).to include("abc")
37
+ expect(e.message).to include('{"x":"y"}')
38
+ end
39
+
40
+ it "no object concat with nil" do
41
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) {
42
+ TestUtils.parse_config(" a : null { x : y } ")
43
+ }
44
+ expect(e.message).to include("Cannot concatenate")
45
+ expect(e.message).to include("null")
46
+ expect(e.message).to include('{"x":"y"}')
47
+ end
48
+
49
+ it "no arrays in string concat" do
50
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) {
51
+ TestUtils.parse_config(" a : abc [1, 2] ")
52
+ }
53
+ expect(e.message).to include("Cannot concatenate")
54
+ expect(e.message).to include("abc")
55
+ expect(e.message).to include("[1,2]")
56
+ end
57
+
58
+ it "no objects substituted in string concat" do
59
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) {
60
+ TestUtils.parse_config(" a : abc ${x}, x : { y : z } ").resolve
61
+ }
62
+ expect(e.message).to include("Cannot concatenate")
63
+ expect(e.message).to include("abc")
64
+ end
65
+
66
+ it "no arrays substituted in string concat" do
67
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) {
68
+ TestUtils.parse_config(" a : abc ${x}, x : [1,2] ").resolve
69
+ }
70
+ expect(e.message).to include("Cannot concatenate")
71
+ expect(e.message).to include("abc")
72
+ end
73
+
74
+ it "no substitutions in list concat" do
75
+ conf = TestUtils.parse_config(" a : [1,2] [3,4] ")
76
+ expect([1, 2, 3, 4]).to eq(conf.get_list("a").unwrapped)
77
+ end
78
+
79
+ it "list concat with substitutions" do
80
+ conf = TestUtils.parse_config(" a : ${x} [3,4] ${y}, x : [1,2], y : [5,6] ").resolve
81
+ expect([1, 2, 3, 4, 5, 6]).to eq(conf.get_list("a").unwrapped)
82
+ end
83
+
84
+ it "list concat self referential" do
85
+ conf = TestUtils.parse_config(" a : [1, 2], a : ${a} [3,4], a : ${a} [5,6] ").resolve
86
+ expect([1, 2, 3, 4, 5, 6]).to eq(conf.get_list("a").unwrapped)
87
+ end
88
+
89
+ it "no substitutions in list concat cannot span lines" do
90
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) {
91
+ TestUtils.parse_config(" a : [1,2]
92
+ [3,4] ")
93
+ }
94
+ expect(e.message).to include("expecting")
95
+ expect(e.message).to include("'['")
96
+ end
97
+
98
+ it "list concat can span lines inside brackest" do
99
+ conf = TestUtils.parse_config(" a : [1,2
100
+ ] [3,4] ")
101
+ expect([1, 2, 3, 4]).to eq(conf.get_list("a").unwrapped)
102
+ end
103
+
104
+ it "no substitutions object concat" do
105
+ conf = TestUtils.parse_config(" a : { b : c } { x : y } ")
106
+ expect({"b" => "c", "x" => "y"}).to eq(conf.get_object("a").unwrapped)
107
+ end
108
+
109
+ it "object concat merge order" do
110
+ conf = TestUtils.parse_config(" a : { b : 1 } { b : 2 } { b : 3 } { b : 4 } ")
111
+ expect(4).to eq(conf.get_int("a.b"))
112
+ end
113
+
114
+ it "object concat with substitutions" do
115
+ conf = TestUtils.parse_config(" a : ${x} { b : 1 } ${y}, x : { a : 0 }, y : { c : 2 } ").resolve
116
+ expect({"a" => 0, "b" => 1, "c" => 2}).to eq(conf.get_object("a").unwrapped)
117
+ end
118
+
119
+ it "object concat self referential" do
120
+ conf = TestUtils.parse_config(" a : { a : 0 }, a : ${a} { b : 1 }, a : ${a} { c : 2 } ").resolve
121
+ expect({"a" => 0, "b" => 1, "c" => 2}).to eq(conf.get_object("a").unwrapped)
122
+ end
123
+
124
+ it "object concat self referential override" do
125
+ conf = TestUtils.parse_config(" a : { b : 3 }, a : { b : 2 } ${a} ").resolve
126
+ expect({"b" => 3}).to eq(conf.get_object("a").unwrapped)
127
+ end
128
+
129
+ it "no substitutions object concat cannot span lines" do
130
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) {
131
+ TestUtils.parse_config(" a : { b : c }
132
+ { x : y }")
133
+ }
134
+ expect(e.message).to include("expecting")
135
+ expect(e.message).to include("'{'")
136
+ end
137
+
138
+ it "object concat can span lines inside braces" do
139
+ conf = TestUtils.parse_config(" a : { b : c
140
+ } { x : y } ")
141
+ expect({"b" => "c", "x" => "y"}).to eq(conf.get_object("a").unwrapped)
142
+ end
143
+
144
+ it "string concat inside array value" do
145
+ conf = TestUtils.parse_config(" a : [ foo bar 10 ] ")
146
+ expect(["foo bar 10"]).to eq(conf.get_string_list("a"))
147
+ end
148
+
149
+ it "string non concat inside array value" do
150
+ conf = TestUtils.parse_config(" a : [ foo
151
+ bar
152
+ 10 ] ")
153
+ expect(["foo", "bar", "10"]).to eq(conf.get_string_list("a"))
154
+ end
155
+
156
+ it "object concat inside array value" do
157
+ conf = TestUtils.parse_config(" a : [ { b : c } { x : y } ] ")
158
+ expect([{"b" => "c", "x" => "y"}]).to eq(conf.get_object_list("a").map { |x| x.unwrapped })
159
+ end
160
+
161
+ it "object non concat inside array value" do
162
+ conf = TestUtils.parse_config(" a : [ { b : c }
163
+ { x : y } ] ")
164
+ expect([{"b" => "c"}, {"x" => "y"}]).to eq(conf.get_object_list("a").map { |x| x.unwrapped })
165
+ end
166
+
167
+ it "list concat inside array value" do
168
+ conf = TestUtils.parse_config(" a : [ [1, 2] [3, 4] ] ")
169
+ expect([[1,2,3,4]]).to eq(conf.get_list("a").unwrapped)
170
+ end
171
+
172
+ it "list non concat inside array value" do
173
+ conf = TestUtils.parse_config(" a : [ [1, 2]
174
+ [3, 4] ] ")
175
+ expect([[1, 2], [3, 4]]).to eq(conf.get_list("a").unwrapped)
176
+ end
177
+
178
+ it "string concats are keys" do
179
+ conf = TestUtils.parse_config(' 123 foo : "value" ')
180
+ expect("value").to eq(conf.get_string("123 foo"))
181
+ end
182
+
183
+ it "objects are not keys" do
184
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) {
185
+ TestUtils.parse_config('{ { a : 1 } : "value" }')
186
+ }
187
+ expect(e.message).to include("expecting a close")
188
+ expect(e.message).to include("'{'")
189
+ end
190
+
191
+ it "arrays are not keys" do
192
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) {
193
+ TestUtils.parse_config('{ [ "a" ] : "value" }')
194
+ }
195
+ expect(e.message).to include("expecting a close")
196
+ expect(e.message).to include("'['")
197
+ end
198
+
199
+ it "empty array plus equals" do
200
+ conf = TestUtils.parse_config(' a = [], a += 2 ').resolve
201
+ expect([2]).to eq(conf.get_int_list("a"))
202
+ end
203
+
204
+ it "missing array plus equals" do
205
+ conf = TestUtils.parse_config(' a += 2 ').resolve
206
+ expect([2]).to eq(conf.get_int_list("a"))
207
+ end
208
+
209
+ it "short array plus equals" do
210
+ conf = TestUtils.parse_config(' a = [1], a += 2 ').resolve
211
+ expect([1, 2]).to eq(conf.get_int_list("a"))
212
+ end
213
+
214
+ it "number plus equals" do
215
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) {
216
+ TestUtils.parse_config(' a = 10, a += 2 ').resolve
217
+ }
218
+ expect(e.message).to include("Cannot concatenate")
219
+ expect(e.message).to include("10")
220
+ expect(e.message).to include("[2]")
221
+ end
222
+
223
+ it "string plus equals" do
224
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) {
225
+ TestUtils.parse_config(' a = abc, a += 2 ').resolve
226
+ }
227
+ expect(e.message).to include("Cannot concatenate")
228
+ expect(e.message).to include("abc")
229
+ expect(e.message).to include("[2]")
230
+ end
231
+
232
+ it "objects plus equals" do
233
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) {
234
+ TestUtils.parse_config(' a = { x : y }, a += 2 ').resolve
235
+ }
236
+ expect(e.message).to include("Cannot concatenate")
237
+ expect(e.message).to include("\"x\":\"y\"")
238
+ expect(e.message).to include("[2]")
239
+ end
240
+
241
+ it "plus equals nested path" do
242
+ conf = TestUtils.parse_config(' a.b.c = [1], a.b.c += 2 ').resolve
243
+ expect([1, 2]).to eq(conf.get_int_list("a.b.c"))
244
+ end
245
+
246
+ it "plus equals nested objects" do
247
+ conf = TestUtils.parse_config(' a : { b : { c : [1] } }, a : { b : { c += 2 } }').resolve
248
+ expect([1, 2]).to eq(conf.get_int_list("a.b.c"))
249
+ end
250
+
251
+ it "plus equals single nested object" do
252
+ conf = TestUtils.parse_config(' a : { b : { c : [1], c += 2 } }').resolve
253
+ expect([1, 2]).to eq(conf.get_int_list("a.b.c"))
254
+ end
255
+
256
+ it "substitution plus equals substitution" do
257
+ conf = TestUtils.parse_config(' a = ${x}, a += ${y}, x = [1], y = 2 ').resolve
258
+ expect([1, 2]).to eq(conf.get_int_list("a"))
259
+ end
260
+
261
+ it "plus equals multiple times" do
262
+ conf = TestUtils.parse_config(' a += 1, a += 2, a += 3 ').resolve
263
+ expect([1, 2, 3]).to eq(conf.get_int_list("a"))
264
+ end
265
+
266
+ it "plus equals multiple times nested" do
267
+ conf = TestUtils.parse_config(' x { a += 1, a += 2, a += 3 } ').resolve
268
+ expect([1, 2, 3]).to eq(conf.get_int_list("x.a"))
269
+ end
270
+
271
+ it "plus equals an object multiple times" do
272
+ conf = TestUtils.parse_config(' a += { b: 1 }, a += { b: 2 }, a += { b: 3 } ').resolve
273
+ expect([1, 2, 3]).to eq(conf.get_object_list("a").map { |x| x.to_config.get_int("b")})
274
+ end
275
+
276
+ it "plus equals an object multiple times nested" do
277
+ conf = TestUtils.parse_config(' x { a += { b: 1 }, a += { b: 2 }, a += { b: 3 } } ').resolve
278
+ expect([1, 2, 3]).to eq(conf.get_object_list("x.a").map { |x| x.to_config.get_int("b") })
279
+ end
280
+
281
+ # We would ideally make this case NOT throw an exception but we need to do some work
282
+ # to get there, see https: // github.com/typesafehub/config/issues/160
283
+ it "plus equals multiple times nested in array" do
284
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) {
285
+ conf = TestUtils.parse_config('x = [ { a += 1, a += 2, a += 3 } ] ').resolve
286
+ expect([1, 2, 3]).to eq(conf.get_object_list("x").to_config.get_int_list("a"))
287
+ }
288
+ expect(e.message).to include("limitation")
289
+ end
290
+
291
+ # We would ideally make this case NOT throw an exception but we need to do some work
292
+ # to get there, see https: // github.com/typesafehub/config/issues/160
293
+ it "plus equals multiple times nested in plus equals" do
294
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) {
295
+ conf = TestUtils.parse_config('x += { a += 1, a += 2, a += 3 } ').resolve
296
+ expect([1, 2, 3]).to eq(conf.get_object_list("x").to_config.get_int_list("a"))
297
+ }
298
+ expect(e.message).to include("limitation")
299
+ end
300
+
301
+ # from https://github.com/typesafehub/config/issues/177
302
+ it "array concatenation in double nested delayed merge" do
303
+ unresolved = TestUtils.parse_config("d { x = [] }, c : ${d}, c { x += 1, x += 2 }")
304
+ conf = unresolved.resolve
305
+ expect([1,2]).to eq(conf.get_int_list("c.x"))
306
+ end
307
+
308
+ # from https://github.com/typesafehub/config/issues/177
309
+ it "array concatenation as part of delayed merge" do
310
+ unresolved = TestUtils.parse_config(" c { x: [], x : ${c.x}[1], x : ${c.x}[2] }")
311
+ conf = unresolved.resolve
312
+ expect([1,2]).to eq(conf.get_int_list("c.x"))
313
+ end
314
+
315
+ # from https://github.com/typesafehub/config/issues/177
316
+ it "array concatenation in double nested delayed merge 2" do
317
+ unresolved = TestUtils.parse_config("d { x = [] }, c : ${d}, c { x : ${c.x}[1], x : ${c.x}[2] }")
318
+ conf = unresolved.resolve
319
+ expect([1,2]).to eq(conf.get_int_list("c.x"))
320
+ end
321
+
322
+ # from https://github.com/typesafehub/config/issues/177
323
+ it "array concatenation in triple nested delayed merge" do
324
+ unresolved = TestUtils.parse_config("{ r: { d.x=[] }, q: ${r}, q : { d { x = [] }, c : ${q.d}, c { x : ${q.c.x}[1], x : ${q.c.x}[2] } } }")
325
+ conf = unresolved.resolve
326
+ expect([1,2]).to eq(conf.get_int_list("q.c.x"))
327
+ end
328
+
329
+ it "concat undefined substitution with string" do
330
+ conf = TestUtils.parse_config("a = foo${?bar}").resolve
331
+ expect("foo").to eq(conf.get_string("a"))
332
+ end
333
+
334
+ it "concat defined optional substitution with string" do
335
+ conf = TestUtils.parse_config("bar=bar, a = foo${?bar}").resolve
336
+ expect("foobar").to eq(conf.get_string("a"))
337
+ end
338
+
339
+ it "concat defined substitution with array" do
340
+ conf = TestUtils.parse_config("a = [1] ${?bar}").resolve
341
+ expect([1]).to eq(conf.get_int_list("a"))
342
+ end
343
+
344
+ it "concat defined optional substitution with array" do
345
+ conf = TestUtils.parse_config("bar=[2], a = [1] ${?bar}").resolve
346
+ expect([1, 2]).to eq(conf.get_int_list("a"))
347
+ end
348
+
349
+ it "concat undefined substitution with object" do
350
+ conf = TestUtils.parse_config('a = { x : "foo" } ${?bar}').resolve
351
+ expect('foo').to eq(conf.get_string("a.x"))
352
+ end
353
+
354
+ it "concat defined optional substitution with object" do
355
+ conf = TestUtils.parse_config('bar={ y : 42 }, a = { x : "foo" } ${?bar}').resolve
356
+ expect('foo').to eq(conf.get_string("a.x"))
357
+ expect(42).to eq(conf.get_int("a.y"))
358
+ end
359
+
360
+ it "concat two undefined substitutions" do
361
+ conf = TestUtils.parse_config("a = ${?foo}${?bar}").resolve
362
+ expect(conf.has_path?("a")).to be_falsey
363
+ end
364
+
365
+ it "concat several undefined substitutions" do
366
+ conf = TestUtils.parse_config("a = ${?foo}${?bar}${?baz}${?woooo}").resolve
367
+ expect(conf.has_path?("a")).to be_falsey
368
+ end
369
+
370
+ it "concat two undefined substitutions with a space" do
371
+ conf = TestUtils.parse_config("a = ${?foo} ${?bar}").resolve
372
+ expect(conf.get_string("a")).to eq(" ")
373
+ end
374
+
375
+ it "concat two defined substitutions with a space" do
376
+ conf = TestUtils.parse_config("foo=abc, bar=def, a = ${foo} ${bar}").resolve
377
+ expect(conf.get_string("a")).to eq("abc def")
378
+ end
379
+
380
+ it "concat two undefined substitutions with empty string" do
381
+ conf = TestUtils.parse_config('a = ""${?foo}${?bar}').resolve
382
+ expect(conf.get_string("a")).to eq("")
383
+ end
384
+
385
+ it "concat substitutions that are objects with no space" do
386
+ conf = TestUtils.parse_config('foo = { a : 1}, bar = { b : 2 }, x = ${foo}${bar}').resolve
387
+ expect(1).to eq(conf.get_int("x.a"))
388
+ expect(2).to eq(conf.get_int("x.b"))
389
+ end
390
+
391
+ # whitespace is insignificant if substitutions don't turn out to be a string
392
+ it "concat substitutions that are objects with space" do
393
+ conf = TestUtils.parse_config('foo = { a : 1}, bar = { b : 2 }, x = ${foo} ${bar}').resolve
394
+ expect(1).to eq(conf.get_int("x.a"))
395
+ expect(2).to eq(conf.get_int("x.b"))
396
+ end
397
+
398
+ # whitespace is insignificant if substitutions don't turn out to be a string
399
+ it "concat substitutions that are lists with space" do
400
+ conf = TestUtils.parse_config('foo = [1], bar = [2], x = ${foo} ${bar}').resolve
401
+ expect([1,2]).to eq(conf.get_int_list("x"))
402
+ end
403
+
404
+ # but quoted whitespace should be an error
405
+ it "concat substitutions that are objects with quoted space" do
406
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) {
407
+ conf = TestUtils.parse_config('foo = { a : 1}, bar = { b : 2 }, x = ${foo}" "${bar}').resolve
408
+ }
409
+ end
410
+
411
+ # but quoted whitespace should be an error
412
+ it "concat substitutions that are lists with quoted space" do
413
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) {
414
+ conf = TestUtils.parse_config('foo = [1], bar = [2], x = ${foo}" "${bar}').resolve
415
+ }
416
+ end
417
+ end
@@ -0,0 +1,822 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'test_utils'
5
+ require 'hocon/config_parse_options'
6
+ require 'hocon/config_syntax'
7
+ require 'hocon/impl/abstract_config_object'
8
+ require 'hocon/impl/resolve_context'
9
+ require 'hocon/config_resolve_options'
10
+ require 'hocon/config_error'
11
+ require 'hocon/impl/simple_config_origin'
12
+ require 'hocon/config_list'
13
+ require 'hocon/impl/config_reference'
14
+ require 'hocon/impl/path_parser'
15
+ require 'hocon/impl/parseable'
16
+ require 'hocon/config_factory'
17
+
18
+ def parse_without_resolving(s)
19
+ options = Hocon::ConfigParseOptions.defaults.
20
+ set_origin_description("test conf string").
21
+ set_syntax(Hocon::ConfigSyntax::CONF)
22
+ Hocon::Impl::Parseable.new_string(s, options).parse_value
23
+ end
24
+
25
+ def parse(s)
26
+ tree = parse_without_resolving(s)
27
+
28
+ if tree.is_a?(Hocon::Impl::AbstractConfigObject)
29
+ Hocon::Impl::ResolveContext.resolve(tree, tree,
30
+ Hocon::ConfigResolveOptions.no_system)
31
+ else
32
+ tree
33
+ end
34
+ end
35
+
36
+
37
+ describe "Config Parser" do
38
+ context "invalid_conf_throws" do
39
+ TestUtils.whitespace_variations(TestUtils::InvalidConf, false).each do |invalid|
40
+ it "should raise an error for invalid config string '#{invalid.test}'" do
41
+ TestUtils.add_offending_json_to_exception("config", invalid.test) {
42
+ TestUtils.intercept(Hocon::ConfigError) {
43
+ parse(invalid.test)
44
+ }
45
+ }
46
+ end
47
+ end
48
+ end
49
+
50
+ context "valid_conf_works" do
51
+ TestUtils.whitespace_variations(TestUtils::ValidConf, true).each do |valid|
52
+ it "should successfully parse config string '#{valid.test}'" do
53
+ our_ast = TestUtils.add_offending_json_to_exception("config-conf", valid.test) {
54
+ parse(valid.test)
55
+ }
56
+ # let's also check round-trip rendering
57
+ rendered = our_ast.render
58
+ reparsed = TestUtils.add_offending_json_to_exception("config-conf-reparsed", rendered) {
59
+ parse(rendered)
60
+ }
61
+ expect(our_ast).to eq(reparsed)
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ def parse_path(s)
68
+ first_exception = nil
69
+ second_exception = nil
70
+ # parser first by wrapping into a whole document and using the regular parser
71
+ result =
72
+ begin
73
+ tree = parse_without_resolving("[${#{s}}]")
74
+ if tree.is_a?(Hocon::ConfigList)
75
+ ref = tree[0]
76
+ if ref.is_a?(Hocon::Impl::ConfigReference)
77
+ ref.expression.path
78
+ end
79
+ end
80
+ rescue Hocon::ConfigError => e
81
+ first_exception = e
82
+ nil
83
+ end
84
+
85
+ # also parse with the standalone path parser and be sure the outcome is the same
86
+ begin
87
+ should_be_same = Hocon::Impl::PathParser.parse_path(s)
88
+ unless result == should_be_same
89
+ raise "expected '#{result}' to equal '#{should_be_same}'"
90
+ end
91
+ rescue Hocon::ConfigError => e
92
+ second_exception = e
93
+ end
94
+
95
+ if first_exception.nil? && (!second_exception.nil?)
96
+ raise "only the standalone path parser threw: #{second_exception}"
97
+ end
98
+
99
+ if (!first_exception.nil?) && second_exception.nil?
100
+ raise "only the whole-document parser threw: #{first_exception}"
101
+ end
102
+
103
+ if !first_exception.nil?
104
+ raise first_exception
105
+ end
106
+ if !second_exception.nil?
107
+ raise "wtf, should have thrown because not equal"
108
+ end
109
+
110
+ result
111
+ end
112
+
113
+ def test_path_parsing(first, second)
114
+ it "'#{first}' should parse to same path as '#{second}'" do
115
+ expect(TestUtils.path(*first)).to eq(parse_path(second))
116
+ end
117
+ end
118
+
119
+ describe "Config Parser" do
120
+ context "path_parsing" do
121
+ test_path_parsing(["a"], "a")
122
+ test_path_parsing(["a", "b"], "a.b")
123
+ test_path_parsing(["a.b"], "\"a.b\"")
124
+ test_path_parsing(["a."], "\"a.\"")
125
+ test_path_parsing([".b"], "\".b\"")
126
+ test_path_parsing(["true"], "true")
127
+ test_path_parsing(["a"], " a ")
128
+ test_path_parsing(["a ", "b"], " a .b")
129
+ test_path_parsing(["a ", " b"], " a . b")
130
+ test_path_parsing(["a b"], " a b")
131
+ test_path_parsing(["a", "b.c", "d"], "a.\"b.c\".d")
132
+ test_path_parsing(["3", "14"], "3.14")
133
+ test_path_parsing(["3", "14", "159"], "3.14.159")
134
+ test_path_parsing(["a3", "14"], "a3.14")
135
+ test_path_parsing([""], "\"\"")
136
+ test_path_parsing(["a", "", "b"], "a.\"\".b")
137
+ test_path_parsing(["a", ""], "a.\"\"")
138
+ test_path_parsing(["", "b"], "\"\".b")
139
+ test_path_parsing(["", "", ""], ' "".""."" ')
140
+ test_path_parsing(["a-c"], "a-c")
141
+ test_path_parsing(["a_c"], "a_c")
142
+ test_path_parsing(["-"], "\"-\"")
143
+ test_path_parsing(["-"], "-")
144
+ test_path_parsing(["-foo"], "-foo")
145
+ test_path_parsing(["-10"], "-10")
146
+
147
+ # here 10.0 is part of an unquoted string
148
+ test_path_parsing(["foo10", "0"], "foo10.0")
149
+ # here 10.0 is a number that gets value-concatenated
150
+ test_path_parsing(["10", "0foo"], "10.0foo")
151
+ # just a number
152
+ test_path_parsing(["10", "0"], "10.0")
153
+ # multiple-decimal number
154
+ test_path_parsing(["1", "2", "3", "4"], "1.2.3.4")
155
+
156
+ ["", " ", " \n \n ", "a.", ".b", "a..b", "a${b}c", "\"\".", ".\"\""].each do |invalid|
157
+ begin
158
+ it "should raise a ConfigBadPathError for '#{invalid}'" do
159
+ TestUtils.intercept(Hocon::ConfigError::ConfigBadPathError) {
160
+ parse_path(invalid)
161
+ }
162
+ end
163
+ rescue => e
164
+ $stderr.puts("failed on '#{invalid}'")
165
+ raise e
166
+ end
167
+ end
168
+ end
169
+
170
+ it "should allow the last instance to win when duplicate keys are found" do
171
+ obj = TestUtils.parse_config('{ "a" : 10, "a" : 11 } ')
172
+
173
+ expect(obj.root.size).to eq(1)
174
+ expect(obj.get_int("a")).to eq(11)
175
+ end
176
+
177
+ it "should merge maps when duplicate keys are found" do
178
+ obj = TestUtils.parse_config('{ "a" : { "x" : 1, "y" : 2 }, "a" : { "x" : 42, "z" : 100 } }')
179
+
180
+ expect(obj.root.size).to eq(1)
181
+ expect(obj.get_object("a").size).to eq(3)
182
+ expect(obj.get_int("a.x")).to eq(42)
183
+ expect(obj.get_int("a.y")).to eq(2)
184
+ expect(obj.get_int("a.z")).to eq(100)
185
+ end
186
+
187
+ it "should merge maps recursively when duplicate keys are found" do
188
+ obj = TestUtils.parse_config('{ "a" : { "b" : { "x" : 1, "y" : 2 } }, "a" : { "b" : { "x" : 42, "z" : 100 } } }')
189
+
190
+ expect(obj.root.size).to eq(1)
191
+ expect(obj.get_object("a").size).to eq(1)
192
+ expect(obj.get_object("a.b").size).to eq(3)
193
+ expect(obj.get_int("a.b.x")).to eq(42)
194
+ expect(obj.get_int("a.b.y")).to eq(2)
195
+ expect(obj.get_int("a.b.z")).to eq(100)
196
+ end
197
+
198
+ it "should merge maps recursively when three levels of duplicate keys are found" do
199
+ obj = TestUtils.parse_config('{ "a" : { "b" : { "c" : { "x" : 1, "y" : 2 } } }, "a" : { "b" : { "c" : { "x" : 42, "z" : 100 } } } }')
200
+
201
+ expect(obj.root.size).to eq(1)
202
+ expect(obj.get_object("a").size).to eq(1)
203
+ expect(obj.get_object("a.b").size).to eq(1)
204
+ expect(obj.get_object("a.b.c").size).to eq(3)
205
+ expect(obj.get_int("a.b.c.x")).to eq(42)
206
+ expect(obj.get_int("a.b.c.y")).to eq(2)
207
+ expect(obj.get_int("a.b.c.z")).to eq(100)
208
+ end
209
+
210
+ it "should 'reset' a key when a null is found" do
211
+ obj = TestUtils.parse_config('{ a : { b : 1 }, a : null, a : { c : 2 } }')
212
+
213
+ expect(obj.root.size).to eq(1)
214
+ expect(obj.get_object("a").size).to eq(1)
215
+ expect(obj.get_int("a.c")).to eq(2)
216
+ end
217
+
218
+ it "should 'reset' a map key when a scalar is found" do
219
+ obj = TestUtils.parse_config('{ a : { b : 1 }, a : 42, a : { c : 2 } }')
220
+
221
+ expect(obj.root.size).to eq(1)
222
+ expect(obj.get_object("a").size).to eq(1)
223
+ expect(obj.get_int("a.c")).to eq(2)
224
+ end
225
+ end
226
+
227
+ def drop_curlies(s)
228
+ # drop the outside curly braces
229
+ first = s.index('{')
230
+ last = s.rindex('}')
231
+ "#{s.slice(0..first)}#{s.slice(first+1..last)}#{s.slice(last + 1)}"
232
+ end
233
+
234
+ describe "Config Parser" do
235
+ context "implied_comma_handling" do
236
+ valids = ['
237
+ // one line
238
+ {
239
+ a : y, b : z, c : [ 1, 2, 3 ]
240
+ }', '
241
+ // multiline but with all commas
242
+ {
243
+ a : y,
244
+ b : z,
245
+ c : [
246
+ 1,
247
+ 2,
248
+ 3,
249
+ ],
250
+ }
251
+ ', '
252
+ // multiline with no commas
253
+ {
254
+ a : y
255
+ b : z
256
+ c : [
257
+ 1
258
+ 2
259
+ 3
260
+ ]
261
+ }
262
+ ']
263
+
264
+ changes = [
265
+ Proc.new { |s| s },
266
+ Proc.new { |s| s.gsub("\n", "\n\n") },
267
+ Proc.new { |s| s.gsub("\n", "\n\n\n") },
268
+ Proc.new { |s| s.gsub(",\n", "\n,\n")},
269
+ Proc.new { |s| s.gsub(",\n", "\n\n,\n\n") },
270
+ Proc.new { |s| s.gsub("\n", " \n ") },
271
+ Proc.new { |s| s.gsub(",\n", " \n \n , \n \n ") },
272
+ Proc.new { |s| drop_curlies(s) }
273
+ ]
274
+
275
+ tested = 0
276
+ changes.each do |change|
277
+ valids.each do |v|
278
+ tested += 1
279
+ s = change.call(v)
280
+ it "should handle commas and whitespaces properly for string '#{s}'" do
281
+ obj = TestUtils.parse_config(s)
282
+ expect(obj.root.size).to eq(3)
283
+ expect(obj.get_string("a")).to eq("y")
284
+ expect(obj.get_string("b")).to eq("z")
285
+ expect(obj.get_int_list("c")).to eq([1,2,3])
286
+ end
287
+ end
288
+ end
289
+
290
+ it "should have run one test per change per valid string" do
291
+ expect(tested).to eq(changes.length * valids.length)
292
+ end
293
+
294
+ context "should concatenate values when there is no newline or comma" do
295
+ it "with no newline in array" do
296
+ expect(TestUtils.parse_config(" { c : [ 1 2 3 ] } ").
297
+ get_string_list("c")).to eq (["1 2 3"])
298
+ end
299
+
300
+ it "with no newline in array with quoted strings" do
301
+ expect(TestUtils.parse_config(' { c : [ "4" "5" "6" ] } ').
302
+ get_string_list("c")).to eq (["4 5 6"])
303
+ end
304
+
305
+ it "with no newline in object" do
306
+ expect(TestUtils.parse_config(' { a : b c } ').
307
+ get_string("a")).to eq ("b c")
308
+ end
309
+
310
+ it "with no newline at end" do
311
+ expect(TestUtils.parse_config('a: b').
312
+ get_string("a")).to eq ("b")
313
+ end
314
+
315
+ it "errors when no newline between keys" do
316
+ TestUtils.intercept(Hocon::ConfigError) {
317
+ TestUtils.parse_config('{ a : y b : z }')
318
+ }
319
+ end
320
+
321
+ it "errors when no newline between quoted keys" do
322
+ TestUtils.intercept(Hocon::ConfigError) {
323
+ TestUtils.parse_config('{ "a" : "y" "b" : "z" }')
324
+ }
325
+ end
326
+ end
327
+ end
328
+
329
+ it "should support keys with slashes" do
330
+ obj = TestUtils.parse_config('/a/b/c=42, x/y/z : 32')
331
+ expect(obj.get_int("/a/b/c")).to eq(42)
332
+ expect(obj.get_int("x/y/z")).to eq(32)
333
+ end
334
+ end
335
+
336
+ def line_number_test(num, text)
337
+ it "should include the line number #{num} in the error message for invalid string '#{text}'" do
338
+ e = TestUtils.intercept(Hocon::ConfigError) {
339
+ TestUtils.parse_config(text)
340
+ }
341
+ if ! (e.message.include?("#{num}:"))
342
+ raise "error message did not contain line '#{num}' '#{text.gsub("\n", "\\n")}' (#{e})"
343
+ end
344
+ end
345
+ end
346
+
347
+ describe "Config Parser" do
348
+ context "line_numbers_in_errors" do
349
+ # error is at the last char
350
+ line_number_test(1, "}")
351
+ line_number_test(2, "\n}")
352
+ line_number_test(3, "\n\n}")
353
+
354
+ # error is before a final newline
355
+ line_number_test(1, "}\n")
356
+ line_number_test(2, "\n}\n")
357
+ line_number_test(3, "\n\n}\n")
358
+
359
+ # with unquoted string
360
+ line_number_test(1, "foo")
361
+ line_number_test(2, "\nfoo")
362
+ line_number_test(3, "\n\nfoo")
363
+
364
+ # with quoted string
365
+ line_number_test(1, "\"foo\"")
366
+ line_number_test(2, "\n\"foo\"")
367
+ line_number_test(3, "\n\n\"foo\"")
368
+
369
+ # newlines in triple-quoted string should not hose up the numbering
370
+ line_number_test(1, "a : \"\"\"foo\"\"\"}")
371
+ line_number_test(2, "a : \"\"\"foo\n\"\"\"}")
372
+ line_number_test(3, "a : \"\"\"foo\nbar\nbaz\"\"\"}")
373
+ # newlines after the triple quoted string
374
+ line_number_test(5, "a : \"\"\"foo\nbar\nbaz\"\"\"\n\n}")
375
+ # triple quoted string ends in a newline
376
+ line_number_test(6, "a : \"\"\"foo\nbar\nbaz\n\"\"\"\n\n}")
377
+ # end in the middle of triple-quoted string
378
+ line_number_test(5, "a : \"\"\"foo\n\n\nbar\n")
379
+ end
380
+
381
+ context "to_string_for_parseables" do
382
+ # just to be sure the to_string don't throw, to get test coverage
383
+ options = Hocon::ConfigParseOptions.defaults
384
+ it "should allow to_s on File Parseable" do
385
+ Hocon::Impl::Parseable.new_file("foo", options).to_s
386
+ end
387
+
388
+ it "should allow to_s on Resources Parseable" do
389
+ Hocon::Impl::Parseable.new_resources("foo", options).to_s
390
+ end
391
+
392
+ it "should allow to_s on Resources Parseable" do
393
+ Hocon::Impl::Parseable.new_string("foo", options).to_s
394
+ end
395
+
396
+ # NOTE: Skipping 'newURL', 'newProperties', 'newReader' tests here
397
+ # because we don't implement them
398
+ end
399
+ end
400
+
401
+ def assert_comments(comments, conf)
402
+ it "should have comments #{comments} at root" do
403
+ expect(conf.root.origin.comments).to eq(comments)
404
+ end
405
+ end
406
+
407
+ def assert_comments_at_path(comments, conf, path)
408
+ it "should have comments #{comments} at path #{path}" do
409
+ expect(conf.get_value(path).origin.comments).to eq(comments)
410
+ end
411
+ end
412
+
413
+ def assert_comments_at_path_index(comments, conf, path, index)
414
+ it "should have comments #{comments} at path #{path} and index #{index}" do
415
+ expect(conf.get_list(path).get(index).origin.comments).to eq(comments)
416
+ end
417
+ end
418
+
419
+ describe "Config Parser" do
420
+ context "track_comments_for_single_field" do
421
+ # no comments
422
+ conf0 = TestUtils.parse_config('
423
+ {
424
+ foo=10 }
425
+ ')
426
+ assert_comments_at_path([], conf0, "foo")
427
+
428
+ # comment in front of a field is used
429
+ conf1 = TestUtils.parse_config('
430
+ { # Before
431
+ foo=10 }
432
+ ')
433
+ assert_comments_at_path([" Before"], conf1, "foo")
434
+
435
+ # comment with a blank line after is dropped
436
+ conf2 = TestUtils.parse_config('
437
+ { # BlankAfter
438
+
439
+ foo=10 }
440
+ ')
441
+ assert_comments_at_path([], conf2, "foo")
442
+
443
+ # comment in front of a field is used with no root {}
444
+ conf3 = TestUtils.parse_config('
445
+ # BeforeNoBraces
446
+ foo=10
447
+ ')
448
+ assert_comments_at_path([" BeforeNoBraces"], conf3, "foo")
449
+
450
+ # comment with a blank line after is dropped with no root {}
451
+ conf4 = TestUtils.parse_config('
452
+ # BlankAfterNoBraces
453
+
454
+ foo=10
455
+ ')
456
+ assert_comments_at_path([], conf4, "foo")
457
+
458
+ # comment same line after field is used
459
+ conf5 = TestUtils.parse_config('
460
+ {
461
+ foo=10 # SameLine
462
+ }
463
+ ')
464
+ assert_comments_at_path([" SameLine"], conf5, "foo")
465
+
466
+ # comment before field separator is used
467
+ conf6 = TestUtils.parse_config('
468
+ {
469
+ foo # BeforeSep
470
+ =10
471
+ }
472
+ ')
473
+ assert_comments_at_path([" BeforeSep"], conf6, "foo")
474
+
475
+ # comment after field separator is used
476
+ conf7 = TestUtils.parse_config('
477
+ {
478
+ foo= # AfterSep
479
+ 10
480
+ }
481
+ ')
482
+ assert_comments_at_path([" AfterSep"], conf7, "foo")
483
+
484
+ # comment on next line is NOT used
485
+ conf8 = TestUtils.parse_config('
486
+ {
487
+ foo=10
488
+ # NextLine
489
+ }
490
+ ')
491
+ assert_comments_at_path([], conf8, "foo")
492
+
493
+ # comment before field separator on new line
494
+ conf9 = TestUtils.parse_config('
495
+ {
496
+ foo
497
+ # BeforeSepOwnLine
498
+ =10
499
+ }
500
+ ')
501
+ assert_comments_at_path([" BeforeSepOwnLine"], conf9, "foo")
502
+
503
+ # comment after field separator on its own line
504
+ conf10 = TestUtils.parse_config('
505
+ {
506
+ foo=
507
+ # AfterSepOwnLine
508
+ 10
509
+ }
510
+ ')
511
+ assert_comments_at_path([" AfterSepOwnLine"], conf10, "foo")
512
+
513
+ # comments comments everywhere
514
+ conf11 = TestUtils.parse_config('
515
+ {# Before
516
+ foo
517
+ # BeforeSep
518
+ = # AfterSepSameLine
519
+ # AfterSepNextLine
520
+ 10 # AfterValue
521
+ # AfterValueNewLine (should NOT be used)
522
+ }
523
+ ')
524
+ assert_comments_at_path([" Before", " BeforeSep", " AfterSepSameLine", " AfterSepNextLine", " AfterValue"], conf11, "foo")
525
+
526
+ # empty object
527
+ conf12 = TestUtils.parse_config('# BeforeEmpty
528
+ {} #AfterEmpty
529
+ # NewLine
530
+ ')
531
+ assert_comments([" BeforeEmpty", "AfterEmpty"], conf12)
532
+
533
+ # empty array
534
+ conf13 = TestUtils.parse_config('
535
+ foo=
536
+ # BeforeEmptyArray
537
+ [] #AfterEmptyArray
538
+ # NewLine
539
+ ')
540
+ assert_comments_at_path([" BeforeEmptyArray", "AfterEmptyArray"], conf13, "foo")
541
+
542
+ # array element
543
+ conf14 = TestUtils.parse_config('
544
+ foo=[
545
+ # BeforeElement
546
+ 10 # AfterElement
547
+ ]
548
+ ')
549
+ assert_comments_at_path_index(
550
+ [" BeforeElement", " AfterElement"], conf14, "foo", 0)
551
+
552
+ # field with comma after it
553
+ conf15 = TestUtils.parse_config('
554
+ foo=10, # AfterCommaField
555
+ ')
556
+ assert_comments_at_path([" AfterCommaField"], conf15, "foo")
557
+
558
+ # element with comma after it
559
+ conf16 = TestUtils.parse_config('
560
+ foo=[10, # AfterCommaElement
561
+ ]
562
+ ')
563
+ assert_comments_at_path_index([" AfterCommaElement"], conf16, "foo", 0)
564
+
565
+ # field with comma after it but comment isn't on the field's line, so not used
566
+ conf17 = TestUtils.parse_config('
567
+ foo=10
568
+ , # AfterCommaFieldNotUsed
569
+ ')
570
+ assert_comments_at_path([], conf17, "foo")
571
+
572
+ # element with comma after it but comment isn't on the field's line, so not used
573
+ conf18 = TestUtils.parse_config('
574
+ foo=[10
575
+ , # AfterCommaElementNotUsed
576
+ ]
577
+ ')
578
+ assert_comments_at_path_index([], conf18, "foo", 0)
579
+
580
+ # comment on new line, before comma, should not be used
581
+ conf19 = TestUtils.parse_config('
582
+ foo=10
583
+ # BeforeCommaFieldNotUsed
584
+ ,
585
+ ')
586
+ assert_comments_at_path([], conf19, "foo")
587
+
588
+ # comment on new line, before comma, should not be used
589
+ conf20 = TestUtils.parse_config('
590
+ foo=[10
591
+ # BeforeCommaElementNotUsed
592
+ ,
593
+ ]
594
+ ')
595
+ assert_comments_at_path_index([], conf20, "foo", 0)
596
+
597
+ # comment on same line before comma
598
+ conf21 = TestUtils.parse_config('
599
+ foo=10 # BeforeCommaFieldSameLine
600
+ ,
601
+ ')
602
+ assert_comments_at_path([" BeforeCommaFieldSameLine"], conf21, "foo")
603
+
604
+ # comment on same line before comma
605
+ conf22 = TestUtils.parse_config('
606
+ foo=[10 # BeforeCommaElementSameLine
607
+ ,
608
+ ]
609
+ ')
610
+ assert_comments_at_path_index([" BeforeCommaElementSameLine"], conf22, "foo", 0)
611
+ end
612
+
613
+ context "track_comments_for_multiple_fields" do
614
+ # nested objects
615
+ conf5 = TestUtils.parse_config('
616
+ # Outside
617
+ bar {
618
+ # Ignore me
619
+
620
+ # Middle
621
+ # two lines
622
+ baz {
623
+ # Inner
624
+ foo=10 # AfterInner
625
+ # This should be ignored
626
+ } # AfterMiddle
627
+ # ignored
628
+ } # AfterOutside
629
+ # ignored!
630
+ ')
631
+ assert_comments_at_path([" Inner", " AfterInner"], conf5, "bar.baz.foo")
632
+ assert_comments_at_path([" Middle", " two lines", " AfterMiddle"], conf5, "bar.baz")
633
+ assert_comments_at_path([" Outside", " AfterOutside"], conf5, "bar")
634
+
635
+ # multiple fields
636
+ conf6 = TestUtils.parse_config('{
637
+ # this is not with a field
638
+
639
+ # this is field A
640
+ a : 10,
641
+ # this is field B
642
+ b : 12 # goes with field B which has no comma
643
+ # this is field C
644
+ c : 14, # goes with field C after comma
645
+ # not used
646
+ # this is not used
647
+ # nor is this
648
+ # multi-line block
649
+
650
+ # this is with field D
651
+ # this is with field D also
652
+ d : 16
653
+
654
+ # this is after the fields
655
+ }')
656
+ assert_comments_at_path([" this is field A"], conf6, "a")
657
+ assert_comments_at_path([" this is field B", " goes with field B which has no comma"], conf6, "b")
658
+ assert_comments_at_path([" this is field C", " goes with field C after comma"], conf6, "c")
659
+ assert_comments_at_path([" this is with field D", " this is with field D also"], conf6, "d")
660
+
661
+ # array
662
+ conf7 = TestUtils.parse_config('
663
+ # before entire array
664
+ array = [
665
+ # goes with 0
666
+ 0,
667
+ # goes with 1
668
+ 1, # with 1 after comma
669
+ # goes with 2
670
+ 2 # no comma after 2
671
+ # not with anything
672
+ ] # after entire array
673
+ ')
674
+ assert_comments_at_path_index([" goes with 0"], conf7, "array", 0)
675
+ assert_comments_at_path_index([" goes with 1", " with 1 after comma"], conf7, "array", 1)
676
+ assert_comments_at_path_index([" goes with 2", " no comma after 2"], conf7, "array", 2)
677
+ assert_comments_at_path([" before entire array", " after entire array"], conf7, "array")
678
+
679
+ # properties-like syntax
680
+ conf8 = TestUtils.parse_config('
681
+ # ignored comment
682
+
683
+ # x.y comment
684
+ x.y = 10
685
+ # x.z comment
686
+ x.z = 11
687
+ # x.a comment
688
+ x.a = 12
689
+ # a.b comment
690
+ a.b = 14
691
+ a.c = 15
692
+ a.d = 16 # a.d comment
693
+ # ignored comment
694
+ ')
695
+
696
+ assert_comments_at_path([" x.y comment"], conf8, "x.y")
697
+ assert_comments_at_path([" x.z comment"], conf8, "x.z")
698
+ assert_comments_at_path([" x.a comment"], conf8, "x.a")
699
+ assert_comments_at_path([" a.b comment"], conf8, "a.b")
700
+ assert_comments_at_path([], conf8, "a.c")
701
+ assert_comments_at_path([" a.d comment"], conf8, "a.d")
702
+ # here we're concerned that comments apply only to leaf
703
+ # nodes, not to parent objects.
704
+ assert_comments_at_path([], conf8, "x")
705
+ assert_comments_at_path([], conf8, "a")
706
+ end
707
+
708
+
709
+ it "includeFile" do
710
+ conf = Hocon::ConfigFactory.parse_string("include file(" +
711
+ TestUtils.json_quoted_resource_file("test01") + ")")
712
+
713
+ # should have loaded conf, json... skipping properties
714
+ expect(conf.get_int("ints.fortyTwo")).to eq(42)
715
+ expect(conf.get_int("fromJson1")).to eq(1)
716
+ end
717
+
718
+ it "includeFileWithExtension" do
719
+ conf = Hocon::ConfigFactory.parse_string("include file(" +
720
+ TestUtils.json_quoted_resource_file("test01.conf") + ")")
721
+
722
+ expect(conf.get_int("ints.fortyTwo")).to eq(42)
723
+ expect(conf.has_path?("fromJson1")).to eq(false)
724
+ expect(conf.has_path?("fromProps.abc")).to eq(false)
725
+ end
726
+
727
+ it "includeFileWhitespaceInsideParens" do
728
+ conf = Hocon::ConfigFactory.parse_string("include file( \n " +
729
+ TestUtils.json_quoted_resource_file("test01") + " \n )")
730
+
731
+ # should have loaded conf, json... NOT properties
732
+ expect(conf.get_int("ints.fortyTwo")).to eq(42)
733
+ expect(conf.get_int("fromJson1")).to eq(1)
734
+ end
735
+
736
+ it "includeFileNoWhitespaceOutsideParens" do
737
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) {
738
+ Hocon::ConfigFactory.parse_string("include file (" +
739
+ TestUtils.json_quoted_resource_file("test01") + ")")
740
+ }
741
+ expect(e.message.include?("expecting include parameter")).to eq(true)
742
+ end
743
+
744
+ it "includeFileNotQuoted" do
745
+ # this test cannot work on Windows
746
+ f = TestUtils.resource_file("test01")
747
+ if (f.to_s.include?("\\"))
748
+ $stderr.puts("includeFileNotQuoted test skipped on Windows")
749
+ else
750
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) {
751
+ Hocon::ConfigFactory.parse_string("include file(" + f + ")")
752
+ }
753
+ expect(e.message.include?("expecting include parameter")).to eq(true)
754
+ end
755
+ end
756
+
757
+ it "includeFileNotQuotedAndSpecialChar" do
758
+ f = TestUtils.resource_file("test01")
759
+ if (f.to_s.include?("\\"))
760
+ $stderr.puts("includeFileNotQuoted test skipped on Windows")
761
+ else
762
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) {
763
+ Hocon::ConfigFactory.parse_string("include file(:" + f + ")")
764
+ }
765
+ expect(e.message.include?("expecting a quoted string")).to eq(true)
766
+ end
767
+
768
+ end
769
+
770
+ it "includeFileUnclosedParens" do
771
+ e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) {
772
+ Hocon::ConfigFactory.parse_string("include file(" + TestUtils.json_quoted_resource_file("test01") + " something")
773
+ }
774
+ expect(e.message.include?("expecting a close paren")).to eq(true)
775
+ end
776
+
777
+ # Skipping 'includeURLBasename' because we don't support URLs
778
+ # Skipping 'includeURLWithExtension' because we don't support URLs
779
+ # Skipping 'includeURLInvalid' because we don't support URLs
780
+ # Skipping 'includeResources' because we don't support classpath resources
781
+ # Skipping 'includeURLHeuristically' because we don't support URLs
782
+ # Skipping 'includeURLBasenameHeuristically' because we don't support URLs
783
+
784
+ it "acceptBOMStartingFile" do
785
+ skip("BOM not parsing properly yet; not fixing this now because it most likely only affects windows") do
786
+ # BOM at start of file should be ignored
787
+ conf = Hocon::ConfigFactory.parse_file(TestUtils.resource_file("bom.conf"))
788
+ expect(conf.get_string("foo")).to eq("bar")
789
+ end
790
+ end
791
+
792
+ it "acceptBOMStartOfStringConfig" do
793
+ skip("BOM not parsing properly yet; not fixing this now because it most likely only affects windows") do
794
+ # BOM at start of file is just whitespace, so ignored
795
+ conf = Hocon::ConfigFactory.parse_string("\uFEFFfoo=bar")
796
+ expect(conf.get_string("foo")).to eq("bar")
797
+ end
798
+ end
799
+
800
+ it "acceptBOMInStringValue" do
801
+ # BOM inside quotes should be preserved, just as other whitespace would be
802
+ conf = Hocon::ConfigFactory.parse_string("foo=\"\uFEFF\uFEFF\"")
803
+ expect(conf.get_string("foo")).to eq("\uFEFF\uFEFF")
804
+ end
805
+
806
+ it "acceptBOMWhitespace" do
807
+ skip("BOM not parsing properly yet; not fixing this now because it most likely only affects windows") do
808
+ # BOM here should be treated like other whitespace (ignored, since no quotes)
809
+ conf = Hocon::ConfigFactory.parse_string("foo= \uFEFFbar\uFEFF")
810
+ expect(conf.get_string("foo")).to eq("bar")
811
+ end
812
+ end
813
+
814
+ it "acceptMultiPeriodNumericPath" do
815
+ conf1 = Hocon::ConfigFactory.parse_string("0.1.2.3=foobar1")
816
+ expect(conf1.get_string("0.1.2.3")).to eq("foobar1")
817
+ conf2 = Hocon::ConfigFactory.parse_string("0.1.2.3.ABC=foobar2")
818
+ expect(conf2.get_string("0.1.2.3.ABC")).to eq("foobar2")
819
+ conf3 = Hocon::ConfigFactory.parse_string("ABC.0.1.2.3=foobar3")
820
+ expect(conf3.get_string("ABC.0.1.2.3")).to eq("foobar3")
821
+ end
822
+ end