hocon 1.1.3 → 1.3.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 +5 -5
- data/CHANGELOG.md +35 -1
- data/HISTORY.md +2140 -0
- data/README.md +125 -0
- data/bin/hocon +5 -0
- data/lib/hocon/cli.rb +225 -0
- data/lib/hocon/config_render_options.rb +3 -2
- data/lib/hocon/impl/abstract_config_value.rb +7 -10
- data/lib/hocon/impl/config_delayed_merge.rb +1 -1
- data/lib/hocon/impl/config_impl.rb +18 -0
- data/lib/hocon/impl/config_node_object.rb +1 -2
- data/lib/hocon/impl/config_node_root.rb +1 -1
- data/lib/hocon/impl/parseable.rb +6 -5
- data/lib/hocon/impl/simple_config_document.rb +1 -1
- data/lib/hocon/impl/simple_config_origin.rb +8 -2
- data/lib/hocon/version.rb +5 -0
- metadata +9 -50
- data/spec/fixtures/hocon/by_extension/cat.conf +0 -4
- data/spec/fixtures/hocon/by_extension/cat.test +0 -4
- data/spec/fixtures/hocon/by_extension/cat.test-json +0 -3
- data/spec/fixtures/hocon/with_substitution/subst.conf +0 -2
- data/spec/fixtures/parse_render/example1/input.conf +0 -21
- data/spec/fixtures/parse_render/example1/output.conf +0 -26
- data/spec/fixtures/parse_render/example1/output_nocomments.conf +0 -17
- data/spec/fixtures/parse_render/example2/input.conf +0 -10
- data/spec/fixtures/parse_render/example2/output.conf +0 -17
- data/spec/fixtures/parse_render/example2/output_nocomments.conf +0 -17
- data/spec/fixtures/parse_render/example3/input.conf +0 -2
- data/spec/fixtures/parse_render/example3/output.conf +0 -2
- data/spec/fixtures/parse_render/example4/input.json +0 -6
- data/spec/fixtures/parse_render/example4/output.conf +0 -6
- data/spec/fixtures/test_utils/resources/bom.conf +0 -2
- data/spec/fixtures/test_utils/resources/cycle.conf +0 -1
- data/spec/fixtures/test_utils/resources/file-include.conf +0 -5
- data/spec/fixtures/test_utils/resources/include-from-list.conf +0 -4
- data/spec/fixtures/test_utils/resources/subdir/bar.conf +0 -1
- data/spec/fixtures/test_utils/resources/subdir/baz.conf +0 -1
- data/spec/fixtures/test_utils/resources/subdir/foo.conf +0 -5
- data/spec/fixtures/test_utils/resources/test01.conf +0 -80
- data/spec/fixtures/test_utils/resources/test01.json +0 -4
- data/spec/fixtures/test_utils/resources/test03.conf +0 -36
- data/spec/fixtures/test_utils/resources/utf16.conf +0 -0
- data/spec/fixtures/test_utils/resources/utf8.conf +0 -2
- data/spec/fixtures/test_utils/resources//341/232/240/341/233/207/341/232/273.conf +0 -2
- data/spec/spec_helper.rb +0 -43
- data/spec/test_utils.rb +0 -757
- data/spec/unit/hocon/README.md +0 -7
- data/spec/unit/hocon/hocon_spec.rb +0 -114
- data/spec/unit/typesafe/config/README.md +0 -4
- data/spec/unit/typesafe/config/concatenation_spec.rb +0 -417
- data/spec/unit/typesafe/config/conf_parser_spec.rb +0 -832
- data/spec/unit/typesafe/config/config_document_parser_spec.rb +0 -494
- data/spec/unit/typesafe/config/config_document_spec.rb +0 -576
- data/spec/unit/typesafe/config/config_factory_spec.rb +0 -120
- data/spec/unit/typesafe/config/config_node_spec.rb +0 -552
- data/spec/unit/typesafe/config/config_value_factory_spec.rb +0 -85
- data/spec/unit/typesafe/config/config_value_spec.rb +0 -935
- data/spec/unit/typesafe/config/path_spec.rb +0 -261
- data/spec/unit/typesafe/config/public_api_spec.rb +0 -520
- data/spec/unit/typesafe/config/simple_config_spec.rb +0 -112
- data/spec/unit/typesafe/config/token_spec.rb +0 -188
- data/spec/unit/typesafe/config/tokenizer_spec.rb +0 -801
@@ -1,832 +0,0 @@
|
|
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 "shouldacceptUTF8FileNames" do
|
785
|
-
skip('UTF-8 filenames not currently supported') do
|
786
|
-
expect { Hocon::ConfigFactory.parse_file(TestUtils.resource_file("ᚠᛇᚻ.conf")) }.to raise_error
|
787
|
-
end
|
788
|
-
end
|
789
|
-
|
790
|
-
it "acceptsUTF8FileContents" do
|
791
|
-
# utf8.conf is UTF-8 with no BOM
|
792
|
-
rune_utf8 = "\u16EB\u16D2\u16E6\u16A6\u16EB\u16A0\u16B1\u16A9\u16A0\u16A2"
|
793
|
-
conf = Hocon::ConfigFactory.parse_file(TestUtils.resource_file("utf8.conf"))
|
794
|
-
expect(conf.get_string("\u16A0\u16C7\u16BB")).to eq(rune_utf8)
|
795
|
-
end
|
796
|
-
|
797
|
-
it "shouldacceptUTF16FileContents" do
|
798
|
-
skip('supporting UTF-16 requires appropriate BOM detection during parsing') do
|
799
|
-
# utf16.conf is UTF-16LE with a BOM
|
800
|
-
expect { Hocon::ConfigFactory.parse_file(TestUtils.resource_file("utf16.conf")) }.to raise_error
|
801
|
-
end
|
802
|
-
end
|
803
|
-
|
804
|
-
it "acceptBOMStartingFile" do
|
805
|
-
# BOM at start of file should be ignored
|
806
|
-
conf = Hocon::ConfigFactory.parse_file(TestUtils.resource_file("bom.conf"))
|
807
|
-
expect(conf.get_string("foo")).to eq("bar")
|
808
|
-
end
|
809
|
-
|
810
|
-
it "acceptBOMInStringValue" do
|
811
|
-
# BOM inside quotes should be preserved, just as other whitespace would be
|
812
|
-
conf = Hocon::ConfigFactory.parse_string("foo=\"\uFEFF\uFEFF\"")
|
813
|
-
expect(conf.get_string("foo")).to eq("\uFEFF\uFEFF")
|
814
|
-
end
|
815
|
-
|
816
|
-
it "acceptBOMWhitespace" do
|
817
|
-
skip("BOM not parsing properly yet; not fixing this now because it most likely only affects windows") do
|
818
|
-
# BOM here should be treated like other whitespace (ignored, since no quotes)
|
819
|
-
conf = Hocon::ConfigFactory.parse_string("foo= \uFEFFbar\uFEFF")
|
820
|
-
expect(conf.get_string("foo")).to eq("bar")
|
821
|
-
end
|
822
|
-
end
|
823
|
-
|
824
|
-
it "acceptMultiPeriodNumericPath" do
|
825
|
-
conf1 = Hocon::ConfigFactory.parse_string("0.1.2.3=foobar1")
|
826
|
-
expect(conf1.get_string("0.1.2.3")).to eq("foobar1")
|
827
|
-
conf2 = Hocon::ConfigFactory.parse_string("0.1.2.3.ABC=foobar2")
|
828
|
-
expect(conf2.get_string("0.1.2.3.ABC")).to eq("foobar2")
|
829
|
-
conf3 = Hocon::ConfigFactory.parse_string("ABC.0.1.2.3=foobar3")
|
830
|
-
expect(conf3.get_string("ABC.0.1.2.3")).to eq("foobar3")
|
831
|
-
end
|
832
|
-
end
|