zuzu-js 0.1.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.
- package/LICENSE +5 -0
- package/README.md +113 -0
- package/bin/zuzu +17 -0
- package/bin/zuzu-build-browser-bundle +57 -0
- package/bin/zuzu-generate-browser-stdlib +584 -0
- package/bin/zuzu-js +23 -0
- package/bin/zuzu-js-compile +152 -0
- package/bin/zuzu-js-electron +19 -0
- package/dist/zuzu-browser-worker.js +45574 -0
- package/dist/zuzu-browser.js +45362 -0
- package/lib/browser-bundle-entry.js +160 -0
- package/lib/browser-gui-renderer.js +387 -0
- package/lib/browser-runtime.js +167 -0
- package/lib/browser-worker-entry.js +413 -0
- package/lib/browser-ztests/runner.html +103 -0
- package/lib/browser-ztests/runner.js +369 -0
- package/lib/cli.js +350 -0
- package/lib/collections.js +367 -0
- package/lib/compiler.js +303 -0
- package/lib/electron/launcher.js +70 -0
- package/lib/electron/main.js +956 -0
- package/lib/electron/preload.js +80 -0
- package/lib/electron/renderer.html +122 -0
- package/lib/electron/renderer.js +24 -0
- package/lib/execution-metadata.js +18 -0
- package/lib/gui/dom-renderer.js +778 -0
- package/lib/host/browser-host.js +278 -0
- package/lib/host/capabilities.js +47 -0
- package/lib/host/electron-host.js +15 -0
- package/lib/host/node-host.js +74 -0
- package/lib/paths.js +150 -0
- package/lib/runtime-entrypoints.js +60 -0
- package/lib/runtime-helpers.js +886 -0
- package/lib/runtime.js +3529 -0
- package/lib/tap.js +37 -0
- package/lib/transpiler-new/ast.js +23 -0
- package/lib/transpiler-new/codegen.js +2455 -0
- package/lib/transpiler-new/errors.js +28 -0
- package/lib/transpiler-new/index.js +26 -0
- package/lib/transpiler-new/lexer.js +834 -0
- package/lib/transpiler-new/parser.js +2332 -0
- package/lib/transpiler-new/validate-bindings.js +326 -0
- package/lib/transpiler-utils.js +95 -0
- package/lib/transpiler.js +33 -0
- package/lib/zuzu.js +53 -0
- package/modules/javascript.js +193 -0
- package/modules/std/archive.js +603 -0
- package/modules/std/clib.js +338 -0
- package/modules/std/data/csv.js +1331 -0
- package/modules/std/data/json.js +531 -0
- package/modules/std/data/xml.js +441 -0
- package/modules/std/data/yaml.js +256 -0
- package/modules/std/db-worker.js +250 -0
- package/modules/std/db.js +664 -0
- package/modules/std/digest/_hash.js +443 -0
- package/modules/std/digest/md5.js +26 -0
- package/modules/std/digest/sha.js +72 -0
- package/modules/std/eval.js +10 -0
- package/modules/std/gui/objects.js +1519 -0
- package/modules/std/internals.js +571 -0
- package/modules/std/io/socks-worker.js +318 -0
- package/modules/std/io/socks.js +186 -0
- package/modules/std/io.js +475 -0
- package/modules/std/marshal/cbor.js +463 -0
- package/modules/std/marshal/graph.js +1624 -0
- package/modules/std/marshal.js +87 -0
- package/modules/std/math/bignum.js +91 -0
- package/modules/std/math.js +79 -0
- package/modules/std/net/dns.js +306 -0
- package/modules/std/net/http.js +820 -0
- package/modules/std/net/smtp.js +943 -0
- package/modules/std/net/url.js +109 -0
- package/modules/std/proc.js +602 -0
- package/modules/std/secure.js +3724 -0
- package/modules/std/string/base64.js +138 -0
- package/modules/std/string.js +299 -0
- package/modules/std/task.js +914 -0
- package/modules/std/time.js +579 -0
- package/modules/std/tui.js +188 -0
- package/modules/std/worker-thread.js +246 -0
- package/modules/std/worker.js +790 -0
- package/package.json +67 -0
- package/stdlib/modules/javascript.zzm +99 -0
- package/stdlib/modules/perl.zzm +105 -0
- package/stdlib/modules/std/archive.zzm +132 -0
- package/stdlib/modules/std/cache/lru.zzm +174 -0
- package/stdlib/modules/std/clib.zzm +112 -0
- package/stdlib/modules/std/colour.zzm +220 -0
- package/stdlib/modules/std/config.zzm +818 -0
- package/stdlib/modules/std/data/cbor.zzm +497 -0
- package/stdlib/modules/std/data/csv.zzm +285 -0
- package/stdlib/modules/std/data/ini.zzm +472 -0
- package/stdlib/modules/std/data/json/schema/core.zzm +573 -0
- package/stdlib/modules/std/data/json/schema/format.zzm +581 -0
- package/stdlib/modules/std/data/json/schema/model.zzm +255 -0
- package/stdlib/modules/std/data/json/schema/output.zzm +272 -0
- package/stdlib/modules/std/data/json/schema/relative_pointer.zzm +299 -0
- package/stdlib/modules/std/data/json/schema/validation.zzm +1503 -0
- package/stdlib/modules/std/data/json/schema.zzm +306 -0
- package/stdlib/modules/std/data/json.zzm +102 -0
- package/stdlib/modules/std/data/kdl/json.zzm +460 -0
- package/stdlib/modules/std/data/kdl/xml.zzm +387 -0
- package/stdlib/modules/std/data/kdl.zzm +1631 -0
- package/stdlib/modules/std/data/toml.zzm +756 -0
- package/stdlib/modules/std/data/toon.zzm +1017 -0
- package/stdlib/modules/std/data/xml/escape.zzm +156 -0
- package/stdlib/modules/std/data/xml.zzm +276 -0
- package/stdlib/modules/std/data/yaml.zzm +94 -0
- package/stdlib/modules/std/db.zzm +173 -0
- package/stdlib/modules/std/defer.zzm +75 -0
- package/stdlib/modules/std/digest/crc32.zzm +196 -0
- package/stdlib/modules/std/digest/md5.zzm +54 -0
- package/stdlib/modules/std/digest/sha.zzm +83 -0
- package/stdlib/modules/std/dump.zzm +317 -0
- package/stdlib/modules/std/eval.zzm +63 -0
- package/stdlib/modules/std/getopt.zzm +432 -0
- package/stdlib/modules/std/gui/dialogue.zzm +592 -0
- package/stdlib/modules/std/gui/objects.zzm +123 -0
- package/stdlib/modules/std/gui.zzm +1914 -0
- package/stdlib/modules/std/internals.zzm +139 -0
- package/stdlib/modules/std/io/socks.zzm +139 -0
- package/stdlib/modules/std/io.zzm +157 -0
- package/stdlib/modules/std/lingua/en.zzm +347 -0
- package/stdlib/modules/std/log.zzm +169 -0
- package/stdlib/modules/std/mail.zzm +2726 -0
- package/stdlib/modules/std/marshal.zzm +138 -0
- package/stdlib/modules/std/math/bignum.zzm +98 -0
- package/stdlib/modules/std/math/range.zzm +116 -0
- package/stdlib/modules/std/math/roman.zzm +156 -0
- package/stdlib/modules/std/math.zzm +141 -0
- package/stdlib/modules/std/net/dns.zzm +93 -0
- package/stdlib/modules/std/net/http.zzm +278 -0
- package/stdlib/modules/std/net/smtp.zzm +257 -0
- package/stdlib/modules/std/net/url.zzm +69 -0
- package/stdlib/modules/std/path/jsonpointer.zzm +526 -0
- package/stdlib/modules/std/path/kdl.zzm +1003 -0
- package/stdlib/modules/std/path/simple.zzm +520 -0
- package/stdlib/modules/std/path/z/context.zzm +147 -0
- package/stdlib/modules/std/path/z/evaluate.zzm +549 -0
- package/stdlib/modules/std/path/z/functions.zzm +874 -0
- package/stdlib/modules/std/path/z/lexer.zzm +490 -0
- package/stdlib/modules/std/path/z/node.zzm +1455 -0
- package/stdlib/modules/std/path/z/operators.zzm +445 -0
- package/stdlib/modules/std/path/z/parser.zzm +359 -0
- package/stdlib/modules/std/path/z.zzm +403 -0
- package/stdlib/modules/std/path/zz/functions.zzm +828 -0
- package/stdlib/modules/std/path/zz/operators.zzm +1036 -0
- package/stdlib/modules/std/path/zz.zzm +100 -0
- package/stdlib/modules/std/proc.zzm +155 -0
- package/stdlib/modules/std/result.zzm +149 -0
- package/stdlib/modules/std/secure.zzm +606 -0
- package/stdlib/modules/std/string/base64.zzm +66 -0
- package/stdlib/modules/std/string/quoted_printable.zzm +485 -0
- package/stdlib/modules/std/string.zzm +179 -0
- package/stdlib/modules/std/task.zzm +221 -0
- package/stdlib/modules/std/template/z.zzm +531 -0
- package/stdlib/modules/std/template/zz.zzm +62 -0
- package/stdlib/modules/std/time.zzm +188 -0
- package/stdlib/modules/std/tui.zzm +89 -0
- package/stdlib/modules/std/uuid.zzm +223 -0
- package/stdlib/modules/std/web/session.zzm +388 -0
- package/stdlib/modules/std/web/static.zzm +329 -0
- package/stdlib/modules/std/web.zzm +1942 -0
- package/stdlib/modules/std/worker.zzm +202 -0
- package/stdlib/modules/std/zuzuzoo.zzm +3960 -0
- package/stdlib/modules/test/more.zzm +528 -0
- package/stdlib/modules/test/parser.zzm +209 -0
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
=encoding utf8
|
|
2
|
+
|
|
3
|
+
=head1 NAME
|
|
4
|
+
|
|
5
|
+
std/config - High-level configuration loading, merging, and querying.
|
|
6
|
+
|
|
7
|
+
=head1 SYNOPSIS
|
|
8
|
+
|
|
9
|
+
from std/config import Config;
|
|
10
|
+
from std/io import Path;
|
|
11
|
+
|
|
12
|
+
let cfg := Config.load( [
|
|
13
|
+
Path.join( [ "config", "base.toml" ] ),
|
|
14
|
+
Path.join( [ "config", "local.json" ] ),
|
|
15
|
+
] );
|
|
16
|
+
|
|
17
|
+
cfg.merge_flat(
|
|
18
|
+
{
|
|
19
|
+
"APP__port": "8080",
|
|
20
|
+
"APP__debug": "true",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
prefix: "APP__",
|
|
24
|
+
separator: "__",
|
|
25
|
+
coerce: true,
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
let host := cfg @ "/database/host";
|
|
30
|
+
let port := cfg.get( "/port", 3000 );
|
|
31
|
+
|
|
32
|
+
=head1 IMPLEMENTATION SUPPORT
|
|
33
|
+
|
|
34
|
+
This module is supported by zuzu.pl, zuzu-rust, and zuzu-js on Node and
|
|
35
|
+
Electron. It is partially supported by zuzu-js in the browser: environment
|
|
36
|
+
override and multi-format branch selection coverage passes, but the main
|
|
37
|
+
filesystem-backed configuration coverage is unsupported.
|
|
38
|
+
|
|
39
|
+
=head1 DESCRIPTION
|
|
40
|
+
|
|
41
|
+
This module provides a C<Config> object for application-style
|
|
42
|
+
configuration loading and overlaying.
|
|
43
|
+
|
|
44
|
+
Use C<Config.from_data(...)> when you want to wrap an already-built
|
|
45
|
+
Zuzu value instead of loading from files.
|
|
46
|
+
|
|
47
|
+
It is intentionally geared toward patterns common in higher-level config
|
|
48
|
+
frameworks:
|
|
49
|
+
|
|
50
|
+
=over
|
|
51
|
+
|
|
52
|
+
=item *
|
|
53
|
+
|
|
54
|
+
load one or more files with format auto-detection,
|
|
55
|
+
|
|
56
|
+
=item *
|
|
57
|
+
|
|
58
|
+
deep-merge config layers,
|
|
59
|
+
|
|
60
|
+
=item *
|
|
61
|
+
|
|
62
|
+
apply flat overlays such as env-style C<FOO__BAR__BAZ> keys,
|
|
63
|
+
|
|
64
|
+
=item *
|
|
65
|
+
|
|
66
|
+
query config using ZPath and the C<@>, C<@@>, and C<@?> operators.
|
|
67
|
+
|
|
68
|
+
=back
|
|
69
|
+
|
|
70
|
+
The object itself is path-aware, so this works directly:
|
|
71
|
+
|
|
72
|
+
let city := cfg @ "/service/address/city";
|
|
73
|
+
|
|
74
|
+
=head1 EXPORTS
|
|
75
|
+
|
|
76
|
+
=head2 Classes
|
|
77
|
+
|
|
78
|
+
=over
|
|
79
|
+
|
|
80
|
+
=item C<Config>
|
|
81
|
+
|
|
82
|
+
Static methods:
|
|
83
|
+
|
|
84
|
+
=over
|
|
85
|
+
|
|
86
|
+
=item * C<from_data(data, options?)>
|
|
87
|
+
|
|
88
|
+
Parameters: C<data> is configuration data and C<options> controls
|
|
89
|
+
metadata. Returns: C<Config>. Wraps data in a config object.
|
|
90
|
+
|
|
91
|
+
=item * C<parse(text, format, options?)>
|
|
92
|
+
|
|
93
|
+
Parameters: C<text> is config text, C<format> is a format name, and
|
|
94
|
+
C<options> configures parsing. Returns: C<Config>. Parses text into a
|
|
95
|
+
config object.
|
|
96
|
+
|
|
97
|
+
=item * C<load(path_or_paths, options?)>
|
|
98
|
+
|
|
99
|
+
Parameters: C<path_or_paths> is one source or an array of sources.
|
|
100
|
+
Returns: C<Config>. Loads and layers config files.
|
|
101
|
+
|
|
102
|
+
=item * C<detect_format(path_or_name, fallback?)>
|
|
103
|
+
|
|
104
|
+
Parameters: C<path_or_name> is a path-like value and C<fallback> is
|
|
105
|
+
optional. Returns: C<String> or C<null>. Detects the config format.
|
|
106
|
+
|
|
107
|
+
=back
|
|
108
|
+
|
|
109
|
+
Instance methods:
|
|
110
|
+
|
|
111
|
+
=over
|
|
112
|
+
|
|
113
|
+
=item * C<type()>, C<can_have_named_children()>, C<can_have_indexed_children()>, C<can_have_named_indexed_children()>, C<children()>, C<attributes()>
|
|
114
|
+
|
|
115
|
+
Parameters: none. Returns: ZPath node metadata. Provides the inherited
|
|
116
|
+
node API used when a C<Config> is queried as a path root.
|
|
117
|
+
|
|
118
|
+
=item * C<do_action_on_child(child, action)>, C<ref_on_child(child)>
|
|
119
|
+
|
|
120
|
+
Parameters: C<child> is a selected node and C<action> is a path action.
|
|
121
|
+
Returns: value or C<Function>. Provides inherited mutation and reference
|
|
122
|
+
support for path assignments.
|
|
123
|
+
|
|
124
|
+
=item * C<to_data()>, C<clone()>
|
|
125
|
+
|
|
126
|
+
Parameters: none. Returns: value or C<Config>. Returns raw data or a
|
|
127
|
+
copy of the config.
|
|
128
|
+
|
|
129
|
+
=item * C<source()>, C<format()>, C<layers()>
|
|
130
|
+
|
|
131
|
+
Parameters: none. Returns: value. Returns config source metadata.
|
|
132
|
+
|
|
133
|
+
=item * C<get(path, fallback?)>, C<get_all(path)>, C<select(path)>
|
|
134
|
+
|
|
135
|
+
Parameters: C<path> is a path expression and C<fallback> is optional.
|
|
136
|
+
Returns: value or C<Array>. Reads config values.
|
|
137
|
+
|
|
138
|
+
=item * C<exists(path)>, C<require(path, message?)>
|
|
139
|
+
|
|
140
|
+
Parameters: C<path> is a path expression and C<message> is optional.
|
|
141
|
+
Returns: C<Boolean> or value. Tests for or requires a config value.
|
|
142
|
+
|
|
143
|
+
=item * C<query(path)>, C<first(path, fallback?)>
|
|
144
|
+
|
|
145
|
+
Parameters: C<path> is a path expression and C<fallback> is optional.
|
|
146
|
+
Returns: C<Array> or value. Queries config values.
|
|
147
|
+
|
|
148
|
+
=item * C<assign_first(path, value, op?)>
|
|
149
|
+
|
|
150
|
+
Parameters: C<path> selects values, C<value> is assigned, and C<op> is
|
|
151
|
+
optional. Returns: value. Updates the first match.
|
|
152
|
+
|
|
153
|
+
=item * C<assign_all(path, value, op?)>
|
|
154
|
+
|
|
155
|
+
Parameters: C<path> selects values, C<value> is assigned, and C<op> is
|
|
156
|
+
optional. Returns: value. Updates every match.
|
|
157
|
+
|
|
158
|
+
=item * C<assign_maybe(path, value, op?)>
|
|
159
|
+
|
|
160
|
+
Parameters: C<path> selects values, C<value> is assigned, and C<op> is
|
|
161
|
+
optional. Returns: C<Boolean>. Updates the first match when present.
|
|
162
|
+
|
|
163
|
+
=item * C<ref_first(path)>, C<ref_all(path)>, C<ref_maybe(path)>
|
|
164
|
+
|
|
165
|
+
Parameters: C<path> is a path expression. Returns: C<Function>,
|
|
166
|
+
C<Array>, or C<null>. Returns reference-like accessors.
|
|
167
|
+
|
|
168
|
+
=item * C<merge(data_or_config, options?)>, C<overlay(data_or_config, options?)>
|
|
169
|
+
|
|
170
|
+
Parameters: C<data_or_config> is incoming data and C<options> controls
|
|
171
|
+
merge behaviour. Returns: C<Config>. Merges configuration data.
|
|
172
|
+
|
|
173
|
+
=item * C<merge_flat(values, options?)>, C<merge_env(values, options?)>
|
|
174
|
+
|
|
175
|
+
Parameters: C<values> is flat config data and C<options> controls key
|
|
176
|
+
mapping. Returns: C<Config>. Merges flat or environment-style values.
|
|
177
|
+
|
|
178
|
+
=item * C<set(path, value)>, C<set_default(path, value)>
|
|
179
|
+
|
|
180
|
+
Parameters: C<path> is a simple path and C<value> is any value. Returns:
|
|
181
|
+
C<Config>. Sets or defaults a config value.
|
|
182
|
+
|
|
183
|
+
=item * C<encode(format?, options?)>, C<save(path, options?)>
|
|
184
|
+
|
|
185
|
+
Parameters: C<format>, C<path>, and C<options> control output. Returns:
|
|
186
|
+
C<String> or C<Config>. Encodes or saves configuration data.
|
|
187
|
+
|
|
188
|
+
=item * C<load_file(path, options?)>
|
|
189
|
+
|
|
190
|
+
Parameters: C<path> is a config source and C<options> controls parsing.
|
|
191
|
+
Returns: C<Config>. Loads one additional config file into the object.
|
|
192
|
+
|
|
193
|
+
=back
|
|
194
|
+
|
|
195
|
+
=back
|
|
196
|
+
|
|
197
|
+
=head1 NOTES
|
|
198
|
+
|
|
199
|
+
=over
|
|
200
|
+
|
|
201
|
+
=item *
|
|
202
|
+
|
|
203
|
+
C<merge> performs a deep merge for dictionaries.
|
|
204
|
+
|
|
205
|
+
=item *
|
|
206
|
+
|
|
207
|
+
Arrays are replaced by default; pass C<< { array_merge: "append" } >>
|
|
208
|
+
to append instead.
|
|
209
|
+
|
|
210
|
+
=item *
|
|
211
|
+
|
|
212
|
+
C<set> and C<set_default> create missing parent dictionaries, but only
|
|
213
|
+
for simple absolute paths such as C</server/port>. They intentionally do
|
|
214
|
+
not try to create missing nodes for complex selectors or filters.
|
|
215
|
+
|
|
216
|
+
=back
|
|
217
|
+
|
|
218
|
+
=head1 COPYRIGHT AND LICENCE
|
|
219
|
+
|
|
220
|
+
B<< std/config >> is copyright Toby Inkster.
|
|
221
|
+
|
|
222
|
+
It is free software; you may redistribute it and/or modify it under
|
|
223
|
+
the terms of either the Artistic License 1.0 or the GNU General Public
|
|
224
|
+
License version 2.
|
|
225
|
+
|
|
226
|
+
=cut
|
|
227
|
+
|
|
228
|
+
from std/cache/lru import Cache;
|
|
229
|
+
from std/path/z import ZPath;
|
|
230
|
+
from std/path/z/node import Node;
|
|
231
|
+
from std/string import join, split, substr, trim;
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
const _PATH_CACHE := new Cache( capacity: 50 );
|
|
235
|
+
|
|
236
|
+
function _opt ( options, key, fallback := null ) {
|
|
237
|
+
if ( options instanceof Dict and options.exists(key) ) {
|
|
238
|
+
return options.get(key);
|
|
239
|
+
}
|
|
240
|
+
return fallback;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function _unwrap_config_value ( value ) {
|
|
244
|
+
if (
|
|
245
|
+
value ≢ null and
|
|
246
|
+
value can raw and
|
|
247
|
+
value can query and
|
|
248
|
+
value can merge
|
|
249
|
+
) {
|
|
250
|
+
return value.raw();
|
|
251
|
+
}
|
|
252
|
+
return value;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function _stringify_pathish ( pathish ) {
|
|
256
|
+
from std/io import Path;
|
|
257
|
+
if ( pathish instanceof Path ) {
|
|
258
|
+
return pathish.to_String();
|
|
259
|
+
}
|
|
260
|
+
return "" _ pathish;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function _normalize_format ( raw_format ) {
|
|
264
|
+
if ( raw_format ≡ null ) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let fmt := lc( "" _ raw_format );
|
|
269
|
+
if ( fmt ≡ "yml" ) {
|
|
270
|
+
return "yaml";
|
|
271
|
+
}
|
|
272
|
+
return fmt;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function _detect_format_from_name ( source, fallback := null ) {
|
|
276
|
+
let text := lc( _stringify_pathish(source) );
|
|
277
|
+
let dot := null;
|
|
278
|
+
let i := length text - 1;
|
|
279
|
+
while ( i >= 0 ) {
|
|
280
|
+
let ch := substr( text, i, 1 );
|
|
281
|
+
if ( ch ≡ "." ) {
|
|
282
|
+
dot := i;
|
|
283
|
+
last;
|
|
284
|
+
}
|
|
285
|
+
if ( ch ≡ "/" or ch ≡ "\\" ) {
|
|
286
|
+
last;
|
|
287
|
+
}
|
|
288
|
+
i--;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if ( dot ≡ null or dot = length text - 1 ) {
|
|
292
|
+
return fallback;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return _normalize_format( substr( text, dot + 1 ) ) ?: fallback;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function _codec_for_format ( raw_format, options? ) {
|
|
299
|
+
let format := _normalize_format(raw_format);
|
|
300
|
+
if ( _opt( options, "codec", null ) ≢ null ) {
|
|
301
|
+
return _opt( options, "codec", null );
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if ( format ≡ "json" ) {
|
|
305
|
+
from std/data/json import JSON;
|
|
306
|
+
return new JSON();
|
|
307
|
+
}
|
|
308
|
+
if ( format ≡ "yaml" ) {
|
|
309
|
+
from std/data/yaml import YAML;
|
|
310
|
+
return new YAML();
|
|
311
|
+
}
|
|
312
|
+
if ( format ≡ "toml" ) {
|
|
313
|
+
from std/data/toml import TOML;
|
|
314
|
+
return new TOML();
|
|
315
|
+
}
|
|
316
|
+
if ( format ≡ "ini" ) {
|
|
317
|
+
from std/data/ini import INI;
|
|
318
|
+
return new INI();
|
|
319
|
+
}
|
|
320
|
+
if ( format ≡ "toon" ) {
|
|
321
|
+
from std/data/toon import TOON;
|
|
322
|
+
return new TOON();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
die `std/config does not know how to handle format '${format}'`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function _ensure_path_object ( source ) {
|
|
329
|
+
from std/io import Path;
|
|
330
|
+
return source instanceof Path ? source : new Path( "" _ source );
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function _compile_path ( pathish ) {
|
|
334
|
+
if ( pathish instanceof ZPath ) {
|
|
335
|
+
return pathish;
|
|
336
|
+
}
|
|
337
|
+
if (
|
|
338
|
+
pathish can query and
|
|
339
|
+
pathish can first and
|
|
340
|
+
pathish can exists and
|
|
341
|
+
pathish can assign_first and
|
|
342
|
+
pathish can ref_first
|
|
343
|
+
) {
|
|
344
|
+
return pathish;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
let expression := "" _ pathish;
|
|
348
|
+
return _PATH_CACHE.get(
|
|
349
|
+
expression,
|
|
350
|
+
fn path_text -> new ZPath( path: path_text ),
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function _coerce_scalar_text ( raw_value, options? ) {
|
|
355
|
+
if ( not _opt( options, "coerce", false ) ) {
|
|
356
|
+
return raw_value;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if ( not( raw_value instanceof String ) ) {
|
|
360
|
+
return raw_value;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
let text := trim(raw_value);
|
|
364
|
+
let lowered := lc(text);
|
|
365
|
+
|
|
366
|
+
if ( lowered ≡ "true" ) {
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
if ( lowered ≡ "false" ) {
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
if ( lowered ≡ "null" or lowered ≡ "~" ) {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
if ( text ~ /^-?[0-9]+$/ ) {
|
|
376
|
+
return int(text);
|
|
377
|
+
}
|
|
378
|
+
if ( text ~ /^-?(?:[0-9]+\.[0-9]+|\.[0-9]+)$/ ) {
|
|
379
|
+
return 0 + text;
|
|
380
|
+
}
|
|
381
|
+
if (
|
|
382
|
+
length text >= 2 and
|
|
383
|
+
(
|
|
384
|
+
(
|
|
385
|
+
substr( text, 0, 1 ) ≡ "{" and
|
|
386
|
+
substr( text, length text - 1, 1 ) ≡ "}"
|
|
387
|
+
) or
|
|
388
|
+
(
|
|
389
|
+
substr( text, 0, 1 ) ≡ "[" and
|
|
390
|
+
substr( text, length text - 1, 1 ) ≡ "]"
|
|
391
|
+
)
|
|
392
|
+
)
|
|
393
|
+
) {
|
|
394
|
+
try {
|
|
395
|
+
from std/data/json import JSON;
|
|
396
|
+
return new JSON().decode(text);
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return raw_value;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function _merge_values ( left, right, options? ) {
|
|
406
|
+
let left_value := _unwrap_config_value(left);
|
|
407
|
+
let right_value := _unwrap_config_value(right);
|
|
408
|
+
|
|
409
|
+
if ( left_value instanceof Dict and right_value instanceof Dict ) {
|
|
410
|
+
for ( let key in right_value.keys() ) {
|
|
411
|
+
if ( left_value.exists(key) ) {
|
|
412
|
+
left_value{(key)} := _merge_values(
|
|
413
|
+
left_value.get(key),
|
|
414
|
+
right_value.get(key),
|
|
415
|
+
options,
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
left_value{(key)} := right_value.get(key);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return left_value;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (
|
|
426
|
+
left_value instanceof Array and
|
|
427
|
+
right_value instanceof Array and
|
|
428
|
+
_opt( options, "array_merge", "replace" ) ≡ "append"
|
|
429
|
+
) {
|
|
430
|
+
for ( let item in right_value ) {
|
|
431
|
+
left_value.push(item);
|
|
432
|
+
}
|
|
433
|
+
return left_value;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return right_value;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function _simple_path_parts ( raw_path ) {
|
|
440
|
+
let text := "" _ raw_path;
|
|
441
|
+
if ( text ≡ "" ) {
|
|
442
|
+
return [];
|
|
443
|
+
}
|
|
444
|
+
if ( substr( text, 0, 1 ) ≡ "/" ) {
|
|
445
|
+
text := substr( text, 1 );
|
|
446
|
+
}
|
|
447
|
+
if ( text ≡ "" ) {
|
|
448
|
+
return [];
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
let parts := [];
|
|
452
|
+
for ( let part in split( text, "/" ) ) {
|
|
453
|
+
next if part ≡ "";
|
|
454
|
+
die `std/config simple path cannot contain complex selector '${part}'`
|
|
455
|
+
if part ~ /[\*\[\]#@\(\)]/;
|
|
456
|
+
parts.push(part);
|
|
457
|
+
}
|
|
458
|
+
return parts;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function _ensure_root_dict ( current ) {
|
|
462
|
+
if ( current ≡ null ) {
|
|
463
|
+
return {};
|
|
464
|
+
}
|
|
465
|
+
if ( current instanceof Dict ) {
|
|
466
|
+
return current;
|
|
467
|
+
}
|
|
468
|
+
die "std/config expected Dict root for simple path mutation";
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
class Config extends Node {
|
|
472
|
+
let _source := null;
|
|
473
|
+
let _format := null;
|
|
474
|
+
let _layers := [];
|
|
475
|
+
|
|
476
|
+
method __build__ () {
|
|
477
|
+
self.set_raw( self.raw ≡ null ? {}: self.raw );
|
|
478
|
+
_layers := _layers ≡ null ? []: _layers;
|
|
479
|
+
self._build_id();
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
static method detect_format ( source, fallback := null ) {
|
|
483
|
+
return _detect_format_from_name( source, fallback );
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
static method from_data ( data, options? ) {
|
|
487
|
+
return new Config(
|
|
488
|
+
raw: _unwrap_config_value(data),
|
|
489
|
+
_source: _opt( options, "source", null ),
|
|
490
|
+
_format: _normalize_format( _opt( options, "format", null ) ),
|
|
491
|
+
_layers: _opt( options, "layers", [] ),
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
static method parse ( text, format, options? ) {
|
|
496
|
+
let codec := _codec_for_format( format, options );
|
|
497
|
+
let layer_source := _opt( options, "source", null );
|
|
498
|
+
let decoded := codec.decode(text);
|
|
499
|
+
return new Config(
|
|
500
|
+
raw: decoded,
|
|
501
|
+
_source: layer_source,
|
|
502
|
+
_format: _normalize_format(format),
|
|
503
|
+
_layers: [
|
|
504
|
+
{
|
|
505
|
+
source: layer_source,
|
|
506
|
+
format: _normalize_format(format),
|
|
507
|
+
},
|
|
508
|
+
],
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
static method load ( sources, options? ) {
|
|
513
|
+
let cfg := new Config( raw: {}, _layers: [] );
|
|
514
|
+
for ( let source in ( sources instanceof Array ? sources : [ sources ] ) ) {
|
|
515
|
+
cfg.load_file( source, options );
|
|
516
|
+
}
|
|
517
|
+
return cfg;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
method source () {
|
|
521
|
+
return _source;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
method format () {
|
|
525
|
+
return _format;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
method layers () {
|
|
529
|
+
return _layers;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
method to_data () {
|
|
533
|
+
return self.raw;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
method clone () {
|
|
537
|
+
let cloned := new Config( raw: _merge_values( {}, self.raw, {} ) );
|
|
538
|
+
cloned._layers := _layers.to_Array();
|
|
539
|
+
cloned._source := _source;
|
|
540
|
+
cloned._format := _format;
|
|
541
|
+
return cloned;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
method _delegate_node () {
|
|
545
|
+
return Node.from_root( self.raw );
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
method type () {
|
|
549
|
+
return self._delegate_node().type();
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
method can_have_named_children () {
|
|
553
|
+
return self._delegate_node().can_have_named_children();
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
method can_have_indexed_children () {
|
|
557
|
+
return self._delegate_node().can_have_indexed_children();
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
method can_have_named_indexed_children () {
|
|
561
|
+
return self._delegate_node().can_have_named_indexed_children();
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
method children () {
|
|
565
|
+
let out := [];
|
|
566
|
+
for ( let child in self._delegate_node().children() ) {
|
|
567
|
+
out.push(
|
|
568
|
+
Node.wrap(
|
|
569
|
+
child.raw(),
|
|
570
|
+
self,
|
|
571
|
+
child.key(),
|
|
572
|
+
child.ix(),
|
|
573
|
+
),
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
return out;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
method attributes () {
|
|
580
|
+
let out := [];
|
|
581
|
+
for ( let child in self._delegate_node().attributes() ) {
|
|
582
|
+
out.push(
|
|
583
|
+
Node.wrap(
|
|
584
|
+
child.raw(),
|
|
585
|
+
self,
|
|
586
|
+
child.key(),
|
|
587
|
+
child.ix(),
|
|
588
|
+
),
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
return out;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
method do_action_on_child ( child, action ) {
|
|
595
|
+
return super( child, action ) if action{op} ne ":=";
|
|
596
|
+
|
|
597
|
+
let container := self.raw;
|
|
598
|
+
if ( container instanceof Dict ) {
|
|
599
|
+
let key := child.key();
|
|
600
|
+
die "Path assignment expects string dict key" if key ≡ null;
|
|
601
|
+
container{(key)} := action{value};
|
|
602
|
+
return action{value};
|
|
603
|
+
}
|
|
604
|
+
if ( container instanceof Array ) {
|
|
605
|
+
let ix := child.ix();
|
|
606
|
+
die "Path assignment expects numeric array index" if ix ≡ null;
|
|
607
|
+
container[ix] := action{value};
|
|
608
|
+
return action{value};
|
|
609
|
+
}
|
|
610
|
+
if ( container instanceof PairList ) {
|
|
611
|
+
let key := child.key();
|
|
612
|
+
die "Path assignment expects string pairlist key" if key ≡ null;
|
|
613
|
+
container.set( key, action{value} );
|
|
614
|
+
return action{value};
|
|
615
|
+
}
|
|
616
|
+
return super( child, action );
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
method ref_on_child ( child ) {
|
|
620
|
+
let container := self.raw;
|
|
621
|
+
if ( container instanceof Dict ) {
|
|
622
|
+
let key := child.key();
|
|
623
|
+
die "Path assignment expects string dict key" if key ≡ null;
|
|
624
|
+
return \ container{(key)};
|
|
625
|
+
}
|
|
626
|
+
if ( container instanceof Array ) {
|
|
627
|
+
let ix := child.ix();
|
|
628
|
+
die "Path assignment expects numeric array index" if ix ≡ null;
|
|
629
|
+
return \ container[ix];
|
|
630
|
+
}
|
|
631
|
+
if ( container instanceof PairList ) {
|
|
632
|
+
let key := child.key();
|
|
633
|
+
die "Path assignment expects string pairlist key" if key ≡ null;
|
|
634
|
+
return \ container{(key)};
|
|
635
|
+
}
|
|
636
|
+
return super(child);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
method query ( pathish ) {
|
|
640
|
+
return _compile_path(pathish).query(self);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
method select ( pathish ) {
|
|
644
|
+
return self.query(pathish);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
method get_all ( pathish ) {
|
|
648
|
+
return self.query(pathish);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
method first ( pathish, fallback? ) {
|
|
652
|
+
return _compile_path(pathish).first( self, fallback );
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
method get ( pathish, fallback? ) {
|
|
656
|
+
return self.first( pathish, fallback );
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
method exists ( pathish ) {
|
|
660
|
+
return _compile_path(pathish).exists(self);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
method require ( pathish, message? ) {
|
|
664
|
+
if ( self.exists(pathish) ) {
|
|
665
|
+
return self.get(pathish);
|
|
666
|
+
}
|
|
667
|
+
die(
|
|
668
|
+
message ?:
|
|
669
|
+
`std/config required setting not found at '${pathish}'`
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
method assign_first ( pathish, value, op := ":=", weak := false ) {
|
|
674
|
+
return _compile_path(pathish).assign_first( self, value, op, weak );
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
method assign_all ( pathish, value, op := ":=", weak := false ) {
|
|
678
|
+
return _compile_path(pathish).assign_all( self, value, op, weak );
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
method assign_maybe ( pathish, value, op := ":=", weak := false ) {
|
|
682
|
+
return _compile_path(pathish).assign_maybe( self, value, op, weak );
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
method ref_first ( pathish ) {
|
|
686
|
+
return _compile_path(pathish).ref_first(self);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
method ref_all ( pathish ) {
|
|
690
|
+
return _compile_path(pathish).ref_all(self);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
method ref_maybe ( pathish ) {
|
|
694
|
+
return _compile_path(pathish).ref_maybe(self);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
method merge ( other, options? ) {
|
|
698
|
+
let incoming := _unwrap_config_value(other);
|
|
699
|
+
self.set_raw( _merge_values( self.raw, incoming, options ) );
|
|
700
|
+
return self;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
method overlay ( other, options? ) {
|
|
704
|
+
return self.merge( other, options );
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
method merge_flat ( values, options? ) {
|
|
708
|
+
return self if not( values instanceof Dict );
|
|
709
|
+
|
|
710
|
+
let prefix := "" _ _opt( options, "prefix", "" );
|
|
711
|
+
let separator := "" _ _opt( options, "separator", "__" );
|
|
712
|
+
let downcase := _opt( options, "lowercase", false );
|
|
713
|
+
|
|
714
|
+
for ( let key in values.keys() ) {
|
|
715
|
+
let name := "" _ key;
|
|
716
|
+
if ( prefix ≢ "" ) {
|
|
717
|
+
next if substr( name, 0, length prefix ) ne prefix;
|
|
718
|
+
name := substr( name, length prefix );
|
|
719
|
+
}
|
|
720
|
+
next if name ≡ "";
|
|
721
|
+
|
|
722
|
+
let parts := [];
|
|
723
|
+
for ( let part in split( name, separator ) ) {
|
|
724
|
+
next if part ≡ "";
|
|
725
|
+
parts.push( downcase ? lc(part) : part );
|
|
726
|
+
}
|
|
727
|
+
next if parts.length() = 0;
|
|
728
|
+
|
|
729
|
+
self.set(
|
|
730
|
+
"/" _ join( "/", parts ),
|
|
731
|
+
_coerce_scalar_text( values.get(key), options ),
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return self;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
method merge_env ( values, options? ) {
|
|
739
|
+
return self.merge_flat( values, options );
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
method set ( pathish, value ) {
|
|
743
|
+
let parts := _simple_path_parts(pathish);
|
|
744
|
+
if ( parts.length() = 0 ) {
|
|
745
|
+
self.set_raw(value);
|
|
746
|
+
return value;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
self.set_raw( _ensure_root_dict( self.raw ) );
|
|
750
|
+
let cursor := self.raw;
|
|
751
|
+
let i := 0;
|
|
752
|
+
while ( i < parts.length() - 1 ) {
|
|
753
|
+
let key := parts[i];
|
|
754
|
+
if ( not cursor.exists(key) or cursor.get(key) ≡ null ) {
|
|
755
|
+
cursor{(key)} := {};
|
|
756
|
+
}
|
|
757
|
+
else if ( not( cursor.get(key) instanceof Dict ) ) {
|
|
758
|
+
die `std/config cannot create child path below non-Dict setting '${key}'`;
|
|
759
|
+
}
|
|
760
|
+
cursor := cursor.get(key);
|
|
761
|
+
i++;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
cursor{(parts[parts.length() - 1])} := value;
|
|
765
|
+
return value;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
method set_default ( pathish, value ) {
|
|
769
|
+
if ( not self.exists(pathish) or self.get(pathish) ≡ null ) {
|
|
770
|
+
return self.set( pathish, value );
|
|
771
|
+
}
|
|
772
|
+
return self.get(pathish);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
method encode ( format?, options? ) {
|
|
776
|
+
let chosen := _normalize_format(
|
|
777
|
+
format ?: _format ?: "json"
|
|
778
|
+
);
|
|
779
|
+
return _codec_for_format( chosen, options ).encode( self.raw );
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
method save ( destination, options? ) {
|
|
783
|
+
let path := _ensure_path_object(destination);
|
|
784
|
+
let chosen := _normalize_format(
|
|
785
|
+
_opt( options, "format", null ) ?:
|
|
786
|
+
Config.detect_format( path, _format ?: "json" )
|
|
787
|
+
);
|
|
788
|
+
_codec_for_format( chosen, options ).dump( path, self.raw );
|
|
789
|
+
return path;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
method load_file ( source, options? ) {
|
|
793
|
+
let path := _ensure_path_object(source);
|
|
794
|
+
if ( _opt( options, "optional", false ) and not path.exists() ) {
|
|
795
|
+
return self;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
let chosen := _normalize_format(
|
|
799
|
+
_opt( options, "format", null ) ?:
|
|
800
|
+
Config.detect_format(path)
|
|
801
|
+
);
|
|
802
|
+
die `std/config could not determine format for '${path}'`
|
|
803
|
+
if chosen ≡ null;
|
|
804
|
+
|
|
805
|
+
let decoded := _codec_for_format( chosen, options ).load(path);
|
|
806
|
+
self.merge( decoded, options );
|
|
807
|
+
|
|
808
|
+
_source := path.to_String();
|
|
809
|
+
_format := chosen;
|
|
810
|
+
_layers.push(
|
|
811
|
+
{
|
|
812
|
+
source: _source,
|
|
813
|
+
format: chosen,
|
|
814
|
+
},
|
|
815
|
+
);
|
|
816
|
+
return self;
|
|
817
|
+
}
|
|
818
|
+
}
|