ucl 0.1.3.2 → 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.
data/test/test_ucl.rb ADDED
@@ -0,0 +1,452 @@
1
+ require 'minitest/autorun'
2
+ require 'tempfile'
3
+
4
+ # Load the compiled extension. By default it is looked up in ../ext (where
5
+ # `rake compile` builds it); UCL_EXT_DIR can point elsewhere.
6
+ libdir = ENV['UCL_EXT_DIR'] || File.expand_path('../ext', __dir__)
7
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
8
+ require 'ucl'
9
+
10
+ class TestUCL < Minitest::Test
11
+ def setup
12
+ # UCL.flags is global state; keep tests independent.
13
+ UCL.flags = 0
14
+ end
15
+
16
+ # ---- scalar types -------------------------------------------------------
17
+
18
+ def test_integer
19
+ assert_equal({ 'n' => 42 }, UCL.parse('n = 42'))
20
+ end
21
+
22
+ def test_negative_integer
23
+ assert_equal({ 'n' => -5 }, UCL.parse('n = -5'))
24
+ end
25
+
26
+ def test_large_integer
27
+ assert_equal({ 'n' => 9_999_999_999_999 }, UCL.parse('n = 9999999999999'))
28
+ end
29
+
30
+ def test_max_int64
31
+ assert_equal({ 'n' => 9_223_372_036_854_775_807 },
32
+ UCL.parse('n = 9223372036854775807'))
33
+ end
34
+
35
+ def test_hexadecimal_integer
36
+ assert_equal({ 'h' => 31 }, UCL.parse('h = 0x1f'))
37
+ end
38
+
39
+ def test_float
40
+ assert_equal({ 'f' => 1.5 }, UCL.parse('f = 1.5'))
41
+ end
42
+
43
+ def test_float_with_exponent
44
+ assert_equal({ 'f' => 1500.0 }, UCL.parse('f = 1.5e3'))
45
+ end
46
+
47
+ def test_string
48
+ assert_equal({ 's' => 'hello' }, UCL.parse('s = hello'))
49
+ end
50
+
51
+ def test_quoted_string
52
+ assert_equal({ 's' => 'hello world' }, UCL.parse('s = "hello world"'))
53
+ end
54
+
55
+ def test_string_keeps_utf8_bytes
56
+ # Strings are returned with ASCII-8BIT encoding, but the bytes are the
57
+ # verbatim (UTF-8) content of the configuration.
58
+ s = UCL.parse('s = "héllo"')['s']
59
+ assert_equal 'héllo'.b, s.b
60
+ end
61
+
62
+ def test_booleans
63
+ assert_equal({ 't' => true, 'f' => false }, UCL.parse("t = true\nf = false"))
64
+ end
65
+
66
+ def test_boolean_word_variants
67
+ assert_equal({ 'a' => true, 'b' => true, 'c' => false, 'd' => false },
68
+ UCL.parse("a = yes\nb = on\nc = off\nd = no"))
69
+ end
70
+
71
+ def test_null
72
+ assert_equal({ 'z' => nil }, UCL.parse('z = null'))
73
+ end
74
+
75
+ # ---- size multipliers ---------------------------------------------------
76
+
77
+ def test_si_multiplier
78
+ assert_equal({ 's' => 1000 }, UCL.parse('s = 1k'))
79
+ end
80
+
81
+ def test_binary_multipliers
82
+ assert_equal({ 'a' => 1024, 'b' => 1_048_576 },
83
+ UCL.parse("a = 1kb\nb = 1mb"))
84
+ end
85
+
86
+ # ---- containers ---------------------------------------------------------
87
+
88
+ def test_array
89
+ assert_equal({ 'a' => [1, 2, 3] }, UCL.parse('a = [1, 2, 3]'))
90
+ end
91
+
92
+ def test_mixed_type_array
93
+ assert_equal({ 'a' => [1, 'two', true, nil] },
94
+ UCL.parse('a = [1, "two", true, null]'))
95
+ end
96
+
97
+ def test_nested_arrays
98
+ assert_equal({ 'a' => [[1, 2], [3, 4]] }, UCL.parse('a = [[1,2],[3,4]]'))
99
+ end
100
+
101
+ def test_array_of_objects
102
+ assert_equal({ 'a' => [{ 'x' => 1 }, { 'y' => 2 }] },
103
+ UCL.parse('a = [ {x=1}, {y=2} ]'))
104
+ end
105
+
106
+ def test_nested_object
107
+ assert_equal({ 'o' => { 'x' => 1 } }, UCL.parse('o { x = 1 }'))
108
+ end
109
+
110
+ def test_deeply_nested
111
+ assert_equal({ 'a' => { 'b' => { 'c' => { 'd' => 1 } } } },
112
+ UCL.parse('a { b { c { d = 1 } } }'))
113
+ end
114
+
115
+ def test_dotted_key_is_kept_flat
116
+ assert_equal({ 'a.b.c' => 1 }, UCL.parse('a.b.c = 1'))
117
+ end
118
+
119
+ # ---- JSON compatibility -------------------------------------------------
120
+
121
+ def test_parses_json
122
+ assert_equal({ 'a' => 1, 'b' => [2, 3] }, UCL.parse('{"a": 1, "b": [2,3]}'))
123
+ end
124
+
125
+ # ---- comments -----------------------------------------------------------
126
+
127
+ def test_comments_are_ignored
128
+ assert_equal({ 'x' => 1, 'y' => 2 },
129
+ UCL.parse("# header\nx = 1 # inline\ny = 2"))
130
+ end
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
+
144
+ # ---- duplicate keys become an explicit array ----------------------------
145
+
146
+ def test_duplicate_keys_make_array
147
+ assert_equal({ 'k' => [1, 2, 3] }, UCL.parse("k = 1\nk = 2\nk = 3"))
148
+ end
149
+
150
+ def test_single_key_stays_scalar
151
+ assert_equal({ 'k' => 1 }, UCL.parse('k = 1'))
152
+ end
153
+
154
+ # ---- empty objects (regression: used to raise) --------------------------
155
+
156
+ def test_empty_input_returns_empty_hash
157
+ assert_equal({}, UCL.parse(''))
158
+ end
159
+
160
+ def test_whitespace_only_returns_empty_hash
161
+ assert_equal({}, UCL.parse(" \n "))
162
+ end
163
+
164
+ def test_comment_only_returns_empty_hash
165
+ assert_equal({}, UCL.parse("# nothing here\n"))
166
+ end
167
+
168
+ def test_empty_object
169
+ assert_equal({ 'e' => {} }, UCL.parse('e {}'))
170
+ end
171
+
172
+ def test_nested_empty_object
173
+ assert_equal({ 'a' => { 'b' => {} } }, UCL.parse('a { b {} }'))
174
+ end
175
+
176
+ def test_empty_object_then_more_keys
177
+ assert_equal({ 'a' => {}, 'b' => 2 }, UCL.parse("a {}\nb = 2"))
178
+ end
179
+
180
+ def test_empty_array
181
+ assert_equal({ 'a' => [] }, UCL.parse('a = []'))
182
+ end
183
+
184
+ # ---- time ---------------------------------------------------------------
185
+
186
+ def test_time_is_parsed_to_float
187
+ assert_equal({ 't' => 10.0 }, UCL.parse('t = 10s'))
188
+ end
189
+
190
+ def test_no_time_keeps_string
191
+ assert_equal({ 't' => '10s' }, UCL.parse('t = 10s', UCL::NO_TIME))
192
+ end
193
+
194
+ # ---- key flags ----------------------------------------------------------
195
+
196
+ def test_key_symbol
197
+ assert_equal({ name: 'value' }, UCL.parse('name = value', UCL::KEY_SYMBOL))
198
+ end
199
+
200
+ def test_key_symbol_recurses_into_nested_objects
201
+ assert_equal({ o: { x: 1 } }, UCL.parse('o { x = 1 }', UCL::KEY_SYMBOL))
202
+ end
203
+
204
+ def test_key_lowercase
205
+ assert_equal({ 'foo' => 1 }, UCL.parse('FOO = 1', UCL::KEY_LOWERCASE))
206
+ end
207
+
208
+ def test_combined_flags
209
+ assert_equal({ foo: 1 },
210
+ UCL.parse('FOO = 1', UCL::KEY_SYMBOL | UCL::KEY_LOWERCASE))
211
+ end
212
+
213
+ # ---- macros -------------------------------------------------------------
214
+
215
+ def test_disable_macro_does_not_process_include
216
+ # DISABLE_MACRO stops macros from being executed, so the referenced file
217
+ # is never read. libucl versions differ in how they treat the macro line:
218
+ # older ones ignore it (parsing only the remaining keys), newer ones reject
219
+ # it as an invalid key. Both outcomes are acceptable here.
220
+ config = %(.include "/no/such/file"\nx = 1)
221
+ begin
222
+ assert_equal({ 'x' => 1 }, UCL.parse(config, UCL::DISABLE_MACRO))
223
+ rescue UCL::Error => e
224
+ refute_empty e.message
225
+ end
226
+ end
227
+
228
+ def test_include_of_missing_file_raises_without_flag
229
+ assert_raises(UCL::Error) { UCL.parse(%(.include "/no/such"\nx = 1)) }
230
+ end
231
+
232
+ # ---- file variables -----------------------------------------------------
233
+
234
+ def test_no_filevars_leaves_variable_unexpanded
235
+ assert_equal({ 'd' => '$CURDIR' },
236
+ UCL.parse('d = $CURDIR', UCL::NO_FILEVARS))
237
+ end
238
+
239
+ def test_parse_expands_curdir_by_default
240
+ refute_equal '$CURDIR', UCL.parse('d = $CURDIR')['d']
241
+ end
242
+
243
+ # ---- default flags ------------------------------------------------------
244
+
245
+ def test_flags_default_to_zero
246
+ assert_equal 0, UCL.flags
247
+ end
248
+
249
+ def test_flags_are_used_as_default
250
+ UCL.flags = UCL::KEY_SYMBOL
251
+ assert_equal({ name: 'value' }, UCL.parse('name = value'))
252
+ end
253
+
254
+ def test_explicit_flags_override_default
255
+ UCL.flags = UCL::KEY_SYMBOL
256
+ assert_equal({ 'name' => 'value' }, UCL.parse('name = value', 0))
257
+ end
258
+
259
+ # ---- result object ------------------------------------------------------
260
+
261
+ def test_result_is_mutable
262
+ result = UCL.parse('a = 1')
263
+ result['b'] = 2
264
+ assert_equal({ 'a' => 1, 'b' => 2 }, result)
265
+ end
266
+
267
+ # ---- load_file ----------------------------------------------------------
268
+
269
+ def test_load_file
270
+ with_conf("list = [1, 2, 3]\nname = test\n") do |path|
271
+ assert_equal({ 'list' => [1, 2, 3], 'name' => 'test' },
272
+ UCL.load_file(path))
273
+ end
274
+ end
275
+
276
+ def test_load_file_sets_filename_variable
277
+ with_conf('f = "$FILENAME"' + "\n") do |path|
278
+ assert_equal({ 'f' => File.realpath(path) }, UCL.load_file(path))
279
+ end
280
+ end
281
+
282
+ def test_load_file_with_flags
283
+ with_conf("Name = value\n") do |path|
284
+ assert_equal({ name: 'value' },
285
+ UCL.load_file(path, UCL::KEY_SYMBOL | UCL::KEY_LOWERCASE))
286
+ end
287
+ end
288
+
289
+ def test_load_missing_file_raises
290
+ assert_raises(UCL::Error) { UCL.load_file('/no/such/file.conf') }
291
+ end
292
+
293
+ # ---- errors -------------------------------------------------------------
294
+
295
+ def test_malformed_raises_ucl_error
296
+ assert_raises(UCL::Error) { UCL.parse('a = [') }
297
+ end
298
+
299
+ def test_error_message_is_present
300
+ err = assert_raises(UCL::Error) { UCL.parse('a = [') }
301
+ refute_empty err.message
302
+ end
303
+
304
+ def test_error_is_a_standard_error
305
+ assert_operator UCL::Error, :<, StandardError
306
+ end
307
+
308
+ def test_repeated_malformed_parses_do_not_crash
309
+ 100.times do
310
+ assert_raises(UCL::Error) { UCL.parse('} bad {') }
311
+ end
312
+ end
313
+
314
+ def test_parse_rejects_non_string
315
+ assert_raises(TypeError) { UCL.parse(42) }
316
+ end
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
+
421
+ # ---- constants ----------------------------------------------------------
422
+
423
+ def test_constants_defined
424
+ %i[KEY_SYMBOL KEY_LOWERCASE NO_TIME DISABLE_MACRO NO_FILEVARS].each do |c|
425
+ assert UCL.const_defined?(c), "UCL::#{c} should be defined"
426
+ end
427
+ end
428
+
429
+ private
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
+
445
+ def with_conf(content)
446
+ Tempfile.create(['ucl', '.conf']) do |f|
447
+ f.write(content)
448
+ f.flush
449
+ yield f.path
450
+ end
451
+ end
452
+ end
data/ucl.gemspec CHANGED
@@ -1,12 +1,12 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'ucl'
3
- s.version = '0.1.3.2'
4
- s.summary = " Universal configuration library parser"
3
+ s.version = '0.2.0'
4
+ s.summary = 'Universal Configuration Language (UCL) parser'
5
5
  s.description = <<~EOF
6
-
7
- Read configuration file in UCL format (binding to the libucl).
8
-
9
- EOF
6
+ Parse configuration files written in the Universal Configuration
7
+ Language (UCL), a human-friendly JSON superset. Native bindings to
8
+ the libucl library; results are returned as plain Ruby objects.
9
+ EOF
10
10
 
11
11
  s.homepage = 'https://github.com/sdalu/ruby-ucl'
12
12
  s.license = 'MIT'
@@ -14,6 +14,29 @@ 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 ] + Dir['ext/**/*.{c,h,rb}']
22
+
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']
38
+
39
+ s.add_development_dependency 'rake'
40
+ s.add_development_dependency 'minitest', '~> 5.0'
41
+ s.add_development_dependency 'yard'
19
42
  end
metadata CHANGED
@@ -1,19 +1,60 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ucl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stéphane D'Alu
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-12-02 00:00:00.000000000 Z
12
- dependencies: []
13
- description: |2+
14
-
15
- Read configuration file in UCL format (binding to the libucl).
16
-
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rake
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: minitest
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '5.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: yard
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ description: |
55
+ Parse configuration files written in the Universal Configuration
56
+ Language (UCL), a human-friendly JSON superset. Native bindings to
57
+ the libucl library; results are returned as plain Ruby objects.
17
58
  email:
18
59
  - sdalu@sdalu.com
19
60
  executables: []
@@ -21,14 +62,38 @@ extensions:
21
62
  - ext/extconf.rb
22
63
  extra_rdoc_files: []
23
64
  files:
65
+ - LICENSE
66
+ - LICENSE-DEPENDENCIES.md
67
+ - README.md
24
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
25
90
  - ext/ucl.c
91
+ - test/test_ucl.rb
26
92
  - ucl.gemspec
27
93
  homepage: https://github.com/sdalu/ruby-ucl
28
94
  licenses:
29
95
  - MIT
30
96
  metadata: {}
31
- post_install_message:
32
97
  rdoc_options: []
33
98
  require_paths:
34
99
  - lib
@@ -36,16 +101,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
36
101
  requirements:
37
102
  - - ">="
38
103
  - !ruby/object:Gem::Version
39
- version: '0'
104
+ version: '3.1'
40
105
  required_rubygems_version: !ruby/object:Gem::Requirement
41
106
  requirements:
42
107
  - - ">="
43
108
  - !ruby/object:Gem::Version
44
109
  version: '0'
45
110
  requirements: []
46
- rubygems_version: 3.5.19
47
- signing_key:
111
+ rubygems_version: 4.0.16
48
112
  specification_version: 4
49
- summary: Universal configuration library parser
113
+ summary: Universal Configuration Language (UCL) parser
50
114
  test_files: []
51
- ...