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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -2
- data/README.md +22 -10
- data/lib/hocon.rb +9 -3
- data/lib/hocon/config_factory.rb +4 -0
- data/lib/hocon/config_value_factory.rb +13 -2
- data/lib/hocon/impl/config_reference.rb +5 -2
- data/lib/hocon/impl/simple_config_origin.rb +1 -1
- data/spec/fixtures/parse_render/example1/input.conf +21 -0
- data/spec/fixtures/parse_render/example1/output.conf +26 -0
- data/spec/fixtures/parse_render/example1/output_nocomments.conf +17 -0
- data/spec/fixtures/parse_render/example2/input.conf +10 -0
- data/spec/fixtures/parse_render/example2/output.conf +17 -0
- data/spec/fixtures/parse_render/example2/output_nocomments.conf +17 -0
- data/spec/fixtures/parse_render/example3/input.conf +2 -0
- data/spec/fixtures/parse_render/example3/output.conf +2 -0
- data/spec/fixtures/parse_render/example4/input.json +6 -0
- data/spec/fixtures/parse_render/example4/output.conf +6 -0
- data/spec/fixtures/test_utils/resources/bom.conf +2 -0
- data/spec/fixtures/test_utils/resources/cycle.conf +1 -0
- data/spec/fixtures/test_utils/resources/file-include.conf +5 -0
- data/spec/fixtures/test_utils/resources/include-from-list.conf +4 -0
- data/spec/fixtures/test_utils/resources/subdir/bar.conf +1 -0
- data/spec/fixtures/test_utils/resources/subdir/baz.conf +1 -0
- data/spec/fixtures/test_utils/resources/subdir/foo.conf +5 -0
- data/spec/fixtures/test_utils/resources/test01.conf +80 -0
- data/spec/fixtures/test_utils/resources/test01.json +4 -0
- data/spec/fixtures/test_utils/resources/test03.conf +36 -0
- data/spec/spec_helper.rb +43 -0
- data/spec/test_utils.rb +757 -0
- data/spec/unit/typesafe/config/concatenation_spec.rb +417 -0
- data/spec/unit/typesafe/config/conf_parser_spec.rb +822 -0
- data/spec/unit/typesafe/config/config_document_parser_spec.rb +494 -0
- data/spec/unit/typesafe/config/config_document_spec.rb +576 -0
- data/spec/unit/typesafe/config/config_factory_spec.rb +120 -0
- data/spec/unit/typesafe/config/config_node_spec.rb +552 -0
- data/spec/unit/typesafe/config/config_value_factory_spec.rb +85 -0
- data/spec/unit/typesafe/config/config_value_spec.rb +935 -0
- data/spec/unit/typesafe/config/hocon_spec.rb +54 -0
- data/spec/unit/typesafe/config/path_spec.rb +261 -0
- data/spec/unit/typesafe/config/public_api_spec.rb +520 -0
- data/spec/unit/typesafe/config/simple_config_spec.rb +112 -0
- data/spec/unit/typesafe/config/token_spec.rb +188 -0
- data/spec/unit/typesafe/config/tokenizer_spec.rb +801 -0
- 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
|