hocon 0.9.5 → 1.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 (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