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,494 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'hocon'
5
+ require 'hocon/impl/config_document_parser'
6
+ require 'test_utils'
7
+
8
+ describe "ConfigDocumentParser" do
9
+ ConfigDocumentParser = Hocon::Impl::ConfigDocumentParser
10
+ ConfigParseOptions = Hocon::ConfigParseOptions
11
+ ConfigSyntax = Hocon::ConfigSyntax
12
+ shared_examples_for "parse test" do
13
+ it "should correctly render the parsed node" do
14
+ node = ConfigDocumentParser.parse(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults)
15
+ expect(node.render).to eq(orig_text)
16
+ end
17
+ end
18
+
19
+ shared_examples_for "parse JSON failures test" do
20
+ it "should thrown an exception when parsing invalid JSON" do
21
+ e = TestUtils.intercept(Hocon::ConfigError) {
22
+ ConfigDocumentParser.parse(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults.set_syntax(ConfigSyntax::JSON))
23
+ }
24
+ expect(e.message).to include(contains_message)
25
+ end
26
+ end
27
+
28
+ shared_examples_for "parse simple value test" do
29
+ it "should correctly parse and render the original text as CONF" do
30
+ expected_rendered_text = final_text.nil? ? orig_text : final_text
31
+ node = ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults)
32
+ expect(node.render).to eq(expected_rendered_text)
33
+ expect(node).to be_a(Hocon::Impl::ConfigNodeSimpleValue)
34
+ end
35
+
36
+ it "should correctly parse and render the original text as JSON" do
37
+ expected_rendered_text = final_text.nil? ? orig_text : final_text
38
+ nodeJSON = ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults.set_syntax(ConfigSyntax::JSON))
39
+ expect(nodeJSON.render).to eq(expected_rendered_text)
40
+ expect(nodeJSON).to be_a(Hocon::Impl::ConfigNodeSimpleValue)
41
+ end
42
+ end
43
+
44
+ shared_examples_for "parse complex value test" do
45
+ it "should correctly parse and render the original text as CONF" do
46
+ node = ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults)
47
+ expect(node.render).to eq(orig_text)
48
+ expect(node).to be_a(Hocon::Impl::ConfigNodeComplexValue)
49
+ end
50
+
51
+ it "should correctly parse and render the original text as JSON" do
52
+ nodeJSON = ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults.set_syntax(ConfigSyntax::JSON))
53
+ expect(nodeJSON.render).to eq(orig_text)
54
+ expect(nodeJSON).to be_a(Hocon::Impl::ConfigNodeComplexValue)
55
+ end
56
+ end
57
+
58
+ shared_examples_for "parse single value invalid JSON test" do
59
+ it "should correctly parse and render the original text as CONF" do
60
+ node = ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults)
61
+ expect(node.render).to eq(orig_text)
62
+ end
63
+
64
+ it "should throw an exception when parsing the original text as JSON" do
65
+ e = TestUtils.intercept(Hocon::ConfigError) {
66
+ ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults.set_syntax(ConfigSyntax::JSON))
67
+ }
68
+ expect(e.message).to include(contains_message)
69
+ end
70
+ end
71
+
72
+ shared_examples_for "parse leading trailing failure" do
73
+ it "should throw an exception when parsing an invalid single value" do
74
+ e = TestUtils.intercept(Hocon::ConfigError) {
75
+ ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults)
76
+ }
77
+ expect(e.message).to include("The value from setValue cannot have leading or trailing newlines, whitespace, or comments")
78
+ end
79
+ end
80
+
81
+ context "parse_success" do
82
+ context "simple map with no braces" do
83
+ let (:orig_text) { "foo:bar" }
84
+ include_examples "parse test"
85
+ end
86
+
87
+ context "simple map with no braces and whitespace" do
88
+ let (:orig_text) { " foo : bar " }
89
+ include_examples "parse test"
90
+ end
91
+
92
+ context "include with no braces" do
93
+ let (:orig_text) { 'include "foo.conf" ' }
94
+ include_examples "parse test"
95
+ end
96
+
97
+ context "simple map with no braces and newlines" do
98
+ let (:orig_text) { " \nfoo:bar\n " }
99
+ include_examples "parse test"
100
+ end
101
+
102
+ context "map with no braces and all simple types" do
103
+ let (:orig_text) { '
104
+ aUnquoted : bar
105
+ aString = "qux"
106
+ aNum:123
107
+ aDouble=123.456
108
+ aTrue=true
109
+ aFalse=false
110
+ aNull=null
111
+ aSub = ${a.b}
112
+ include "foo.conf"
113
+ ' }
114
+ include_examples "parse test"
115
+ end
116
+
117
+ context "empty map" do
118
+ let (:orig_text) { "{}" }
119
+ include_examples "parse test"
120
+ end
121
+
122
+ context "simple map with braces" do
123
+ let (:orig_text) { "{foo:bar}" }
124
+ include_examples "parse test"
125
+ end
126
+
127
+ context "simple map with braces and whitespace" do
128
+ let (:orig_text) { "{ foo : bar }" }
129
+ include_examples "parse test"
130
+ end
131
+
132
+ context "simple map with braces and trailing whitespace" do
133
+ let (:orig_text) { "{foo:bar} " }
134
+ include_examples "parse test"
135
+ end
136
+
137
+ context "simple map with braces and include" do
138
+ let (:orig_text) { '{include "foo.conf"}' }
139
+ include_examples "parse test"
140
+ end
141
+
142
+ context "simple map with braces and leading/trailing newlines" do
143
+ let (:orig_text) { "\n{foo:bar}\n" }
144
+ include_examples "parse test"
145
+ end
146
+
147
+ context "map with braces and all simple types" do
148
+ let (:orig_text) { '{
149
+ aUnquoted : bar
150
+ aString = "qux"
151
+ aNum:123
152
+ aDouble=123.456
153
+ aTrue=true
154
+ aFalse=false
155
+ aNull=null
156
+ aSub = ${a.b}
157
+ include "foo.conf"
158
+ }' }
159
+ include_examples "parse test"
160
+ end
161
+
162
+ context "maps can be nested within other maps" do
163
+ let(:orig_text) {
164
+ '
165
+ foo.bar.baz : {
166
+ qux : "abcdefg"
167
+ "abc".def."ghi" : 123
168
+ abc = { foo:bar }
169
+ }
170
+ qux = 123.456
171
+ '}
172
+ include_examples "parse test"
173
+ end
174
+
175
+ context "comments can be parsed in maps" do
176
+ let(:orig_text) {
177
+ '{
178
+ foo: bar
179
+ // This is a comment
180
+ baz:qux // This is another comment
181
+ }'}
182
+ include_examples "parse test"
183
+ end
184
+
185
+ context "empty array" do
186
+ let (:orig_text) { "[]" }
187
+ include_examples "parse test"
188
+ end
189
+
190
+ context "single-element array" do
191
+ let (:orig_text) { "[foo]" }
192
+ include_examples "parse test"
193
+ end
194
+
195
+ context "trailing comment" do
196
+ let (:orig_text) { "[foo,]" }
197
+ include_examples "parse test"
198
+ end
199
+
200
+ context "trailing comment and whitespace" do
201
+ let (:orig_text) { "[foo,] " }
202
+ include_examples "parse test"
203
+ end
204
+
205
+ context "leading and trailing whitespace" do
206
+ let (:orig_text) { " \n[]\n " }
207
+ include_examples "parse test"
208
+ end
209
+
210
+ context "array with all simple types" do
211
+ let (:orig_text) { '[foo, bar,"qux", 123,123.456, true,false, null, ${a.b}]' }
212
+ include_examples "parse test"
213
+ end
214
+
215
+ context "array with all simple types and weird whitespace" do
216
+ let (:orig_text) { '[foo, bar,"qux" , 123 , 123.456, true,false, null, ${a.b} ]' }
217
+ include_examples "parse test"
218
+ end
219
+
220
+ context "basic concatenation inside an array" do
221
+ let (:orig_text) { "[foo bar baz qux]" }
222
+ include_examples "parse test"
223
+ end
224
+
225
+ context "basic concatenation inside a map" do
226
+ let (:orig_text) { "{foo: foo bar baz qux}" }
227
+ include_examples "parse test"
228
+ end
229
+
230
+ context "complex concatenation in an array with multiple elements" do
231
+ let (:orig_text) { "[abc 123 123.456 null true false [1, 2, 3] {a:b}, 2]" }
232
+ include_examples "parse test"
233
+ end
234
+
235
+ context "complex node with all types" do
236
+ let (:orig_text) {
237
+ '{
238
+ foo: bar baz qux ernie
239
+ // The above was a concatenation
240
+
241
+ baz = [ abc 123, {a:12
242
+ b: {
243
+ c: 13
244
+ d: {
245
+ a: 22
246
+ b: "abcdefg" # this is a comment
247
+ c: [1, 2, 3]
248
+ }
249
+ }
250
+ }, # this was an object in an array
251
+ //The above value is a map containing a map containing a map, all in an array
252
+ 22,
253
+ // The below value is an array contained in another array
254
+ [1,2,3]]
255
+ // This is a map with some nested maps and arrays within it, as well as some concatenations
256
+ qux {
257
+ baz: abc 123
258
+ bar: {
259
+ baz: abcdefg
260
+ bar: {
261
+ a: null
262
+ b: true
263
+ c: [true false 123, null, [1, 2, 3]]
264
+ }
265
+ }
266
+ }
267
+ // Did I cover everything?
268
+ }'
269
+ }
270
+ include_examples "parse test"
271
+ end
272
+
273
+ context "can correctly parse a JSON string" do
274
+ it "should correctly parse and render a JSON string" do
275
+ orig_text =
276
+ '{
277
+ "foo": "bar",
278
+ "baz": 123,
279
+ "qux": true,
280
+ "array": [
281
+ {"a": true,
282
+ "c": false},
283
+ 12
284
+ ]
285
+ }
286
+ '
287
+ node = ConfigDocumentParser.parse(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults.set_syntax(ConfigSyntax::JSON))
288
+ expect(node.render).to eq(orig_text)
289
+ end
290
+ end
291
+ end
292
+
293
+ context "parse JSON failures" do
294
+ context "JSON does not support concatenations" do
295
+ let (:orig_text) { '{ "foo": 123 456 789 } ' }
296
+ let (:contains_message) { "Expecting close brace } or a comma" }
297
+ include_examples "parse JSON failures test"
298
+ end
299
+
300
+ context "JSON must begin with { or [" do
301
+ let (:orig_text) { '"a": 123, "b": 456' }
302
+ let (:contains_message) { "Document must have an object or array at root" }
303
+ include_examples "parse JSON failures test"
304
+ end
305
+
306
+ context "JSON does not support unquoted text" do
307
+ let (:orig_text) { '{"foo": unquotedtext}' }
308
+ let (:contains_message) { "Token not allowed in valid JSON" }
309
+ include_examples "parse JSON failures test"
310
+ end
311
+
312
+ context "JSON does not support substitutions" do
313
+ let (:orig_text) { '{"foo": ${"a.b"}}' }
314
+ let (:contains_message) { "Substitutions (${} syntax) not allowed in JSON" }
315
+ include_examples "parse JSON failures test"
316
+ end
317
+
318
+ context "JSON does not support multi-element paths" do
319
+ let (:orig_text) { '{"foo"."bar": 123}' }
320
+ let (:contains_message) { "Token not allowed in valid JSON" }
321
+ include_examples "parse JSON failures test"
322
+ end
323
+
324
+ context "JSON does not support =" do
325
+ let (:orig_text) { '{"foo"=123}' }
326
+ let (:contains_message) { "Key '\"foo\"' may not be followed by token: '='" }
327
+ include_examples "parse JSON failures test"
328
+ end
329
+
330
+ context "JSON does not support +=" do
331
+ let (:orig_text) { '{"foo" += "bar"}' }
332
+ let (:contains_message) { "Key '\"foo\"' may not be followed by token: '+='" }
333
+ include_examples "parse JSON failures test"
334
+ end
335
+
336
+ context "JSON does not support duplicate keys" do
337
+ let (:orig_text) { '{"foo" : 123, "foo": 456}' }
338
+ let (:contains_message) { "JSON does not allow duplicate fields" }
339
+ include_examples "parse JSON failures test"
340
+ end
341
+
342
+ context "JSON does not support trailing commas" do
343
+ let (:orig_text) { '{"foo" : 123,}' }
344
+ let (:contains_message) { "expecting a field name after a comma, got a close brace } instead" }
345
+ include_examples "parse JSON failures test"
346
+ end
347
+
348
+ context "JSON does not support empty documents" do
349
+ let (:orig_text) { '' }
350
+ let (:contains_message) { "Empty document" }
351
+ include_examples "parse JSON failures test"
352
+ end
353
+ end
354
+
355
+ context "parse single values" do
356
+ let (:final_text) { nil }
357
+
358
+ context "parse a single integer" do
359
+ let (:orig_text) { "123" }
360
+ include_examples "parse simple value test"
361
+ end
362
+
363
+ context "parse a single double" do
364
+ let (:orig_text) { "123.456" }
365
+ include_examples "parse simple value test"
366
+ end
367
+
368
+ context "parse a single string" do
369
+ let (:orig_text) { '"a string"' }
370
+ include_examples "parse simple value test"
371
+ end
372
+
373
+ context "parse true" do
374
+ let (:orig_text) { "true" }
375
+ include_examples "parse simple value test"
376
+ end
377
+
378
+ context "parse false" do
379
+ let (:orig_text) { "false" }
380
+ include_examples "parse simple value test"
381
+ end
382
+
383
+ context "parse null" do
384
+ let (:orig_text) { "null" }
385
+ include_examples "parse simple value test"
386
+ end
387
+
388
+ context "parse a map" do
389
+ let (:orig_text) { '{"a": "b"}' }
390
+ include_examples "parse complex value test"
391
+ end
392
+
393
+ context "parse an array" do
394
+ let (:orig_text) { '{"a": "b"}' }
395
+ include_examples "parse complex value test"
396
+ end
397
+
398
+ it "should parse concatenations when using CONF syntax" do
399
+ orig_text = "123 456 \"abc\""
400
+ node = ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults)
401
+ expect(node.render).to eq(orig_text)
402
+ end
403
+
404
+ it "should parse keys with no separators and object values with CONF parsing" do
405
+ orig_text = '{"foo" { "bar" : 12 } }'
406
+ node = ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults)
407
+ expect(node.render).to eq(orig_text)
408
+ end
409
+ end
410
+
411
+ context "parse single values failures" do
412
+ context "throws on leading whitespace" do
413
+ let (:orig_text) { " 123" }
414
+ include_examples "parse leading trailing failure"
415
+ end
416
+
417
+ context "throws on trailing whitespace" do
418
+ let (:orig_text) { "123 " }
419
+ include_examples "parse leading trailing failure"
420
+ end
421
+
422
+ context "throws on leading and trailing whitespace" do
423
+ let (:orig_text) { " 123 " }
424
+ include_examples "parse leading trailing failure"
425
+ end
426
+
427
+ context "throws on leading newline" do
428
+ let (:orig_text) { "\n123" }
429
+ include_examples "parse leading trailing failure"
430
+ end
431
+
432
+ context "throws on trailing newline" do
433
+ let (:orig_text) { "123\n" }
434
+ include_examples "parse leading trailing failure"
435
+ end
436
+
437
+ context "throws on leading and trailing newline" do
438
+ let (:orig_text) { "\n123\n" }
439
+ include_examples "parse leading trailing failure"
440
+ end
441
+
442
+ context "throws on leading and trailing comments" do
443
+ let (:orig_text) { "#thisisacomment\n123#comment" }
444
+ include_examples "parse leading trailing failure"
445
+ end
446
+
447
+ context "throws on whitespace after a concatenation" do
448
+ let (:orig_text) { "123 456 789 " }
449
+ include_examples "parse leading trailing failure"
450
+ end
451
+
452
+ context "throws on unquoted text in JSON" do
453
+ let (:orig_text) { "unquotedtext" }
454
+ let (:contains_message) { "Token not allowed in valid JSON" }
455
+ include_examples("parse single value invalid JSON test")
456
+ end
457
+
458
+ context "throws on substitutions in JSON" do
459
+ let (:orig_text) { "${a.b}" }
460
+ let (:contains_message) { "Substitutions (${} syntax) not allowed in JSON" }
461
+ include_examples("parse single value invalid JSON test")
462
+ end
463
+
464
+ it "should throw an error when parsing concatenations in JSON" do
465
+ orig_text = "123 456 \"abc\""
466
+ e = TestUtils.intercept(Hocon::ConfigError) {
467
+ ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults.set_syntax(ConfigSyntax::JSON))
468
+ }
469
+ expect(e.message).to include("Parsing JSON and the value set in setValue was either a concatenation or had trailing whitespace, newlines, or comments")
470
+ end
471
+
472
+ it "should throw an error when parsing keys with no separators in JSON" do
473
+ orig_text = '{"foo" { "bar" : 12 } }'
474
+ e = TestUtils.intercept(Hocon::ConfigError) {
475
+ ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults.set_syntax(ConfigSyntax::JSON))
476
+ }
477
+ expect(e.message).to include("Key '\"foo\"' may not be followed by token: '{'")
478
+ end
479
+ end
480
+
481
+ context "parse empty document" do
482
+ it "should parse an empty document with CONF syntax" do
483
+ node = ConfigDocumentParser.parse(TestUtils.tokenize_from_s(""), TestUtils.fake_origin, ConfigParseOptions.defaults)
484
+ expect(node.value).to be_a(Hocon::Impl::ConfigNodeObject)
485
+ expect(node.value.children.empty?).to be_truthy
486
+ end
487
+
488
+ it "should parse a document with only comments and whitespace with CONF syntax" do
489
+ node = ConfigDocumentParser.parse(TestUtils.tokenize_from_s("#comment\n#comment\n\n"), TestUtils.fake_origin, ConfigParseOptions.defaults)
490
+ expect(node.value).to be_a(Hocon::Impl::ConfigNodeObject)
491
+ end
492
+
493
+ end
494
+ end