ucl 0.1.4 → 0.2.0
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/LICENSE-DEPENDENCIES.md +161 -0
- data/README.md +104 -15
- data/ext/extconf.rb +42 -40
- data/ext/libucl/COPYING +23 -0
- data/ext/libucl/include/ucl.h +1689 -0
- data/ext/libucl/klib/khash.h +627 -0
- data/ext/libucl/klib/kvec.h +161 -0
- data/ext/libucl/src/mum.h +440 -0
- data/ext/libucl/src/tree.h +209 -0
- data/ext/libucl/src/ucl_chartable.h +267 -0
- data/ext/libucl/src/ucl_emitter.c +721 -0
- data/ext/libucl/src/ucl_emitter_streamline.c +178 -0
- data/ext/libucl/src/ucl_emitter_utils.c +521 -0
- data/ext/libucl/src/ucl_hash.c +642 -0
- data/ext/libucl/src/ucl_hash.h +115 -0
- data/ext/libucl/src/ucl_internal.h +668 -0
- data/ext/libucl/src/ucl_msgpack.c +1574 -0
- data/ext/libucl/src/ucl_parser.c +3247 -0
- data/ext/libucl/src/ucl_schema.c +1102 -0
- data/ext/libucl/src/ucl_sexp.c +229 -0
- data/ext/libucl/src/ucl_util.c +4004 -0
- data/ext/libucl/uthash/uthash.h +720 -0
- data/ext/libucl/uthash/utlist.h +1076 -0
- data/ext/libucl/uthash/utstring.h +412 -0
- data/ext/ucl.c +308 -89
- data/test/test_ucl.rb +129 -0
- data/ucl.gemspec +20 -7
- metadata +25 -17
data/test/test_ucl.rb
CHANGED
|
@@ -129,6 +129,18 @@ class TestUCL < Minitest::Test
|
|
|
129
129
|
UCL.parse("# header\nx = 1 # inline\ny = 2"))
|
|
130
130
|
end
|
|
131
131
|
|
|
132
|
+
# Characterisation, not endorsement: in libucl a trailing comment suppresses
|
|
133
|
+
# suffix parsing, so a value carrying a unit or a multiplier comes back as a
|
|
134
|
+
# string. Plain numbers are unaffected (see above). The README documents this
|
|
135
|
+
# as a trap; if a future libucl fixes it, this test is the canary that says
|
|
136
|
+
# the documentation needs updating.
|
|
137
|
+
def test_trailing_comment_suppresses_suffix_parsing
|
|
138
|
+
assert_equal({ 't' => 30.0 }, UCL.parse('t = 30s'))
|
|
139
|
+
assert_equal({ 't' => '30s' }, UCL.parse('t = 30s # note'))
|
|
140
|
+
assert_equal({ 'n' => 10_485_760 }, UCL.parse('n = 10mb'))
|
|
141
|
+
assert_equal({ 'n' => '10mb' }, UCL.parse('n = 10mb # note'))
|
|
142
|
+
end
|
|
143
|
+
|
|
132
144
|
# ---- duplicate keys become an explicit array ----------------------------
|
|
133
145
|
|
|
134
146
|
def test_duplicate_keys_make_array
|
|
@@ -303,6 +315,109 @@ class TestUCL < Minitest::Test
|
|
|
303
315
|
assert_raises(TypeError) { UCL.parse(42) }
|
|
304
316
|
end
|
|
305
317
|
|
|
318
|
+
# ---- nesting limit ------------------------------------------------------
|
|
319
|
+
|
|
320
|
+
# The conversion to Ruby objects and libucl's own tree handling both recurse
|
|
321
|
+
# once per nesting level, so the depth is capped instead of being left to
|
|
322
|
+
# exhaust the C stack. The threads below are what make these meaningful: a
|
|
323
|
+
# thread gets a 1 MiB machine stack, which is the small stack the limit has
|
|
324
|
+
# to protect.
|
|
325
|
+
|
|
326
|
+
def test_deeply_nested_array_raises_ucl_error
|
|
327
|
+
err = Thread.new { assert_raises(UCL::Error) { UCL.parse(deep_array) } }.value
|
|
328
|
+
assert_match(/nesting/, err.message)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def test_deeply_nested_object_raises_ucl_error
|
|
332
|
+
src = ('k {' * 20_000) + ('}' * 20_000)
|
|
333
|
+
Thread.new { assert_raises(UCL::Error) { UCL.parse(src) } }.join
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def test_deeply_nested_file_raises_ucl_error
|
|
337
|
+
with_conf(deep_array) do |path|
|
|
338
|
+
Thread.new { assert_raises(UCL::Error) { UCL.load_file(path) } }.join
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def test_nesting_just_below_the_limit_is_accepted
|
|
343
|
+
src = 'a = ' + ('[' * 999) + (']' * 999)
|
|
344
|
+
assert_kind_of Array, UCL.parse(src)['a']
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def test_parser_still_works_after_a_rejected_document
|
|
348
|
+
Thread.new { assert_raises(UCL::Error) { UCL.parse(deep_array) } }.join
|
|
349
|
+
assert_equal({ 'x' => 1 }, UCL.parse('x = 1'))
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def test_rejected_document_does_not_leak_the_parser
|
|
353
|
+
# Regression: the conversion used to escape with the parser and the whole
|
|
354
|
+
# parsed tree still allocated, losing ~2.7 MiB per rejected 40 KiB input.
|
|
355
|
+
before = process_rss_kib
|
|
356
|
+
skip 'cannot read RSS on this platform' if before.nil?
|
|
357
|
+
100.times do
|
|
358
|
+
UCL.parse(deep_array)
|
|
359
|
+
rescue UCL::Error
|
|
360
|
+
# expected; what is being measured is what the failure path leaves behind
|
|
361
|
+
end
|
|
362
|
+
GC.start
|
|
363
|
+
growth = process_rss_kib - before
|
|
364
|
+
# Pre-fix this grew by ~280 MiB; the margin is deliberately generous so
|
|
365
|
+
# that ordinary heap growth cannot make the test flaky.
|
|
366
|
+
assert_operator growth, :<, 20 * 1024,
|
|
367
|
+
"leaked #{growth} KiB over 100 rejected documents"
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# ---- macro safety -------------------------------------------------------
|
|
371
|
+
|
|
372
|
+
def test_parse_processes_the_include_macro
|
|
373
|
+
with_conf("secret = value\n") do |path|
|
|
374
|
+
assert_equal({ 'secret' => 'value' }, UCL.parse(%(.include "#{path}")))
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def test_safe_parse_does_not_read_the_included_file
|
|
379
|
+
with_conf("secret = value\n") do |path|
|
|
380
|
+
# libucl versions differ in how they treat a macro line when macros are
|
|
381
|
+
# disabled (ignored, or rejected as an invalid key); either is fine, as
|
|
382
|
+
# long as the file is not read.
|
|
383
|
+
begin
|
|
384
|
+
refute_includes UCL.safe_parse(%(.include "#{path}")), 'secret'
|
|
385
|
+
rescue UCL::Error => e
|
|
386
|
+
refute_empty e.message
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def test_safe_parse_parses_ordinary_input
|
|
392
|
+
assert_equal({ 'a' => 1, 'b' => [2, 3] }, UCL.safe_parse("a = 1\nb = [2,3]"))
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def test_safe_parse_honours_explicit_flags
|
|
396
|
+
assert_equal({ a: 1 },
|
|
397
|
+
UCL.safe_parse('A = 1', UCL::KEY_SYMBOL | UCL::KEY_LOWERCASE))
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def test_safe_parse_uses_the_default_flags
|
|
401
|
+
UCL.flags = UCL::KEY_SYMBOL
|
|
402
|
+
assert_equal({ a: 1 }, UCL.safe_parse('a = 1'))
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# ---- subclasses ---------------------------------------------------------
|
|
406
|
+
|
|
407
|
+
def test_subclass_uses_its_own_default_flags
|
|
408
|
+
klass = Class.new(UCL)
|
|
409
|
+
klass.flags = UCL::KEY_SYMBOL
|
|
410
|
+
assert_equal({ 'name' => 'value' }, UCL.parse('name = value'))
|
|
411
|
+
assert_equal({ name: 'value' }, klass.parse('name = value'))
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def test_subclass_without_flags_falls_back_to_ucl
|
|
415
|
+
UCL.flags = UCL::KEY_SYMBOL
|
|
416
|
+
klass = Class.new(UCL)
|
|
417
|
+
assert_equal UCL::KEY_SYMBOL, klass.flags
|
|
418
|
+
assert_equal({ name: 'value' }, klass.parse('name = value'))
|
|
419
|
+
end
|
|
420
|
+
|
|
306
421
|
# ---- constants ----------------------------------------------------------
|
|
307
422
|
|
|
308
423
|
def test_constants_defined
|
|
@@ -313,6 +428,20 @@ class TestUCL < Minitest::Test
|
|
|
313
428
|
|
|
314
429
|
private
|
|
315
430
|
|
|
431
|
+
# 20 000 levels: deeper than the 1000-level limit, and deep enough that the
|
|
432
|
+
# C stack would be gone without it.
|
|
433
|
+
def deep_array
|
|
434
|
+
'a = ' + ('[' * 20_000) + (']' * 20_000)
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
# Resident set size in KiB, or nil where it cannot be read.
|
|
438
|
+
def process_rss_kib
|
|
439
|
+
out = `ps -o rss= -p #{$$} 2>/dev/null`
|
|
440
|
+
$?.success? && !out.strip.empty? ? out.to_i : nil
|
|
441
|
+
rescue StandardError
|
|
442
|
+
nil
|
|
443
|
+
end
|
|
444
|
+
|
|
316
445
|
def with_conf(content)
|
|
317
446
|
Tempfile.create(['ucl', '.conf']) do |f|
|
|
318
447
|
f.write(content)
|
data/ucl.gemspec
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Gem::Specification.new do |s|
|
|
2
2
|
s.name = 'ucl'
|
|
3
|
-
s.version = '0.
|
|
3
|
+
s.version = '0.2.0'
|
|
4
4
|
s.summary = 'Universal Configuration Language (UCL) parser'
|
|
5
5
|
s.description = <<~EOF
|
|
6
6
|
Parse configuration files written in the Universal Configuration
|
|
@@ -14,14 +14,27 @@ Gem::Specification.new do |s|
|
|
|
14
14
|
s.authors = [ "Stéphane D'Alu" ]
|
|
15
15
|
s.email = [ 'sdalu@sdalu.com' ]
|
|
16
16
|
|
|
17
|
+
# The floor that has actually been exercised: 3.1 (Debian bookworm) and
|
|
18
|
+
# 3.4. Nothing here is known to need a newer Ruby.
|
|
19
|
+
s.required_ruby_version = '>= 3.1'
|
|
20
|
+
|
|
17
21
|
s.extensions = [ 'ext/extconf.rb' ]
|
|
18
|
-
s.files = %w[ ucl.gemspec README.md LICENSE ] +
|
|
19
|
-
Dir['ext/**/*.{c,h,rb}'] +
|
|
20
|
-
Dir['test/**/*.rb']
|
|
21
22
|
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
|
|
23
|
+
# ext/libucl is a git submodule holding the whole upstream tree, most of
|
|
24
|
+
# which (tests, utils, the Lua and Python bindings, docs) has no business
|
|
25
|
+
# in the gem -- hence an explicit list rather than an ext/**/* glob. It
|
|
26
|
+
# names what the embedded build compiles, plus the licence texts that
|
|
27
|
+
# redistributing those sources requires; see LICENSE-DEPENDENCIES.md.
|
|
28
|
+
# `rake build` refuses to run when the submodule is not populated, since
|
|
29
|
+
# these globs would silently come back empty.
|
|
30
|
+
s.files = %w[ ucl.gemspec README.md LICENSE LICENSE-DEPENDENCIES.md ] +
|
|
31
|
+
Dir['ext/*.{c,h,rb}'] +
|
|
32
|
+
Dir['test/**/*.rb'] +
|
|
33
|
+
%w[ ext/libucl/COPYING ] +
|
|
34
|
+
Dir['ext/libucl/include/ucl.h'] +
|
|
35
|
+
Dir['ext/libucl/src/*.{c,h}'] +
|
|
36
|
+
Dir['ext/libucl/uthash/*.h'] +
|
|
37
|
+
Dir['ext/libucl/klib/*.h']
|
|
25
38
|
|
|
26
39
|
s.add_development_dependency 'rake'
|
|
27
40
|
s.add_development_dependency 'minitest', '~> 5.0'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ucl
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stéphane D'Alu
|
|
@@ -9,20 +9,6 @@ bindir: bin
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
-
- !ruby/object:Gem::Dependency
|
|
13
|
-
name: mini_portile2
|
|
14
|
-
requirement: !ruby/object:Gem::Requirement
|
|
15
|
-
requirements:
|
|
16
|
-
- - "~>"
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '2.8'
|
|
19
|
-
type: :runtime
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - "~>"
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: '2.8'
|
|
26
12
|
- !ruby/object:Gem::Dependency
|
|
27
13
|
name: rake
|
|
28
14
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -77,8 +63,30 @@ extensions:
|
|
|
77
63
|
extra_rdoc_files: []
|
|
78
64
|
files:
|
|
79
65
|
- LICENSE
|
|
66
|
+
- LICENSE-DEPENDENCIES.md
|
|
80
67
|
- README.md
|
|
81
68
|
- ext/extconf.rb
|
|
69
|
+
- ext/libucl/COPYING
|
|
70
|
+
- ext/libucl/include/ucl.h
|
|
71
|
+
- ext/libucl/klib/khash.h
|
|
72
|
+
- ext/libucl/klib/kvec.h
|
|
73
|
+
- ext/libucl/src/mum.h
|
|
74
|
+
- ext/libucl/src/tree.h
|
|
75
|
+
- ext/libucl/src/ucl_chartable.h
|
|
76
|
+
- ext/libucl/src/ucl_emitter.c
|
|
77
|
+
- ext/libucl/src/ucl_emitter_streamline.c
|
|
78
|
+
- ext/libucl/src/ucl_emitter_utils.c
|
|
79
|
+
- ext/libucl/src/ucl_hash.c
|
|
80
|
+
- ext/libucl/src/ucl_hash.h
|
|
81
|
+
- ext/libucl/src/ucl_internal.h
|
|
82
|
+
- ext/libucl/src/ucl_msgpack.c
|
|
83
|
+
- ext/libucl/src/ucl_parser.c
|
|
84
|
+
- ext/libucl/src/ucl_schema.c
|
|
85
|
+
- ext/libucl/src/ucl_sexp.c
|
|
86
|
+
- ext/libucl/src/ucl_util.c
|
|
87
|
+
- ext/libucl/uthash/uthash.h
|
|
88
|
+
- ext/libucl/uthash/utlist.h
|
|
89
|
+
- ext/libucl/uthash/utstring.h
|
|
82
90
|
- ext/ucl.c
|
|
83
91
|
- test/test_ucl.rb
|
|
84
92
|
- ucl.gemspec
|
|
@@ -93,14 +101,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
93
101
|
requirements:
|
|
94
102
|
- - ">="
|
|
95
103
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '
|
|
104
|
+
version: '3.1'
|
|
97
105
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
98
106
|
requirements:
|
|
99
107
|
- - ">="
|
|
100
108
|
- !ruby/object:Gem::Version
|
|
101
109
|
version: '0'
|
|
102
110
|
requirements: []
|
|
103
|
-
rubygems_version: 4.0.
|
|
111
|
+
rubygems_version: 4.0.16
|
|
104
112
|
specification_version: 4
|
|
105
113
|
summary: Universal Configuration Language (UCL) parser
|
|
106
114
|
test_files: []
|