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.
- checksums.yaml +4 -4
- data/LICENSE +21 -0
- data/LICENSE-DEPENDENCIES.md +161 -0
- data/README.md +270 -0
- data/ext/extconf.rb +67 -3
- 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 +398 -90
- data/test/test_ucl.rb +452 -0
- data/ucl.gemspec +30 -7
- metadata +77 -14
data/ext/ucl.c
CHANGED
|
@@ -2,22 +2,77 @@
|
|
|
2
2
|
#include <ruby/io.h>
|
|
3
3
|
#include <ucl.h>
|
|
4
4
|
#include <stdio.h>
|
|
5
|
+
#include <stdlib.h>
|
|
5
6
|
#include <stdbool.h>
|
|
6
7
|
|
|
7
8
|
/* Fake flag */
|
|
8
9
|
#define UCL_PARSER_KEY_SYMBOL (1 << 12)
|
|
9
10
|
|
|
11
|
+
/* Deepest object nesting accepted when converting a parsed tree to Ruby
|
|
12
|
+
* objects. Both the conversion below and libucl's own tree destructor walk
|
|
13
|
+
* children recursively, with nothing but the C stack bounding them: a 1 MiB
|
|
14
|
+
* thread stack is gone at roughly 15000 levels. Real configurations nest a
|
|
15
|
+
* handful of levels (Ruby's own JSON parser stops at 100), so anything
|
|
16
|
+
* deeper is refused with UCL::Error instead of being left to overflow the
|
|
17
|
+
* stack. */
|
|
18
|
+
#define UCL_MAX_NESTING 1000
|
|
19
|
+
|
|
10
20
|
|
|
11
21
|
/**
|
|
12
22
|
* Document-class: UCL
|
|
13
23
|
*
|
|
14
|
-
*
|
|
24
|
+
* Parser for configuration files written in the Universal Configuration
|
|
25
|
+
* Language (UCL), a JSON-superset format handled by the libucl library.
|
|
26
|
+
*
|
|
27
|
+
* Parsed configurations are returned as plain Ruby objects (Hash, Array,
|
|
28
|
+
* String, Integer, Float, true/false, nil).
|
|
29
|
+
*
|
|
30
|
+
* Objects nested more than 1000 levels deep are rejected with {UCL::Error};
|
|
31
|
+
* see {UCL.parse}. Input that is both that deeply nested *and* malformed is
|
|
32
|
+
* the one case this cannot cover: libucl releases its half-built tree
|
|
33
|
+
* recursively, out of reach here, and a SystemStackError comes out instead.
|
|
34
|
+
*
|
|
35
|
+
* @example Parse a string
|
|
36
|
+
* UCL.parse('name = value') #=> { "name" => "value" }
|
|
37
|
+
*
|
|
38
|
+
* @example Load a file with symbol keys
|
|
39
|
+
* UCL.load_file('foo.conf', UCL::KEY_SYMBOL)
|
|
40
|
+
*
|
|
41
|
+
* @see https://github.com/vstakhov/libucl
|
|
15
42
|
*/
|
|
16
43
|
|
|
17
44
|
/**
|
|
18
45
|
* Document-class: UCL::Error
|
|
19
46
|
*
|
|
20
|
-
*
|
|
47
|
+
* Raised when a configuration cannot be parsed, or when the parsed tree
|
|
48
|
+
* cannot be converted to Ruby objects.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Document-const: KEY_LOWERCASE
|
|
53
|
+
* Flag: convert all object keys to lower case.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Document-const: NO_TIME
|
|
58
|
+
* Flag: do not parse time values; keep them as strings.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Document-const: DISABLE_MACRO
|
|
63
|
+
* Flag: disable processing of macros (e.g. <code>.include</code>).
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Document-const: NO_FILEVARS
|
|
68
|
+
* Flag: do not predefine the file variables (<code>$FILENAME</code>,
|
|
69
|
+
* <code>$CURDIR</code>). This affects {UCL.parse}; {UCL.load_file} still
|
|
70
|
+
* derives those variables from the file being loaded.
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Document-const: KEY_SYMBOL
|
|
75
|
+
* Flag: return object keys as Symbol instead of String.
|
|
21
76
|
*/
|
|
22
77
|
|
|
23
78
|
|
|
@@ -31,86 +86,303 @@ static int ucl_allowed_c_flags = UCL_PARSER_KEY_LOWERCASE |
|
|
|
31
86
|
UCL_PARSER_NO_FILEVARS ;
|
|
32
87
|
|
|
33
88
|
|
|
89
|
+
/* State threaded through a conversion.
|
|
90
|
+
*
|
|
91
|
+
* `iters` points at an array of UCL_MAX_NESTING slots living in the frame
|
|
92
|
+
* that started the conversion: each level parks its iterator there, so an
|
|
93
|
+
* exception unwinding the C stack does not strand them. `max_depth` is the
|
|
94
|
+
* deepest slot ever written; every shallower slot has been written too (a
|
|
95
|
+
* container at depth d sits inside a container at every depth above it),
|
|
96
|
+
* which is what lets the error path sweep exactly iters[0..max_depth]
|
|
97
|
+
* without having to clear the array up front. */
|
|
98
|
+
struct ucl_conv {
|
|
99
|
+
int flags;
|
|
100
|
+
bool failed;
|
|
101
|
+
int max_depth;
|
|
102
|
+
ucl_object_iter_t *iters;
|
|
103
|
+
};
|
|
104
|
+
|
|
34
105
|
|
|
35
|
-
VALUE
|
|
36
|
-
_iterate_valid_ucl(ucl_object_t const *root, int
|
|
106
|
+
static VALUE
|
|
107
|
+
_iterate_valid_ucl(struct ucl_conv *cv, ucl_object_t const *root, int depth)
|
|
37
108
|
{
|
|
38
|
-
ucl_object_iter_t it =
|
|
109
|
+
ucl_object_iter_t it = NULL; /* only allocated for objects/arrays */
|
|
39
110
|
const ucl_object_t *obj = NULL;
|
|
40
111
|
|
|
41
|
-
VALUE val;
|
|
112
|
+
VALUE val = Qnil;
|
|
113
|
+
|
|
114
|
+
/* Bound the recursion (see UCL_MAX_NESTING). Checked before this level
|
|
115
|
+
* allocates its iterator, so the raise cannot strand one. */
|
|
116
|
+
if (depth >= UCL_MAX_NESTING)
|
|
117
|
+
rb_raise(eUCLError, "nesting deeper than %d levels", UCL_MAX_NESTING);
|
|
42
118
|
|
|
43
119
|
switch (root->type) {
|
|
44
120
|
case UCL_INT:
|
|
45
121
|
val = rb_ll2inum((long long)ucl_object_toint(root));
|
|
46
122
|
break;
|
|
47
|
-
|
|
123
|
+
|
|
48
124
|
case UCL_FLOAT:
|
|
49
125
|
val = rb_float_new(ucl_object_todouble(root));
|
|
50
126
|
break;
|
|
51
|
-
|
|
127
|
+
|
|
52
128
|
case UCL_STRING: {
|
|
53
129
|
size_t len;
|
|
54
130
|
const char *str = ucl_object_tolstring(root, &len);
|
|
55
131
|
val = rb_str_new(str, len);
|
|
56
132
|
break;
|
|
57
133
|
}
|
|
58
|
-
|
|
134
|
+
|
|
59
135
|
case UCL_BOOLEAN:
|
|
60
136
|
val = ucl_object_toboolean(root) ? Qtrue : Qfalse;
|
|
61
137
|
break;
|
|
62
|
-
|
|
138
|
+
|
|
63
139
|
case UCL_TIME:
|
|
64
140
|
val = rb_float_new(ucl_object_todouble(root));
|
|
65
141
|
break;
|
|
66
|
-
|
|
67
|
-
case UCL_OBJECT:
|
|
142
|
+
|
|
143
|
+
case UCL_OBJECT: {
|
|
144
|
+
bool iterated = false;
|
|
68
145
|
val = rb_hash_new();
|
|
69
|
-
it =
|
|
146
|
+
it = ucl_object_iterate_new(root);
|
|
147
|
+
if (it == NULL)
|
|
148
|
+
rb_raise(eUCLError, "failed to allocate UCL iterator");
|
|
149
|
+
cv->iters[depth] = it;
|
|
150
|
+
if (depth > cv->max_depth) cv->max_depth = depth;
|
|
70
151
|
while ((obj = ucl_object_iterate_safe(it, !true))) {
|
|
152
|
+
iterated = true;
|
|
71
153
|
size_t keylen;
|
|
72
154
|
const char *key = ucl_object_keyl(obj, &keylen);
|
|
73
155
|
VALUE v_key = rb_str_new(key, keylen);
|
|
74
|
-
if (flags & UCL_PARSER_KEY_SYMBOL)
|
|
156
|
+
if (cv->flags & UCL_PARSER_KEY_SYMBOL)
|
|
75
157
|
v_key = rb_to_symbol(v_key);
|
|
76
|
-
rb_hash_aset(val, v_key, _iterate_valid_ucl(
|
|
158
|
+
rb_hash_aset(val, v_key, _iterate_valid_ucl(cv, obj, depth + 1));
|
|
77
159
|
}
|
|
78
|
-
|
|
160
|
+
/* An empty object has a NULL hash that the safe iterator reports as an
|
|
161
|
+
* exception (EINVAL); ignore that and only flag a genuine error that
|
|
162
|
+
* occurs while iterating. Accumulate so a nested failure deeper in the
|
|
163
|
+
* tree is never cleared by a successful parent iteration. */
|
|
164
|
+
if (iterated && ucl_object_iter_chk_excpn(it)) cv->failed = true;
|
|
79
165
|
break;
|
|
80
|
-
|
|
81
|
-
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
case UCL_ARRAY: {
|
|
169
|
+
bool iterated = false;
|
|
82
170
|
val = rb_ary_new();
|
|
83
|
-
it
|
|
171
|
+
it = ucl_object_iterate_new(root);
|
|
172
|
+
if (it == NULL)
|
|
173
|
+
rb_raise(eUCLError, "failed to allocate UCL iterator");
|
|
174
|
+
cv->iters[depth] = it;
|
|
175
|
+
if (depth > cv->max_depth) cv->max_depth = depth;
|
|
84
176
|
while ((obj = ucl_object_iterate_safe(it, !true))) {
|
|
85
|
-
|
|
177
|
+
iterated = true;
|
|
178
|
+
rb_ary_push(val, _iterate_valid_ucl(cv, obj, depth + 1));
|
|
86
179
|
}
|
|
87
|
-
|
|
180
|
+
if (iterated && ucl_object_iter_chk_excpn(it)) cv->failed = true;
|
|
88
181
|
break;
|
|
89
|
-
|
|
182
|
+
}
|
|
183
|
+
|
|
90
184
|
case UCL_USERDATA:
|
|
91
185
|
val = rb_str_new(root->value.sv, root->len);
|
|
92
186
|
break;
|
|
93
|
-
|
|
187
|
+
|
|
94
188
|
case UCL_NULL:
|
|
95
189
|
val = Qnil;
|
|
96
190
|
break;
|
|
97
|
-
|
|
191
|
+
|
|
98
192
|
default:
|
|
99
|
-
|
|
100
|
-
|
|
193
|
+
rb_raise(eUCLError, "unhandled UCL type (%d)", root->type);
|
|
194
|
+
|
|
101
195
|
}
|
|
102
196
|
|
|
103
|
-
|
|
197
|
+
if (it != NULL) {
|
|
198
|
+
ucl_object_iterate_free(it);
|
|
199
|
+
cv->iters[depth] = NULL;
|
|
200
|
+
}
|
|
104
201
|
return val;
|
|
105
202
|
}
|
|
106
203
|
|
|
204
|
+
|
|
205
|
+
/* Remove one child from `top` and hand it back with its reference
|
|
206
|
+
* transferred to the caller; NULL once `top` holds no child any more. */
|
|
207
|
+
static ucl_object_t *
|
|
208
|
+
_ucl_detach_child(ucl_object_t *top)
|
|
209
|
+
{
|
|
210
|
+
if (top->type == UCL_ARRAY)
|
|
211
|
+
return ucl_array_pop_last(top);
|
|
212
|
+
|
|
213
|
+
if (top->type == UCL_OBJECT) {
|
|
214
|
+
ucl_object_iter_t it = ucl_object_iterate_new(top);
|
|
215
|
+
const ucl_object_t *first = (it == NULL) ? NULL
|
|
216
|
+
: ucl_object_iterate_safe(it, !true);
|
|
217
|
+
const char *key = NULL;
|
|
218
|
+
size_t keylen = 0;
|
|
219
|
+
|
|
220
|
+
if (first != NULL) key = ucl_object_keyl(first, &keylen);
|
|
221
|
+
if (it != NULL) ucl_object_iterate_free(it);
|
|
222
|
+
if (key == NULL) return NULL;
|
|
223
|
+
/* `key` points into `first`, which `top` still holds a reference to
|
|
224
|
+
* until the pop below hands it over. The iterator is released first
|
|
225
|
+
* because the pop mutates the object it was iterating. */
|
|
226
|
+
return ucl_object_pop_keyl(top, key, keylen);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return NULL;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
/* Append `obj` to the worklist, growing it as needed; false only when the
|
|
234
|
+
* allocation fails. */
|
|
235
|
+
static bool
|
|
236
|
+
_ucl_worklist_push(ucl_object_t ***work, size_t *len, size_t *cap,
|
|
237
|
+
ucl_object_t *obj)
|
|
238
|
+
{
|
|
239
|
+
if (*len == *cap) {
|
|
240
|
+
size_t ncap = (*cap == 0) ? 64 : *cap * 2;
|
|
241
|
+
ucl_object_t **narr = realloc(*work, ncap * sizeof(*narr));
|
|
242
|
+
if (narr == NULL) return false;
|
|
243
|
+
*work = narr;
|
|
244
|
+
*cap = ncap;
|
|
245
|
+
}
|
|
246
|
+
(*work)[(*len)++] = obj;
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
/* Destroy everything below `root`, leaving `root` itself alive but empty,
|
|
252
|
+
* without recursing.
|
|
253
|
+
*
|
|
254
|
+
* libucl's destructor walks children recursively, so releasing a tree
|
|
255
|
+
* deeper than the C stack allows overflows it -- and that is exactly the
|
|
256
|
+
* tree UCL_MAX_NESTING refuses to convert, which would otherwise turn a
|
|
257
|
+
* clean UCL::Error into a stack overflow inside libucl. Children are
|
|
258
|
+
* detached onto a heap worklist and released once they are childless, so
|
|
259
|
+
* only the worklist grows with the tree. Once this returns, releasing
|
|
260
|
+
* `root` is O(1) and safe. */
|
|
261
|
+
static void
|
|
262
|
+
ucl_tree_dismantle(ucl_object_t *root)
|
|
263
|
+
{
|
|
264
|
+
ucl_object_t **work = NULL;
|
|
265
|
+
size_t len = 0;
|
|
266
|
+
size_t cap = 0;
|
|
267
|
+
ucl_object_t *cur = root;
|
|
268
|
+
|
|
269
|
+
if (root == NULL) return;
|
|
270
|
+
|
|
271
|
+
for (;;) {
|
|
272
|
+
ucl_object_t *child;
|
|
273
|
+
|
|
274
|
+
while ((child = _ucl_detach_child(cur)) != NULL) {
|
|
275
|
+
if (!_ucl_worklist_push(&work, &len, &cap, child)) {
|
|
276
|
+
/* Out of memory: fall back to the recursive release. */
|
|
277
|
+
ucl_object_unref(child);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (cur != root) ucl_object_unref(cur); /* childless now: O(1) */
|
|
282
|
+
if (len == 0) break;
|
|
283
|
+
cur = work[--len];
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
free(work);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
struct ucl_conv_args {
|
|
291
|
+
struct ucl_conv *cv;
|
|
292
|
+
const ucl_object_t *root;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
static VALUE
|
|
296
|
+
_ucl_convert_root(VALUE arg)
|
|
297
|
+
{
|
|
298
|
+
struct ucl_conv_args *a = (struct ucl_conv_args *)arg;
|
|
299
|
+
return _iterate_valid_ucl(a->cv, a->root, 0);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
/* Convert the tree held by `parser` into Ruby objects.
|
|
304
|
+
*
|
|
305
|
+
* Takes ownership of `parser`: it is released on every path out, including
|
|
306
|
+
* the ones where the conversion is unwound by an exception -- letting one
|
|
307
|
+
* escape would leak the parser and the whole tree it holds. */
|
|
308
|
+
static VALUE
|
|
309
|
+
ucl_parser_result(struct ucl_parser *parser, int flags)
|
|
310
|
+
{
|
|
311
|
+
ucl_object_iter_t iters[UCL_MAX_NESTING];
|
|
312
|
+
struct ucl_conv cv = { .flags = flags,
|
|
313
|
+
.failed = false,
|
|
314
|
+
.max_depth = -1,
|
|
315
|
+
.iters = iters };
|
|
316
|
+
ucl_object_t *root;
|
|
317
|
+
VALUE res;
|
|
318
|
+
int state = 0;
|
|
319
|
+
|
|
320
|
+
if (ucl_parser_get_error(parser)) {
|
|
321
|
+
/* Copy the message into the exception before freeing the parser:
|
|
322
|
+
* ucl_parser_get_error() points into memory owned by the parser. */
|
|
323
|
+
VALUE err = rb_exc_new2(eUCLError, ucl_parser_get_error(parser));
|
|
324
|
+
ucl_parser_free(parser);
|
|
325
|
+
rb_exc_raise(err);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
root = ucl_parser_get_object(parser);
|
|
329
|
+
if (root == NULL) {
|
|
330
|
+
ucl_parser_free(parser);
|
|
331
|
+
rb_raise(eUCLError, "parser produced no object");
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
struct ucl_conv_args args = { &cv, root };
|
|
335
|
+
res = rb_protect(_ucl_convert_root, (VALUE)&args, &state);
|
|
336
|
+
|
|
337
|
+
if (state != 0) {
|
|
338
|
+
/* Unwound by an exception: release the iterators the conversion was
|
|
339
|
+
* still holding, then take the tree apart iteratively, since it may
|
|
340
|
+
* be deeper than libucl's recursive destructor can walk. */
|
|
341
|
+
int d;
|
|
342
|
+
for (d = 0; d <= cv.max_depth; d++)
|
|
343
|
+
if (iters[d] != NULL) ucl_object_iterate_free(iters[d]);
|
|
344
|
+
ucl_tree_dismantle(root);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
ucl_parser_free(parser);
|
|
348
|
+
ucl_object_unref(root);
|
|
349
|
+
|
|
350
|
+
if (state != 0) rb_jump_tag(state);
|
|
351
|
+
if (cv.failed) rb_raise(eUCLError, "failed to iterate over ucl object");
|
|
352
|
+
|
|
353
|
+
return res;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Default flags applied by {UCL.parse} and {UCL.load_file} when none are
|
|
359
|
+
* given explicitly.
|
|
360
|
+
*
|
|
361
|
+
* @return [Integer] the current default flags (0 by default)
|
|
362
|
+
*/
|
|
107
363
|
static VALUE
|
|
108
364
|
ucl_s_get_flags(VALUE klass)
|
|
109
365
|
{
|
|
110
|
-
|
|
366
|
+
/* @flags is a plain instance variable and so is not inherited: a
|
|
367
|
+
* subclass that never set its own falls back to UCL's, which is the
|
|
368
|
+
* value its parse/load_file would have used anyway. */
|
|
369
|
+
VALUE flags = rb_attr_get(klass, rb_intern("@flags"));
|
|
370
|
+
if (NIL_P(flags)) flags = rb_attr_get(mUCL, rb_intern("@flags"));
|
|
371
|
+
return flags;
|
|
111
372
|
}
|
|
112
373
|
|
|
113
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Set the default flags applied by {UCL.parse} and {UCL.load_file} when
|
|
377
|
+
* none are given explicitly.
|
|
378
|
+
*
|
|
379
|
+
* @param val [Integer] flags, combined with a bitwise OR
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* UCL.flags = UCL::KEY_SYMBOL | UCL::KEY_LOWERCASE
|
|
383
|
+
*
|
|
384
|
+
* @return [Integer] the flags that were set
|
|
385
|
+
*/
|
|
114
386
|
static VALUE
|
|
115
387
|
ucl_s_set_flags(VALUE klass, VALUE val)
|
|
116
388
|
{
|
|
@@ -120,109 +392,146 @@ ucl_s_set_flags(VALUE klass, VALUE val)
|
|
|
120
392
|
}
|
|
121
393
|
|
|
122
394
|
|
|
123
|
-
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* @param data [String]
|
|
127
|
-
* @param flags [Integer]
|
|
128
|
-
*
|
|
129
|
-
* @return configuration file as ruby objects.
|
|
130
|
-
*/
|
|
395
|
+
/* Shared body of UCL.parse and UCL.safe_parse; `forced_flags` are the ones
|
|
396
|
+
* the entry point imposes whatever the caller asked for. */
|
|
131
397
|
static VALUE
|
|
132
|
-
|
|
398
|
+
ucl_parse_string(int argc, VALUE *argv, VALUE klass, int forced_flags)
|
|
133
399
|
{
|
|
134
400
|
VALUE data, flags;
|
|
135
401
|
rb_scan_args(argc, argv, "11", &data, &flags);
|
|
136
|
-
if (NIL_P(flags)) flags = ucl_s_get_flags(
|
|
402
|
+
if (NIL_P(flags)) flags = ucl_s_get_flags(klass);
|
|
137
403
|
|
|
138
404
|
rb_check_type(data, T_STRING);
|
|
139
405
|
rb_check_type(flags, T_FIXNUM);
|
|
140
|
-
|
|
141
|
-
int
|
|
406
|
+
|
|
407
|
+
int r_flags = FIX2INT(flags) | forced_flags;
|
|
408
|
+
int c_flags = r_flags & ucl_allowed_c_flags;
|
|
142
409
|
|
|
143
410
|
struct ucl_parser *parser =
|
|
144
411
|
ucl_parser_new(c_flags | UCL_PARSER_NO_IMPLICIT_ARRAYS);
|
|
412
|
+
if (parser == NULL)
|
|
413
|
+
rb_raise(eUCLError, "failed to allocate UCL parser");
|
|
145
414
|
|
|
146
415
|
ucl_parser_add_chunk(parser,
|
|
147
416
|
(unsigned char *)RSTRING_PTR(data),
|
|
148
417
|
RSTRING_LEN(data));
|
|
149
|
-
|
|
150
|
-
if (ucl_parser_get_error(parser)) {
|
|
151
|
-
const char *errormsg = ucl_parser_get_error(parser);
|
|
152
|
-
if (parser != NULL) { ucl_parser_free(parser); }
|
|
153
|
-
rb_raise(eUCLError, "%s", errormsg);
|
|
154
|
-
}
|
|
155
418
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
VALUE res = _iterate_valid_ucl(root, FIX2INT(flags), &failed);
|
|
419
|
+
return ucl_parser_result(parser, r_flags);
|
|
420
|
+
}
|
|
159
421
|
|
|
160
|
-
if (parser != NULL) { ucl_parser_free(parser); }
|
|
161
|
-
if (root != NULL) { ucl_object_unref(root); }
|
|
162
422
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
423
|
+
/**
|
|
424
|
+
* Parse a UCL configuration from a string.
|
|
425
|
+
*
|
|
426
|
+
* Macros are processed, so a configuration is able to pull in other files
|
|
427
|
+
* through <code>.include</code>. Use {UCL.safe_parse} (or the
|
|
428
|
+
* {UCL::DISABLE_MACRO} flag) for input that is not trusted.
|
|
429
|
+
*
|
|
430
|
+
* Objects nested more than 1000 levels deep are refused: both this
|
|
431
|
+
* conversion and libucl's own tree handling recurse per level, so a deeper
|
|
432
|
+
* tree would exhaust the C stack.
|
|
433
|
+
*
|
|
434
|
+
* @overload parse(data, flags = UCL.flags)
|
|
435
|
+
* @param data [String] the UCL configuration to parse
|
|
436
|
+
* @param flags [Integer] parsing flags combined with a bitwise OR;
|
|
437
|
+
* defaults to {UCL.flags} when omitted
|
|
438
|
+
*
|
|
439
|
+
* @example
|
|
440
|
+
* UCL.parse('name = value') #=> { "name" => "value" }
|
|
441
|
+
* UCL.parse('name = value', UCL::KEY_SYMBOL) #=> { :name => "value" }
|
|
442
|
+
*
|
|
443
|
+
* @raise [UCL::Error] if the configuration is malformed, or nested too deeply
|
|
444
|
+
*
|
|
445
|
+
* @return [Hash, Array, Object] the configuration as Ruby objects
|
|
446
|
+
*/
|
|
447
|
+
static VALUE
|
|
448
|
+
ucl_s_parse(int argc, VALUE *argv, VALUE klass)
|
|
449
|
+
{
|
|
450
|
+
return ucl_parse_string(argc, argv, klass, 0);
|
|
451
|
+
}
|
|
166
452
|
|
|
167
|
-
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Parse a UCL configuration from a string with macros disabled.
|
|
456
|
+
*
|
|
457
|
+
* Identical to {UCL.parse} but always adds {UCL::DISABLE_MACRO}, so the
|
|
458
|
+
* configuration cannot reach outside itself through <code>.include</code>.
|
|
459
|
+
* This is the entry point to use for input from an untrusted source.
|
|
460
|
+
*
|
|
461
|
+
* @overload safe_parse(data, flags = UCL.flags)
|
|
462
|
+
* @param data [String] the UCL configuration to parse
|
|
463
|
+
* @param flags [Integer] parsing flags combined with a bitwise OR;
|
|
464
|
+
* defaults to {UCL.flags} when omitted; {UCL::DISABLE_MACRO} is added
|
|
465
|
+
* to whatever is given
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* UCL.safe_parse('.include "/etc/passwd"') #=> raises UCL::Error
|
|
469
|
+
*
|
|
470
|
+
* @raise [UCL::Error] if the configuration is malformed, or nested too deeply
|
|
471
|
+
*
|
|
472
|
+
* @return [Hash, Array, Object] the configuration as Ruby objects
|
|
473
|
+
*/
|
|
474
|
+
static VALUE
|
|
475
|
+
ucl_s_safe_parse(int argc, VALUE *argv, VALUE klass)
|
|
476
|
+
{
|
|
477
|
+
return ucl_parse_string(argc, argv, klass, UCL_PARSER_DISABLE_MACRO);
|
|
168
478
|
}
|
|
169
479
|
|
|
170
480
|
|
|
171
481
|
/**
|
|
172
|
-
* Load configuration file
|
|
482
|
+
* Load and parse a UCL configuration from a file.
|
|
483
|
+
*
|
|
484
|
+
* Unlike {UCL.parse}, this defines the file variables ($FILENAME,
|
|
485
|
+
* $CURDIR) from the loaded file, so they can be referenced from within
|
|
486
|
+
* the configuration.
|
|
487
|
+
*
|
|
488
|
+
* Macros are processed, and the nesting limit of {UCL.parse} applies here
|
|
489
|
+
* too.
|
|
173
490
|
*
|
|
174
|
-
* @
|
|
175
|
-
*
|
|
491
|
+
* @overload load_file(file, flags = UCL.flags)
|
|
492
|
+
* @param file [String] path to the configuration file
|
|
493
|
+
* @param flags [Integer] parsing flags combined with a bitwise OR;
|
|
494
|
+
* defaults to {UCL.flags} when omitted
|
|
176
495
|
*
|
|
177
496
|
* @example
|
|
178
497
|
* UCL.load_file('foo.conf', UCL::KEY_SYMBOL)
|
|
179
498
|
*
|
|
180
|
-
* @
|
|
499
|
+
* @raise [UCL::Error] if the file cannot be read, is malformed, or is
|
|
500
|
+
* nested too deeply
|
|
501
|
+
*
|
|
502
|
+
* @return [Hash, Array, Object] the configuration as Ruby objects
|
|
181
503
|
*/
|
|
182
504
|
static VALUE
|
|
183
505
|
ucl_s_load_file(int argc, VALUE *argv, VALUE klass)
|
|
184
506
|
{
|
|
185
507
|
VALUE file, flags;
|
|
186
508
|
rb_scan_args(argc, argv, "11", &file, &flags);
|
|
187
|
-
if (NIL_P(flags)) flags = ucl_s_get_flags(
|
|
509
|
+
if (NIL_P(flags)) flags = ucl_s_get_flags(klass);
|
|
188
510
|
|
|
189
511
|
rb_check_type(file, T_STRING);
|
|
190
512
|
rb_check_type(flags, T_FIXNUM);
|
|
191
|
-
|
|
192
|
-
int
|
|
513
|
+
|
|
514
|
+
int r_flags = FIX2INT(flags);
|
|
515
|
+
int c_flags = r_flags & ucl_allowed_c_flags;
|
|
193
516
|
char *c_file = StringValueCStr(file);
|
|
194
517
|
|
|
195
518
|
struct ucl_parser *parser =
|
|
196
519
|
ucl_parser_new(c_flags | UCL_PARSER_NO_IMPLICIT_ARRAYS);
|
|
520
|
+
if (parser == NULL)
|
|
521
|
+
rb_raise(eUCLError, "failed to allocate UCL parser");
|
|
197
522
|
|
|
198
|
-
|
|
523
|
+
/* Before the file is read, not after: the variables are substituted
|
|
524
|
+
* while parsing, so setting them afterwards would have no effect at
|
|
525
|
+
* all. (libucl sets them from the file as well.) */
|
|
199
526
|
ucl_parser_set_filevars(parser, c_file, false);
|
|
200
|
-
|
|
201
|
-
if (ucl_parser_get_error(parser)) {
|
|
202
|
-
const char *errormsg = ucl_parser_get_error(parser);
|
|
203
|
-
if (parser != NULL) { ucl_parser_free(parser); }
|
|
204
|
-
rb_raise(eUCLError, "%s", errormsg);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
bool failed = false;
|
|
208
|
-
ucl_object_t *root = ucl_parser_get_object(parser);
|
|
209
|
-
VALUE res = _iterate_valid_ucl(root, FIX2INT(flags), &failed);
|
|
210
|
-
|
|
211
|
-
if (parser != NULL) { ucl_parser_free(parser); }
|
|
212
|
-
if (root != NULL) { ucl_object_unref(root); }
|
|
213
|
-
|
|
214
|
-
if (failed) {
|
|
215
|
-
rb_raise(eUCLError, "failed to iterate over ucl object");
|
|
216
|
-
}
|
|
527
|
+
ucl_parser_add_file(parser, c_file);
|
|
217
528
|
|
|
218
|
-
return
|
|
529
|
+
return ucl_parser_result(parser, r_flags);
|
|
219
530
|
}
|
|
220
531
|
|
|
221
532
|
|
|
222
533
|
|
|
223
534
|
|
|
224
|
-
|
|
225
|
-
|
|
226
535
|
void Init_ucl(void) {
|
|
227
536
|
/* Main classes */
|
|
228
537
|
mUCL = rb_define_class("UCL", rb_cObject);
|
|
@@ -237,12 +546,11 @@ void Init_ucl(void) {
|
|
|
237
546
|
|
|
238
547
|
/* Variables */
|
|
239
548
|
ucl_s_set_flags(mUCL, INT2FIX(0));
|
|
240
|
-
|
|
549
|
+
|
|
241
550
|
/* Definitions */
|
|
242
|
-
rb_define_singleton_method(mUCL, "load_file",
|
|
243
|
-
rb_define_singleton_method(mUCL, "parse",
|
|
244
|
-
rb_define_singleton_method(mUCL, "
|
|
245
|
-
rb_define_singleton_method(mUCL, "flags
|
|
551
|
+
rb_define_singleton_method(mUCL, "load_file", ucl_s_load_file, -1);
|
|
552
|
+
rb_define_singleton_method(mUCL, "parse", ucl_s_parse, -1);
|
|
553
|
+
rb_define_singleton_method(mUCL, "safe_parse", ucl_s_safe_parse, -1);
|
|
554
|
+
rb_define_singleton_method(mUCL, "flags", ucl_s_get_flags, 0);
|
|
555
|
+
rb_define_singleton_method(mUCL, "flags=", ucl_s_set_flags, 1);
|
|
246
556
|
}
|
|
247
|
-
|
|
248
|
-
|