ucl 0.1.3.1 → 0.1.4
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 +21 -0
- data/README.md +181 -0
- data/ext/extconf.rb +65 -2
- data/ext/ucl.c +125 -36
- data/test/test_ucl.rb +323 -0
- data/ucl.gemspec +17 -7
- metadata +68 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 03ae85a910a91ff58975400ecfc0fa2d71a73491c448935e083c909e74a888b7
|
|
4
|
+
data.tar.gz: '07085efe0e973f192f82fbe5bb0afa4afada1ea7d4f8e97e83f550c615f66322'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 544b300d55a2599c199464048da8206a206e688f13dc6b6eebcf7954e5adaac886686e0c5be322ee48531b213de8d888514834cfff2d87949e84885f650399ab
|
|
7
|
+
data.tar.gz: 7deaa1b5f34932b98b7afcbf71904aa08bd9ad3fd3a87b5f7f29ba5e66be7ec651bb7ddd6e6182e84c4d9baa867e4c7f5f490e0db5bc5e6613d295a8fb0dc239
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Stéphane D'Alu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
ruby-ucl
|
|
2
|
+
========
|
|
3
|
+
|
|
4
|
+
Ruby bindings to the [libucl][1] library for parsing configuration files
|
|
5
|
+
written in the **U**niversal **C**onfiguration **L**anguage (UCL).
|
|
6
|
+
|
|
7
|
+
UCL is a configuration format inspired by [nginx][2] and JSON. It is a
|
|
8
|
+
superset of JSON, so any valid JSON document is also valid UCL, while
|
|
9
|
+
adding a more relaxed, human-friendly syntax (unquoted keys, comments,
|
|
10
|
+
optional commas, multipliers, macros, …).
|
|
11
|
+
|
|
12
|
+
Parsed configurations are returned as plain Ruby objects (`Hash`,
|
|
13
|
+
`Array`, `String`, `Integer`, `Float`, `true`/`false`, `nil`), so no
|
|
14
|
+
special object model has to be learned.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Installation
|
|
18
|
+
------------
|
|
19
|
+
|
|
20
|
+
~~~sh
|
|
21
|
+
gem install ucl
|
|
22
|
+
~~~
|
|
23
|
+
|
|
24
|
+
Or add it to your `Gemfile`:
|
|
25
|
+
|
|
26
|
+
~~~ruby
|
|
27
|
+
gem 'ucl'
|
|
28
|
+
~~~
|
|
29
|
+
|
|
30
|
+
The extension binds to the native [libucl][1] library (version 0.8.2 or
|
|
31
|
+
later). At build time it is located as follows:
|
|
32
|
+
|
|
33
|
+
1. If a system-wide libucl is found (via `pkg-config`, or under `/opt` or
|
|
34
|
+
`/usr/local`), it is used.
|
|
35
|
+
2. Otherwise libucl is **downloaded and compiled from source** automatically
|
|
36
|
+
(using [mini_portile2][4] and CMake). This requires `cmake` and a C
|
|
37
|
+
compiler to be available.
|
|
38
|
+
|
|
39
|
+
You can force the source build regardless of any system installation:
|
|
40
|
+
|
|
41
|
+
~~~sh
|
|
42
|
+
gem install ucl -- --enable-vendor-libucl
|
|
43
|
+
# or, with Bundler:
|
|
44
|
+
bundle config set build.ucl --enable-vendor-libucl
|
|
45
|
+
~~~
|
|
46
|
+
|
|
47
|
+
> **Debian/Ubuntu note:** do **not** `apt install libucl-dev`. That package
|
|
48
|
+
> is an unrelated [UCL *data-compression* library][3] that merely shares the
|
|
49
|
+
> name — it does not provide `ucl_parser_new`. Since the configuration
|
|
50
|
+
> parser is not packaged for Debian, just install `cmake` and a compiler and
|
|
51
|
+
> let the gem build libucl from source. (The parser *is* packaged on Fedora,
|
|
52
|
+
> Homebrew and FreeBSD ports as `libucl`.)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
Usage
|
|
56
|
+
-----
|
|
57
|
+
|
|
58
|
+
~~~ruby
|
|
59
|
+
require 'ucl'
|
|
60
|
+
|
|
61
|
+
# Parse a string of UCL data
|
|
62
|
+
UCL.parse(File.read('foo.conf'))
|
|
63
|
+
|
|
64
|
+
# Parse a file directly (enables file-relative variables such as $FILENAME)
|
|
65
|
+
UCL.load_file('foo.conf')
|
|
66
|
+
|
|
67
|
+
# Pass flags explicitly (combine them with a bitwise OR)
|
|
68
|
+
UCL.load_file('foo.conf', UCL::KEY_SYMBOL | UCL::KEY_LOWERCASE)
|
|
69
|
+
|
|
70
|
+
# Or set the default flags applied to every subsequent call
|
|
71
|
+
UCL.flags = UCL::KEY_SYMBOL
|
|
72
|
+
UCL.parse('name = value') #=> { :name => "value" }
|
|
73
|
+
~~~
|
|
74
|
+
|
|
75
|
+
Both `parse` and `load_file` accept an optional `flags` argument. When it
|
|
76
|
+
is omitted, the value of `UCL.flags` (default: no flag) is used.
|
|
77
|
+
|
|
78
|
+
On a malformed configuration, or if conversion of the parsed tree fails,
|
|
79
|
+
a `UCL::Error` is raised.
|
|
80
|
+
|
|
81
|
+
Given a configuration like:
|
|
82
|
+
|
|
83
|
+
~~~nginx
|
|
84
|
+
# a sample configuration
|
|
85
|
+
name = example
|
|
86
|
+
timeout = 30s # parsed as a number of seconds
|
|
87
|
+
max_size = 10mb # size multipliers are understood
|
|
88
|
+
|
|
89
|
+
servers = [
|
|
90
|
+
{ host = a.example, port = 8080 },
|
|
91
|
+
{ host = b.example, port = 8081 },
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
logging {
|
|
95
|
+
level = info
|
|
96
|
+
enabled = yes
|
|
97
|
+
}
|
|
98
|
+
~~~
|
|
99
|
+
|
|
100
|
+
`UCL.load_file` returns:
|
|
101
|
+
|
|
102
|
+
~~~ruby
|
|
103
|
+
{
|
|
104
|
+
"name" => "example",
|
|
105
|
+
"timeout" => 30.0,
|
|
106
|
+
"max_size" => 10485760,
|
|
107
|
+
"servers" => [ { "host" => "a.example", "port" => 8080 },
|
|
108
|
+
{ "host" => "b.example", "port" => 8081 } ],
|
|
109
|
+
"logging" => { "level" => "info", "enabled" => true },
|
|
110
|
+
}
|
|
111
|
+
~~~
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
Flags
|
|
115
|
+
-----
|
|
116
|
+
|
|
117
|
+
| Flag | Effect |
|
|
118
|
+
|------------------|---------------------------------------------------------------|
|
|
119
|
+
| `KEY_SYMBOL` | Return object keys as `Symbol` instead of `String`. |
|
|
120
|
+
| `KEY_LOWERCASE` | Convert all keys to lower case. |
|
|
121
|
+
| `NO_TIME` | Do not parse time values; keep them as strings. |
|
|
122
|
+
| `DISABLE_MACRO` | Disable processing of macros (e.g. `.include`). |
|
|
123
|
+
| `NO_FILEVARS` | Do not predefine `$FILENAME` / `$CURDIR` (affects `parse`; `load_file` still sets them from the file). |
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
Type mapping
|
|
127
|
+
------------
|
|
128
|
+
|
|
129
|
+
| UCL type | Ruby type |
|
|
130
|
+
|------------|--------------------------|
|
|
131
|
+
| object | `Hash` |
|
|
132
|
+
| array | `Array` |
|
|
133
|
+
| string | `String` |
|
|
134
|
+
| integer | `Integer` |
|
|
135
|
+
| float | `Float` |
|
|
136
|
+
| time | `Float` (seconds) |
|
|
137
|
+
| boolean | `true` / `false` |
|
|
138
|
+
| null | `nil` |
|
|
139
|
+
|
|
140
|
+
Notes:
|
|
141
|
+
|
|
142
|
+
* The parser uses explicit (not implicit) arrays, so a key repeated several
|
|
143
|
+
times is collected into an `Array` of its values:
|
|
144
|
+
|
|
145
|
+
~~~ruby
|
|
146
|
+
UCL.parse("a = 1\na = 2") #=> { "a" => [1, 2] }
|
|
147
|
+
UCL.parse("a = 1") #=> { "a" => 1 }
|
|
148
|
+
~~~
|
|
149
|
+
|
|
150
|
+
* Integers understand size multipliers (`1k` → 1000, `1kb` → 1024,
|
|
151
|
+
`1mb` → 1048576), hexadecimal (`0x1f` → 31) and JSON is accepted as-is.
|
|
152
|
+
|
|
153
|
+
* Returned strings carry their verbatim bytes but are tagged with the
|
|
154
|
+
`ASCII-8BIT` (binary) encoding; call `String#force_encoding('UTF-8')` if
|
|
155
|
+
you need them as UTF-8.
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
Development
|
|
159
|
+
-----------
|
|
160
|
+
|
|
161
|
+
~~~sh
|
|
162
|
+
bundle install # install development dependencies
|
|
163
|
+
rake compile # build the C extension into ext/
|
|
164
|
+
rake test # compile and run the test suite
|
|
165
|
+
rake clobber # remove all generated files
|
|
166
|
+
~~~
|
|
167
|
+
|
|
168
|
+
`rake test` works with the development gems installed system-wide too; with
|
|
169
|
+
a `Gemfile.lock` present, prefix commands with `bundle exec`.
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
License
|
|
173
|
+
-------
|
|
174
|
+
|
|
175
|
+
Released under the MIT License. See [LICENSE](LICENSE).
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
[1]: https://github.com/vstakhov/libucl
|
|
179
|
+
[2]: https://nginx.org/
|
|
180
|
+
[3]: https://www.oberhumer.com/opensource/ucl/
|
|
181
|
+
[4]: https://github.com/flavorjones/mini_portile
|
data/ext/extconf.rb
CHANGED
|
@@ -1,6 +1,69 @@
|
|
|
1
1
|
require 'mkmf'
|
|
2
2
|
|
|
3
|
+
# Pinned libucl (vstakhov's Universal Configuration Language parser) used
|
|
4
|
+
# when the library has to be built from source. NOTE: this is *not* the
|
|
5
|
+
# Debian `libucl-dev` package, which is an unrelated compression library.
|
|
6
|
+
LIBUCL_VERSION = '0.9.4'
|
|
7
|
+
LIBUCL_SHA256 = '319d8ff13441f55d91cd7f3708a54bd03779733e26958c2346c5109014520aaf'
|
|
3
8
|
|
|
4
|
-
|
|
9
|
+
# Force building libucl from source, ignoring any system installation:
|
|
10
|
+
# gem install ucl -- --enable-vendor-libucl
|
|
11
|
+
# bundle config set build.ucl --enable-vendor-libucl
|
|
12
|
+
# UCL_VENDOR_LIBUCL=1 rake compile
|
|
13
|
+
force_vendor = enable_config('vendor-libucl', false) ||
|
|
14
|
+
ENV.key?('UCL_VENDOR_LIBUCL')
|
|
5
15
|
|
|
6
|
-
|
|
16
|
+
# Locate a system-wide libucl: pkg-config first, then the usual prefixes.
|
|
17
|
+
def system_libucl
|
|
18
|
+
return true if pkg_config('libucl')
|
|
19
|
+
|
|
20
|
+
find_header( 'ucl.h', '/opt/include', '/usr/local/include') &&
|
|
21
|
+
find_library('ucl', 'ucl_parser_new', '/opt/lib', '/usr/local/lib')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
if !force_vendor && system_libucl
|
|
25
|
+
message "Using system libucl.\n"
|
|
26
|
+
else
|
|
27
|
+
message "Building libucl #{LIBUCL_VERSION} from source.\n"
|
|
28
|
+
|
|
29
|
+
# Building from source needs cmake and a working C compiler.
|
|
30
|
+
missing = []
|
|
31
|
+
missing << 'cmake' unless find_executable('cmake')
|
|
32
|
+
missing << 'a C compiler' unless try_compile('int main(void) { return 0; }')
|
|
33
|
+
unless missing.empty?
|
|
34
|
+
abort "\nBuilding libucl from source requires #{missing.join(' and ')}, " \
|
|
35
|
+
"which #{missing.length > 1 ? 'are' : 'is'} not available.\n" \
|
|
36
|
+
"Install the missing tool(s), or provide a system-wide libucl.\n"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
require 'mini_portile2'
|
|
40
|
+
|
|
41
|
+
recipe = MiniPortileCMake.new('libucl', LIBUCL_VERSION)
|
|
42
|
+
recipe.files = [{
|
|
43
|
+
url: "https://github.com/vstakhov/libucl/archive/refs/tags/#{LIBUCL_VERSION}.tar.gz",
|
|
44
|
+
sha256: LIBUCL_SHA256,
|
|
45
|
+
}]
|
|
46
|
+
# Static, position-independent build with every optional feature disabled.
|
|
47
|
+
recipe.configure_options += %w[
|
|
48
|
+
-DCMAKE_BUILD_TYPE=Release
|
|
49
|
+
-DBUILD_SHARED_LIBS=OFF
|
|
50
|
+
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
|
|
51
|
+
-DENABLE_URL_INCLUDE=OFF
|
|
52
|
+
-DENABLE_LUA=OFF
|
|
53
|
+
-DENABLE_UTILS=OFF
|
|
54
|
+
]
|
|
55
|
+
recipe.cook # download, verify checksum, cmake build + install into recipe.path
|
|
56
|
+
recipe.activate
|
|
57
|
+
|
|
58
|
+
$INCFLAGS << " -I#{recipe.path}/include"
|
|
59
|
+
$LIBPATH.unshift "#{recipe.path}/lib"
|
|
60
|
+
have_library('m') # libucl relies on the math library
|
|
61
|
+
|
|
62
|
+
unless find_header( 'ucl.h', "#{recipe.path}/include") &&
|
|
63
|
+
find_library('ucl', 'ucl_parser_new', "#{recipe.path}/lib")
|
|
64
|
+
abort "\nlibucl was built but could not be linked " \
|
|
65
|
+
"(check the static archive name/dir under #{recipe.path}/lib).\n"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
create_makefile('ucl')
|
data/ext/ucl.c
CHANGED
|
@@ -11,13 +11,53 @@
|
|
|
11
11
|
/**
|
|
12
12
|
* Document-class: UCL
|
|
13
13
|
*
|
|
14
|
-
*
|
|
14
|
+
* Parser for configuration files written in the Universal Configuration
|
|
15
|
+
* Language (UCL), a JSON-superset format handled by the libucl library.
|
|
16
|
+
*
|
|
17
|
+
* Parsed configurations are returned as plain Ruby objects (Hash, Array,
|
|
18
|
+
* String, Integer, Float, true/false, nil).
|
|
19
|
+
*
|
|
20
|
+
* @example Parse a string
|
|
21
|
+
* UCL.parse('name = value') #=> { "name" => "value" }
|
|
22
|
+
*
|
|
23
|
+
* @example Load a file with symbol keys
|
|
24
|
+
* UCL.load_file('foo.conf', UCL::KEY_SYMBOL)
|
|
25
|
+
*
|
|
26
|
+
* @see https://github.com/vstakhov/libucl
|
|
15
27
|
*/
|
|
16
28
|
|
|
17
29
|
/**
|
|
18
30
|
* Document-class: UCL::Error
|
|
19
31
|
*
|
|
20
|
-
*
|
|
32
|
+
* Raised when a configuration cannot be parsed, or when the parsed tree
|
|
33
|
+
* cannot be converted to Ruby objects.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Document-const: KEY_LOWERCASE
|
|
38
|
+
* Flag: convert all object keys to lower case.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Document-const: NO_TIME
|
|
43
|
+
* Flag: do not parse time values; keep them as strings.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Document-const: DISABLE_MACRO
|
|
48
|
+
* Flag: disable processing of macros (e.g. <code>.include</code>).
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Document-const: NO_FILEVARS
|
|
53
|
+
* Flag: do not predefine the file variables (<code>$FILENAME</code>,
|
|
54
|
+
* <code>$CURDIR</code>). This affects {UCL.parse}; {UCL.load_file} still
|
|
55
|
+
* derives those variables from the file being loaded.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Document-const: KEY_SYMBOL
|
|
60
|
+
* Flag: return object keys as Symbol instead of String.
|
|
21
61
|
*/
|
|
22
62
|
|
|
23
63
|
|
|
@@ -32,13 +72,13 @@ static int ucl_allowed_c_flags = UCL_PARSER_KEY_LOWERCASE |
|
|
|
32
72
|
|
|
33
73
|
|
|
34
74
|
|
|
35
|
-
VALUE
|
|
75
|
+
static VALUE
|
|
36
76
|
_iterate_valid_ucl(ucl_object_t const *root, int flags, bool *failed)
|
|
37
77
|
{
|
|
38
|
-
ucl_object_iter_t it =
|
|
78
|
+
ucl_object_iter_t it = NULL; /* only allocated for objects/arrays */
|
|
39
79
|
const ucl_object_t *obj = NULL;
|
|
40
80
|
|
|
41
|
-
VALUE val;
|
|
81
|
+
VALUE val = Qnil;
|
|
42
82
|
|
|
43
83
|
switch (root->type) {
|
|
44
84
|
case UCL_INT:
|
|
@@ -64,10 +104,12 @@ _iterate_valid_ucl(ucl_object_t const *root, int flags, bool *failed)
|
|
|
64
104
|
val = rb_float_new(ucl_object_todouble(root));
|
|
65
105
|
break;
|
|
66
106
|
|
|
67
|
-
case UCL_OBJECT:
|
|
107
|
+
case UCL_OBJECT: {
|
|
108
|
+
bool iterated = false;
|
|
68
109
|
val = rb_hash_new();
|
|
69
|
-
it =
|
|
110
|
+
it = ucl_object_iterate_new(root);
|
|
70
111
|
while ((obj = ucl_object_iterate_safe(it, !true))) {
|
|
112
|
+
iterated = true;
|
|
71
113
|
size_t keylen;
|
|
72
114
|
const char *key = ucl_object_keyl(obj, &keylen);
|
|
73
115
|
VALUE v_key = rb_str_new(key, keylen);
|
|
@@ -75,17 +117,25 @@ _iterate_valid_ucl(ucl_object_t const *root, int flags, bool *failed)
|
|
|
75
117
|
v_key = rb_to_symbol(v_key);
|
|
76
118
|
rb_hash_aset(val, v_key, _iterate_valid_ucl(obj, flags, failed));
|
|
77
119
|
}
|
|
78
|
-
|
|
120
|
+
/* An empty object has a NULL hash that the safe iterator reports as an
|
|
121
|
+
* exception (EINVAL); ignore that and only flag a genuine error that
|
|
122
|
+
* occurs while iterating. Accumulate so a nested failure deeper in the
|
|
123
|
+
* tree is never cleared by a successful parent iteration. */
|
|
124
|
+
if (iterated && ucl_object_iter_chk_excpn(it)) *failed = true;
|
|
79
125
|
break;
|
|
80
|
-
|
|
81
|
-
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
case UCL_ARRAY: {
|
|
129
|
+
bool iterated = false;
|
|
82
130
|
val = rb_ary_new();
|
|
83
|
-
it =
|
|
131
|
+
it = ucl_object_iterate_new(root);
|
|
84
132
|
while ((obj = ucl_object_iterate_safe(it, !true))) {
|
|
133
|
+
iterated = true;
|
|
85
134
|
rb_ary_push(val, _iterate_valid_ucl(obj, flags, failed));
|
|
86
135
|
}
|
|
87
|
-
|
|
136
|
+
if (iterated && ucl_object_iter_chk_excpn(it)) *failed = true;
|
|
88
137
|
break;
|
|
138
|
+
}
|
|
89
139
|
|
|
90
140
|
case UCL_USERDATA:
|
|
91
141
|
val = rb_str_new(root->value.sv, root->len);
|
|
@@ -96,14 +146,20 @@ _iterate_valid_ucl(ucl_object_t const *root, int flags, bool *failed)
|
|
|
96
146
|
break;
|
|
97
147
|
|
|
98
148
|
default:
|
|
99
|
-
|
|
100
|
-
|
|
149
|
+
rb_raise(eUCLError, "unhandled UCL type (%d)", root->type);
|
|
150
|
+
|
|
101
151
|
}
|
|
102
152
|
|
|
103
|
-
ucl_object_iterate_free(it);
|
|
153
|
+
if (it != NULL) ucl_object_iterate_free(it);
|
|
104
154
|
return val;
|
|
105
155
|
}
|
|
106
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Default flags applied by {UCL.parse} and {UCL.load_file} when none are
|
|
159
|
+
* given explicitly.
|
|
160
|
+
*
|
|
161
|
+
* @return [Integer] the current default flags (0 by default)
|
|
162
|
+
*/
|
|
107
163
|
static VALUE
|
|
108
164
|
ucl_s_get_flags(VALUE klass)
|
|
109
165
|
{
|
|
@@ -111,6 +167,17 @@ ucl_s_get_flags(VALUE klass)
|
|
|
111
167
|
}
|
|
112
168
|
|
|
113
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Set the default flags applied by {UCL.parse} and {UCL.load_file} when
|
|
172
|
+
* none are given explicitly.
|
|
173
|
+
*
|
|
174
|
+
* @param val [Integer] flags, combined with a bitwise OR
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* UCL.flags = UCL::KEY_SYMBOL | UCL::KEY_LOWERCASE
|
|
178
|
+
*
|
|
179
|
+
* @return [Integer] the flags that were set
|
|
180
|
+
*/
|
|
114
181
|
static VALUE
|
|
115
182
|
ucl_s_set_flags(VALUE klass, VALUE val)
|
|
116
183
|
{
|
|
@@ -121,12 +188,19 @@ ucl_s_set_flags(VALUE klass, VALUE val)
|
|
|
121
188
|
|
|
122
189
|
|
|
123
190
|
/**
|
|
124
|
-
* Parse a configuration
|
|
191
|
+
* Parse a UCL configuration from a string.
|
|
192
|
+
*
|
|
193
|
+
* @param data [String] the UCL configuration to parse
|
|
194
|
+
* @param flags [Integer] parsing flags combined with a bitwise OR;
|
|
195
|
+
* defaults to {UCL.flags} when omitted
|
|
125
196
|
*
|
|
126
|
-
* @
|
|
127
|
-
*
|
|
197
|
+
* @example
|
|
198
|
+
* UCL.parse('name = value') #=> { "name" => "value" }
|
|
199
|
+
* UCL.parse('name = value', UCL::KEY_SYMBOL) #=> { :name => "value" }
|
|
200
|
+
*
|
|
201
|
+
* @raise [UCL::Error] if the configuration is malformed
|
|
128
202
|
*
|
|
129
|
-
* @return configuration
|
|
203
|
+
* @return [Hash, Array, Object] the configuration as Ruby objects
|
|
130
204
|
*/
|
|
131
205
|
static VALUE
|
|
132
206
|
ucl_s_parse(int argc, VALUE *argv, VALUE klass)
|
|
@@ -142,23 +216,27 @@ ucl_s_parse(int argc, VALUE *argv, VALUE klass)
|
|
|
142
216
|
|
|
143
217
|
struct ucl_parser *parser =
|
|
144
218
|
ucl_parser_new(c_flags | UCL_PARSER_NO_IMPLICIT_ARRAYS);
|
|
219
|
+
if (parser == NULL)
|
|
220
|
+
rb_raise(eUCLError, "failed to allocate UCL parser");
|
|
145
221
|
|
|
146
222
|
ucl_parser_add_chunk(parser,
|
|
147
223
|
(unsigned char *)RSTRING_PTR(data),
|
|
148
224
|
RSTRING_LEN(data));
|
|
149
|
-
|
|
225
|
+
|
|
150
226
|
if (ucl_parser_get_error(parser)) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
227
|
+
/* Copy the message into the exception before freeing the parser:
|
|
228
|
+
* ucl_parser_get_error() points into memory owned by the parser. */
|
|
229
|
+
VALUE err = rb_exc_new2(eUCLError, ucl_parser_get_error(parser));
|
|
230
|
+
ucl_parser_free(parser);
|
|
231
|
+
rb_exc_raise(err);
|
|
154
232
|
}
|
|
155
233
|
|
|
156
234
|
bool failed = false;
|
|
157
235
|
ucl_object_t *root = ucl_parser_get_object(parser);
|
|
158
236
|
VALUE res = _iterate_valid_ucl(root, FIX2INT(flags), &failed);
|
|
159
237
|
|
|
160
|
-
|
|
161
|
-
if (root
|
|
238
|
+
ucl_parser_free(parser);
|
|
239
|
+
if (root != NULL) { ucl_object_unref(root); }
|
|
162
240
|
|
|
163
241
|
if (failed) {
|
|
164
242
|
rb_raise(eUCLError, "failed to iterate over ucl object");
|
|
@@ -169,15 +247,22 @@ ucl_s_parse(int argc, VALUE *argv, VALUE klass)
|
|
|
169
247
|
|
|
170
248
|
|
|
171
249
|
/**
|
|
172
|
-
* Load configuration file
|
|
250
|
+
* Load and parse a UCL configuration from a file.
|
|
173
251
|
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
252
|
+
* Unlike {UCL.parse}, this defines the file variables ($FILENAME,
|
|
253
|
+
* $CURDIR) from the loaded file, so they can be referenced from within
|
|
254
|
+
* the configuration.
|
|
255
|
+
*
|
|
256
|
+
* @param file [String] path to the configuration file
|
|
257
|
+
* @param flags [Integer] parsing flags combined with a bitwise OR;
|
|
258
|
+
* defaults to {UCL.flags} when omitted
|
|
176
259
|
*
|
|
177
260
|
* @example
|
|
178
261
|
* UCL.load_file('foo.conf', UCL::KEY_SYMBOL)
|
|
179
262
|
*
|
|
180
|
-
* @
|
|
263
|
+
* @raise [UCL::Error] if the file cannot be read or is malformed
|
|
264
|
+
*
|
|
265
|
+
* @return [Hash, Array, Object] the configuration as Ruby objects
|
|
181
266
|
*/
|
|
182
267
|
static VALUE
|
|
183
268
|
ucl_s_load_file(int argc, VALUE *argv, VALUE klass)
|
|
@@ -194,23 +279,27 @@ ucl_s_load_file(int argc, VALUE *argv, VALUE klass)
|
|
|
194
279
|
|
|
195
280
|
struct ucl_parser *parser =
|
|
196
281
|
ucl_parser_new(c_flags | UCL_PARSER_NO_IMPLICIT_ARRAYS);
|
|
282
|
+
if (parser == NULL)
|
|
283
|
+
rb_raise(eUCLError, "failed to allocate UCL parser");
|
|
197
284
|
|
|
198
285
|
ucl_parser_add_file(parser, c_file);
|
|
199
286
|
ucl_parser_set_filevars(parser, c_file, false);
|
|
200
|
-
|
|
287
|
+
|
|
201
288
|
if (ucl_parser_get_error(parser)) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
289
|
+
/* Copy the message into the exception before freeing the parser:
|
|
290
|
+
* ucl_parser_get_error() points into memory owned by the parser. */
|
|
291
|
+
VALUE err = rb_exc_new2(eUCLError, ucl_parser_get_error(parser));
|
|
292
|
+
ucl_parser_free(parser);
|
|
293
|
+
rb_exc_raise(err);
|
|
205
294
|
}
|
|
206
295
|
|
|
207
296
|
bool failed = false;
|
|
208
297
|
ucl_object_t *root = ucl_parser_get_object(parser);
|
|
209
298
|
VALUE res = _iterate_valid_ucl(root, FIX2INT(flags), &failed);
|
|
210
299
|
|
|
211
|
-
|
|
212
|
-
if (root
|
|
213
|
-
|
|
300
|
+
ucl_parser_free(parser);
|
|
301
|
+
if (root != NULL) { ucl_object_unref(root); }
|
|
302
|
+
|
|
214
303
|
if (failed) {
|
|
215
304
|
rb_raise(eUCLError, "failed to iterate over ucl object");
|
|
216
305
|
}
|
data/test/test_ucl.rb
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
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
|
+
# ---- duplicate keys become an explicit array ----------------------------
|
|
133
|
+
|
|
134
|
+
def test_duplicate_keys_make_array
|
|
135
|
+
assert_equal({ 'k' => [1, 2, 3] }, UCL.parse("k = 1\nk = 2\nk = 3"))
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def test_single_key_stays_scalar
|
|
139
|
+
assert_equal({ 'k' => 1 }, UCL.parse('k = 1'))
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# ---- empty objects (regression: used to raise) --------------------------
|
|
143
|
+
|
|
144
|
+
def test_empty_input_returns_empty_hash
|
|
145
|
+
assert_equal({}, UCL.parse(''))
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def test_whitespace_only_returns_empty_hash
|
|
149
|
+
assert_equal({}, UCL.parse(" \n "))
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def test_comment_only_returns_empty_hash
|
|
153
|
+
assert_equal({}, UCL.parse("# nothing here\n"))
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def test_empty_object
|
|
157
|
+
assert_equal({ 'e' => {} }, UCL.parse('e {}'))
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def test_nested_empty_object
|
|
161
|
+
assert_equal({ 'a' => { 'b' => {} } }, UCL.parse('a { b {} }'))
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def test_empty_object_then_more_keys
|
|
165
|
+
assert_equal({ 'a' => {}, 'b' => 2 }, UCL.parse("a {}\nb = 2"))
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def test_empty_array
|
|
169
|
+
assert_equal({ 'a' => [] }, UCL.parse('a = []'))
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# ---- time ---------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
def test_time_is_parsed_to_float
|
|
175
|
+
assert_equal({ 't' => 10.0 }, UCL.parse('t = 10s'))
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def test_no_time_keeps_string
|
|
179
|
+
assert_equal({ 't' => '10s' }, UCL.parse('t = 10s', UCL::NO_TIME))
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# ---- key flags ----------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
def test_key_symbol
|
|
185
|
+
assert_equal({ name: 'value' }, UCL.parse('name = value', UCL::KEY_SYMBOL))
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def test_key_symbol_recurses_into_nested_objects
|
|
189
|
+
assert_equal({ o: { x: 1 } }, UCL.parse('o { x = 1 }', UCL::KEY_SYMBOL))
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def test_key_lowercase
|
|
193
|
+
assert_equal({ 'foo' => 1 }, UCL.parse('FOO = 1', UCL::KEY_LOWERCASE))
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def test_combined_flags
|
|
197
|
+
assert_equal({ foo: 1 },
|
|
198
|
+
UCL.parse('FOO = 1', UCL::KEY_SYMBOL | UCL::KEY_LOWERCASE))
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# ---- macros -------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
def test_disable_macro_does_not_process_include
|
|
204
|
+
# DISABLE_MACRO stops macros from being executed, so the referenced file
|
|
205
|
+
# is never read. libucl versions differ in how they treat the macro line:
|
|
206
|
+
# older ones ignore it (parsing only the remaining keys), newer ones reject
|
|
207
|
+
# it as an invalid key. Both outcomes are acceptable here.
|
|
208
|
+
config = %(.include "/no/such/file"\nx = 1)
|
|
209
|
+
begin
|
|
210
|
+
assert_equal({ 'x' => 1 }, UCL.parse(config, UCL::DISABLE_MACRO))
|
|
211
|
+
rescue UCL::Error => e
|
|
212
|
+
refute_empty e.message
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def test_include_of_missing_file_raises_without_flag
|
|
217
|
+
assert_raises(UCL::Error) { UCL.parse(%(.include "/no/such"\nx = 1)) }
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# ---- file variables -----------------------------------------------------
|
|
221
|
+
|
|
222
|
+
def test_no_filevars_leaves_variable_unexpanded
|
|
223
|
+
assert_equal({ 'd' => '$CURDIR' },
|
|
224
|
+
UCL.parse('d = $CURDIR', UCL::NO_FILEVARS))
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def test_parse_expands_curdir_by_default
|
|
228
|
+
refute_equal '$CURDIR', UCL.parse('d = $CURDIR')['d']
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# ---- default flags ------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
def test_flags_default_to_zero
|
|
234
|
+
assert_equal 0, UCL.flags
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def test_flags_are_used_as_default
|
|
238
|
+
UCL.flags = UCL::KEY_SYMBOL
|
|
239
|
+
assert_equal({ name: 'value' }, UCL.parse('name = value'))
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def test_explicit_flags_override_default
|
|
243
|
+
UCL.flags = UCL::KEY_SYMBOL
|
|
244
|
+
assert_equal({ 'name' => 'value' }, UCL.parse('name = value', 0))
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# ---- result object ------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
def test_result_is_mutable
|
|
250
|
+
result = UCL.parse('a = 1')
|
|
251
|
+
result['b'] = 2
|
|
252
|
+
assert_equal({ 'a' => 1, 'b' => 2 }, result)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# ---- load_file ----------------------------------------------------------
|
|
256
|
+
|
|
257
|
+
def test_load_file
|
|
258
|
+
with_conf("list = [1, 2, 3]\nname = test\n") do |path|
|
|
259
|
+
assert_equal({ 'list' => [1, 2, 3], 'name' => 'test' },
|
|
260
|
+
UCL.load_file(path))
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def test_load_file_sets_filename_variable
|
|
265
|
+
with_conf('f = "$FILENAME"' + "\n") do |path|
|
|
266
|
+
assert_equal({ 'f' => File.realpath(path) }, UCL.load_file(path))
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def test_load_file_with_flags
|
|
271
|
+
with_conf("Name = value\n") do |path|
|
|
272
|
+
assert_equal({ name: 'value' },
|
|
273
|
+
UCL.load_file(path, UCL::KEY_SYMBOL | UCL::KEY_LOWERCASE))
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def test_load_missing_file_raises
|
|
278
|
+
assert_raises(UCL::Error) { UCL.load_file('/no/such/file.conf') }
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# ---- errors -------------------------------------------------------------
|
|
282
|
+
|
|
283
|
+
def test_malformed_raises_ucl_error
|
|
284
|
+
assert_raises(UCL::Error) { UCL.parse('a = [') }
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def test_error_message_is_present
|
|
288
|
+
err = assert_raises(UCL::Error) { UCL.parse('a = [') }
|
|
289
|
+
refute_empty err.message
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def test_error_is_a_standard_error
|
|
293
|
+
assert_operator UCL::Error, :<, StandardError
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def test_repeated_malformed_parses_do_not_crash
|
|
297
|
+
100.times do
|
|
298
|
+
assert_raises(UCL::Error) { UCL.parse('} bad {') }
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def test_parse_rejects_non_string
|
|
303
|
+
assert_raises(TypeError) { UCL.parse(42) }
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# ---- constants ----------------------------------------------------------
|
|
307
|
+
|
|
308
|
+
def test_constants_defined
|
|
309
|
+
%i[KEY_SYMBOL KEY_LOWERCASE NO_TIME DISABLE_MACRO NO_FILEVARS].each do |c|
|
|
310
|
+
assert UCL.const_defined?(c), "UCL::#{c} should be defined"
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
private
|
|
315
|
+
|
|
316
|
+
def with_conf(content)
|
|
317
|
+
Tempfile.create(['ucl', '.conf']) do |f|
|
|
318
|
+
f.write(content)
|
|
319
|
+
f.flush
|
|
320
|
+
yield f.path
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
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.
|
|
4
|
-
s.summary =
|
|
3
|
+
s.version = '0.1.4'
|
|
4
|
+
s.summary = 'Universal Configuration Language (UCL) parser'
|
|
5
5
|
s.description = <<~EOF
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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'
|
|
@@ -15,5 +15,15 @@ Gem::Specification.new do |s|
|
|
|
15
15
|
s.email = [ 'sdalu@sdalu.com' ]
|
|
16
16
|
|
|
17
17
|
s.extensions = [ 'ext/extconf.rb' ]
|
|
18
|
-
s.files = %w[ ucl.gemspec ]
|
|
18
|
+
s.files = %w[ ucl.gemspec README.md LICENSE ] +
|
|
19
|
+
Dir['ext/**/*.{c,h,rb}'] +
|
|
20
|
+
Dir['test/**/*.rb']
|
|
21
|
+
|
|
22
|
+
# Used at build time to download and compile libucl from source when no
|
|
23
|
+
# system-wide installation is found (requires cmake and a C compiler).
|
|
24
|
+
s.add_dependency 'mini_portile2', '~> 2.8'
|
|
25
|
+
|
|
26
|
+
s.add_development_dependency 'rake'
|
|
27
|
+
s.add_development_dependency 'minitest', '~> 5.0'
|
|
28
|
+
s.add_development_dependency 'yard'
|
|
19
29
|
end
|
metadata
CHANGED
|
@@ -1,19 +1,74 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ucl
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.4
|
|
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:
|
|
12
|
-
dependencies:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
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
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rake
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: minitest
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '5.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '5.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: yard
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
description: |
|
|
69
|
+
Parse configuration files written in the Universal Configuration
|
|
70
|
+
Language (UCL), a human-friendly JSON superset. Native bindings to
|
|
71
|
+
the libucl library; results are returned as plain Ruby objects.
|
|
17
72
|
email:
|
|
18
73
|
- sdalu@sdalu.com
|
|
19
74
|
executables: []
|
|
@@ -21,14 +76,16 @@ extensions:
|
|
|
21
76
|
- ext/extconf.rb
|
|
22
77
|
extra_rdoc_files: []
|
|
23
78
|
files:
|
|
79
|
+
- LICENSE
|
|
80
|
+
- README.md
|
|
24
81
|
- ext/extconf.rb
|
|
25
82
|
- ext/ucl.c
|
|
83
|
+
- test/test_ucl.rb
|
|
26
84
|
- ucl.gemspec
|
|
27
85
|
homepage: https://github.com/sdalu/ruby-ucl
|
|
28
86
|
licenses:
|
|
29
87
|
- MIT
|
|
30
88
|
metadata: {}
|
|
31
|
-
post_install_message:
|
|
32
89
|
rdoc_options: []
|
|
33
90
|
require_paths:
|
|
34
91
|
- lib
|
|
@@ -43,9 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
43
100
|
- !ruby/object:Gem::Version
|
|
44
101
|
version: '0'
|
|
45
102
|
requirements: []
|
|
46
|
-
rubygems_version:
|
|
47
|
-
signing_key:
|
|
103
|
+
rubygems_version: 4.0.9
|
|
48
104
|
specification_version: 4
|
|
49
|
-
summary: Universal
|
|
105
|
+
summary: Universal Configuration Language (UCL) parser
|
|
50
106
|
test_files: []
|
|
51
|
-
...
|