knf-cli 0.2.0__py3-none-win_amd64.whl

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.
Binary file
@@ -0,0 +1,297 @@
1
+ Metadata-Version: 2.4
2
+ Name: knf-cli
3
+ Version: 0.2.0
4
+ Classifier: Environment :: Console
5
+ Classifier: License :: OSI Approved :: MIT License
6
+ Classifier: Programming Language :: Rust
7
+ Classifier: Topic :: Utilities
8
+ License-File: LICENSE
9
+ Summary: Merge layered configuration files and print the result
10
+ Keywords: configuration,json,toml,cli
11
+ License-Expression: MIT
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
14
+ Project-URL: Issues, https://github.com/binado/knf/issues
15
+ Project-URL: Repository, https://github.com/binado/knf
16
+
17
+ # knf
18
+
19
+ Merges layered configuration files and prints the result. One job, no query
20
+ language, no template engine.
21
+
22
+ ```bash
23
+ # Print the output to stdout
24
+ knf base.toml prod.toml > merged.toml
25
+ # Add manual overrides via the --set flag
26
+ knf defaults.json overrides.json --set server.port=8080 --set host=name
27
+ # Mix toml and json (if you want)
28
+ knf *.toml *.json
29
+ ```
30
+
31
+ It exists because more powerful alternatives (`yq ea '. as $i ireduce ({}; . * $i)'`,
32
+ `jq -s 'reduce ...'`) require non-obvious incantations for what is a common,
33
+ simple operation. `knf <files>` should need no explanation.
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ pip install knf-cli
39
+ ```
40
+
41
+ The Python distribution is binary-only: it installs the `knf` executable and
42
+ does not provide an importable Python module. Wheels are published for Linux
43
+ (glibc and musl) on x86-64 and ARM64, macOS on Intel and Apple Silicon, and
44
+ Windows on x64 and ARM64. No Rust toolchain is needed to install a wheel.
45
+
46
+ To build from source instead:
47
+
48
+ ```bash
49
+ cargo install knf-cli
50
+ ```
51
+
52
+ ## Rust libraries
53
+
54
+ The whole pipeline — read paths, parse JSON and TOML, merge, interpolate — is
55
+ `knf-config`, published separately from the command line so a Rust consumer or
56
+ a language binding never pulls in `clap`:
57
+
58
+ ```bash
59
+ cargo add knf-config
60
+ ```
61
+
62
+ ```rust
63
+ use knf::{MergeOpts, merge};
64
+
65
+ let merged = merge(&["base.toml", "prod.toml"], MergeOpts::default())?;
66
+ ```
67
+
68
+ `MergeOpts` also accepts strict mode, per-path rules, in-memory terminal
69
+ overlays, an input-format override, and opt-in interpolation. An overlay is a
70
+ `knf::Map` rather than a value, for the reason a file layer must be an object at
71
+ the top level: a scalar layer would replace the whole document instead of
72
+ shadowing a key. The result is the format-independent `knf::Value`, ready for a
73
+ native adapter or language binding to convert without parsing rendered stdout;
74
+ `knf::format::emit` renders it when you do want text.
75
+
76
+ With `interpolate` set, `merge` resolves `${env:NAME}` against the process
77
+ environment; left unset, references are not substituted at all. Pass your own
78
+ environment with `merge_with_env`, and the output is a function of the inputs
79
+ alone:
80
+
81
+ ```rust
82
+ let opts = MergeOpts { interpolate: true, ..MergeOpts::default() };
83
+ let merged = knf::merge_with_env(&paths, opts, &my_env)?;
84
+ ```
85
+
86
+ Errors carry typed causes rather than prose — `LoadError`, `MergeError`,
87
+ `InterpError`, `TomlError` — and name no command-line flags, since a library
88
+ caller has no command line to act on. A null reaching TOML, for instance, is
89
+ reported as the paths it was found at; whether the remedy is spelled `-f json`
90
+ is your interface's business, not the library's.
91
+
92
+ `Map`, `Value`, `Rules`, `Strategy`, `Format`, `Env` and every error type are
93
+ re-exported from `knf`, along with what they are made of — `Number` inside
94
+ `Value::Number`, `Cycle` and `Syntax` inside `InterpError` — so a consumer needs
95
+ no direct dependency on `knf-core` or `knf-interp` to write any of it down.
96
+
97
+ For merging values that are already in memory, use the smaller core crate — it
98
+ has no file I/O and no format crates, only `indexmap` and `thiserror`:
99
+
100
+ ```bash
101
+ cargo add knf-core
102
+ ```
103
+
104
+ ```rust
105
+ use knf_core::{Value, merge};
106
+
107
+ let merged = merge([base, overlay])?;
108
+ ```
109
+
110
+ ## Merging
111
+
112
+ Files are merged left to right in argument order. Exactly one document
113
+ goes to stdout.
114
+
115
+ | Case | Behaviour |
116
+ | --- | --- |
117
+ | object ⊕ object | recurse per key |
118
+ | array ⊕ anything | **replace wholesale**, never index-merge or concat |
119
+ | scalar ⊕ anything | last wins |
120
+ | anything ⊕ null | null is an ordinary value; it overwrites |
121
+
122
+ Two consequences worth knowing:
123
+
124
+ - **Arrays replace**, unless `--append` names the path. Index-merging would turn
125
+ `["a"]` over `["x","y","z"]` into `["a","y","z"]` — a value nobody wrote.
126
+ - **Null is a value, not a delete.** So `knf a.json` with one argument is always
127
+ a byte-level no-op.
128
+
129
+ `--strict` errors when a layer changes the *type* of an existing key, which
130
+ catches the class of mistake where a leaf accidentally shadows a subtree.
131
+
132
+ ```
133
+ $ knf a.json b.json --strict
134
+ error: type conflict at `server`: object would be replaced by number
135
+ ```
136
+
137
+ ### Override merge behavior on specific paths
138
+
139
+ What if a document has one array that should be appended to, and not replaced?
140
+ `knf` understands how to override the merge behavior on a specifc path:
141
+ ```bash
142
+ knf base.toml prod.toml --append plugins # concatenate, base ++ prod
143
+ knf base.toml prod.toml --replace db # take prod's [db] whole
144
+ knf base.toml prod.toml --fail db.host # error if prod overrides db.host
145
+ ```
146
+
147
+ | Flag | At that path |
148
+ | --- | --- |
149
+ | `--append` | concatenate; both sides must be arrays |
150
+ | `--replace` | assign wholesale, no recursion, even object over object |
151
+ | `--fail` | error; the first layer to define the path pins it |
152
+
153
+ ### Variable and environment references
154
+
155
+ A merged config often wants to refer to itself, or to the environment.
156
+ `--interpolate` resolves `${key.path}` and `${env:VAR}` in string values, in one
157
+ pass over the merged document:
158
+
159
+ ```toml
160
+ # base.toml
161
+ root = "/srv"
162
+ data_dir = "${root}/data"
163
+ port = "${env:PORT}"
164
+ url = "http://localhost:${env:PORT}/health"
165
+ literal = "$${NOT_A_REF}"
166
+ ```
167
+
168
+ ```console
169
+ $ PORT=8080 knf base.toml --interpolate
170
+ root = "/srv"
171
+ data_dir = "/srv/data"
172
+ port = 8080
173
+ url = "http://localhost:8080/health"
174
+ literal = "${NOT_A_REF}"
175
+ ```
176
+
177
+ **It is opt-in, and off by default.** knf sits directly upstream of tools whose
178
+ own syntax is `${...}` — compose files, GitHub Actions workflows, Helm charts,
179
+ systemd units. Eating those without being asked would be silent corruption, so
180
+ without the flag the output is byte for byte what it is today.
181
+
182
+ Where the reference sits decides what it yields:
183
+
184
+ | Position | Behaviour |
185
+ | --- | --- |
186
+ | whole string — `port = "${p}"` | takes the referent's **value and type**; `port` above is a number, and `"${db}"` is the whole table |
187
+ | embedded — `url = "x/${p}"` | stringifies; an object or array has no format-independent spelling here, so it is an error |
188
+
189
+ An environment variable is typed by the same rule as `--set`'s right-hand side
190
+ when it is the whole string, and spliced as raw text when it is embedded —
191
+ parsing it only to print it again could only lose something.
192
+
193
+ `$$` is a literal `$`. A `$` followed by anything else is ordinary text, so
194
+ `USD $5` needs no escaping.
195
+
196
+ Document references resolve transitively and in any order; environment values
197
+ are terminal and are never re-scanned. Cycles are an error, and so is a
198
+ reference that names nothing:
199
+
200
+ ```
201
+ $ knf base.toml --interpolate
202
+ error: unresolved reference
203
+ --> server.url: `db.hostname`
204
+ --> tags[0]: `env:REGION`
205
+ help: `${key.path}` names a key in the merged document, `${env:NAME}` an environment variable
206
+ help: drop --interpolate to pass `${...}` through untouched
207
+ ```
208
+
209
+ A reference may also read an array element — `${servers[0].host}` — with the
210
+ same two-position rules: whole-string it takes the element's value and type,
211
+ embedded it stringifies.
212
+
213
+ Two limits worth knowing:
214
+
215
+ - **`env:` is a reserved prefix**, matched literally rather than by splitting on
216
+ the first `:`. So `${a:b}` is the ordinary key `a:b`, and only keys that
217
+ literally begin `env:` are unaddressable.
218
+ - **A key spelled with brackets is unaddressable** — `${a[0]}` now reads as *the
219
+ first element of `a`*, never as a key literally named `a[0]`, and
220
+ `--set 'a[0]=1'` is an error rather than a write into an array. Only a file
221
+ can carry such a key. The same accepted loss as keys containing a literal
222
+ dot, which the dotted grammars have always excluded.
223
+
224
+ `--set` layers interpolate like any other layer. `--strict` runs during the
225
+ merge, before any substitution, so it compares the types values had when they
226
+ were written.
227
+
228
+ ## Caveats with formats
229
+
230
+ JSON and TOML, inferred from the file extension. `--input-format` overrides it
231
+ for every input and is required for `-` (stdin).
232
+
233
+ Output is the inputs' format when they agree; when they don't, `-f` is required
234
+ rather than guessed, so reordering arguments can never silently change the
235
+ encoding. Pretty-printed by default; `--compact` opts out.
236
+
237
+ A TOML datetime is a distinct type all the way through the merge, so every TOML
238
+ output keeps it unquoted — including a merge that mixed in a JSON layer, and
239
+ including `--set` on top. It becomes a plain string only under `-f json`, where
240
+ there is nothing else it could be.
241
+
242
+ TOML cannot represent null, so emitting TOML from a document containing one is
243
+ an error that names every path:
244
+
245
+ ```
246
+ $ knf base.toml override.json -f toml
247
+ error: cannot serialize null to TOML
248
+ --> servers.primary.proxy
249
+ --> logging.sink
250
+ help: emit JSON with -f json, substitute with --null-as, or remove the null
251
+ ```
252
+
253
+ Alternatively, you may use `--null-as <string>` to parse nulls into a custom value:
254
+
255
+ ```bash
256
+ knf base.toml override.json -f toml --null-as=none
257
+ ```
258
+ The option is a no-op for JSON output.
259
+
260
+ Two more values have no spelling in one format or the other, and both are
261
+ rejected the same way — named by path, never silently substituted.
262
+
263
+ TOML integers are signed 64-bit, so an ID above `i64::MAX` (a snowflake, a hash)
264
+ round-trips exactly through JSON but cannot be written as TOML at all:
265
+
266
+ ```
267
+ $ knf ids.json -f toml
268
+ error: cannot serialize integer to TOML
269
+ --> id: `10000000000000000001`
270
+ help: TOML integers are signed 64-bit; emit JSON with -f json
271
+ ```
272
+
273
+ Conversely, TOML's number grammar has `inf`, `-inf` and `nan` literals and
274
+ JSON's has none of them:
275
+
276
+ ```
277
+ $ knf limits.toml -f json
278
+ error: cannot serialize non-finite number to JSON
279
+ --> timeout: `inf`
280
+ help: emit TOML with -f toml, which can represent inf and nan
281
+ ```
282
+
283
+ Each format is the escape from the other's rejection, and no same-format
284
+ round-trip is affected: `knf ids.json -f json` and `knf limits.toml -f toml`
285
+ both emit their input unchanged.
286
+
287
+ ## Testing
288
+
289
+ ```bash
290
+ cargo test --workspace
291
+ cargo test -p knf-core # fast inner loop: no filesystem, no process
292
+ ```
293
+
294
+ ## License
295
+
296
+ MIT — see [LICENSE](LICENSE).
297
+
@@ -0,0 +1,6 @@
1
+ knf_cli-0.2.0.data/scripts/knf.exe,sha256=XgTlvjNwW6fT93tG2vO_HwPSzUU0EX4bV6RIywMQjxE,1401344
2
+ knf_cli-0.2.0.dist-info/METADATA,sha256=XGIC-2-_BxO1LX9kGsZMgxvMIS4zFcj6DiT_DZkvuXA,10820
3
+ knf_cli-0.2.0.dist-info/WHEEL,sha256=2zDlIYIdD4m4N3p5DVEG3iJhGLdhsBQgdH-FqVkAur8,94
4
+ knf_cli-0.2.0.dist-info/licenses/LICENSE,sha256=TSgg6ifXVXaxLfM0VK34j3fsXohLrTcsrxNmJHVb2EQ,1101
5
+ knf_cli-0.2.0.dist-info/sboms/knf-cli.cyclonedx.json,sha256=qvONzCt_RDETNPF_ezXUUaFGm8U6skgi9JeEjEw_kuc,43501
6
+ knf_cli-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.14.1)
3
+ Root-Is-Purelib: false
4
+ Tag: py3-none-win_amd64
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bernardo Porto Veronese
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.