conclude 1.0.2__tar.gz → 1.1.0__tar.gz
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.
- {conclude-1.0.2 → conclude-1.1.0}/CHANGELOG.md +22 -0
- {conclude-1.0.2 → conclude-1.1.0}/PKG-INFO +1 -1
- {conclude-1.0.2 → conclude-1.1.0}/docs/comparison.md +20 -20
- {conclude-1.0.2 → conclude-1.1.0}/docs/concept.md +113 -57
- {conclude-1.0.2 → conclude-1.1.0}/docs/guide.md +10 -1
- {conclude-1.0.2 → conclude-1.1.0}/docs/reference.md +10 -2
- conclude-1.1.0/spec/README.md +76 -0
- {conclude-1.0.2 → conclude-1.1.0}/spec/casters.json +1 -1
- conclude-1.1.0/spec/cli.json +771 -0
- {conclude-1.0.2 → conclude-1.1.0}/spec/config_layers.json +1 -1
- {conclude-1.0.2 → conclude-1.1.0}/spec/config_tables.json +1 -1
- {conclude-1.0.2 → conclude-1.1.0}/spec/dotenv.json +1 -1
- {conclude-1.0.2 → conclude-1.1.0}/spec/gitignore.json +1 -1
- {conclude-1.0.2 → conclude-1.1.0}/spec/guard.json +1 -1
- {conclude-1.0.2 → conclude-1.1.0}/spec/invocation.json +80 -4
- {conclude-1.0.2 → conclude-1.1.0}/spec/merge.json +1 -1
- {conclude-1.0.2 → conclude-1.1.0}/spec/naming.json +49 -2
- {conclude-1.0.2 → conclude-1.1.0}/spec/sources.json +1 -1
- {conclude-1.0.2 → conclude-1.1.0}/spec/templates.json +41 -3
- {conclude-1.0.2 → conclude-1.1.0}/src/conclude/__init__.py +10 -5
- {conclude-1.0.2 → conclude-1.1.0}/src/conclude/app.py +33 -7
- conclude-1.1.0/src/conclude/cli.py +66 -0
- {conclude-1.0.2 → conclude-1.1.0}/src/conclude/formatters.py +27 -11
- {conclude-1.0.2 → conclude-1.1.0}/src/conclude/naming.py +14 -0
- {conclude-1.0.2 → conclude-1.1.0}/src/conclude/templates.py +10 -3
- {conclude-1.0.2 → conclude-1.1.0}/test/test_app.py +85 -1
- {conclude-1.0.2 → conclude-1.1.0}/test/test_formatters.py +3 -1
- {conclude-1.0.2 → conclude-1.1.0}/test/test_repo_hygiene.py +123 -4
- {conclude-1.0.2 → conclude-1.1.0}/test/test_spec.py +22 -2
- {conclude-1.0.2 → conclude-1.1.0}/test/test_templates.py +9 -1
- conclude-1.0.2/spec/README.md +0 -72
- {conclude-1.0.2 → conclude-1.1.0}/.gitignore +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/LICENSE +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/README.md +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/RELEASING.md +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/hatch_build.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/pyproject.toml +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/src/conclude/casters.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/src/conclude/developer.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/src/conclude/env.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/src/conclude/files.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/src/conclude/guard.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/src/conclude/infer.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/src/conclude/merge.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/src/conclude/paths.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/src/conclude/py.typed +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/src/conclude/tomlwrite.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/test/test_casters.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/test/test_comparison.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/test/test_developer.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/test/test_docs.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/test/test_dotenv_guard.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/test/test_env.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/test/test_files.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/test/test_guard.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/test/test_infer.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/test/test_merge.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/test/test_naming.py +0 -0
- {conclude-1.0.2 → conclude-1.1.0}/test/test_tomlwrite_and_paths.py +0 -0
|
@@ -4,6 +4,28 @@ All notable changes to this project are documented in this file. The
|
|
|
4
4
|
format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
5
|
and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [1.1.0] - 2026-10-04
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **A negation for every boolean flag.** A `bool` setting gets `--no-flag` as
|
|
14
|
+
well as `--flag` (the new `NegatableFlag` action): `--no-flag` stores
|
|
15
|
+
`False`, the last one given wins, and giving neither leaves the setting unset.
|
|
16
|
+
A key that already starts with `no_` is turned off by dropping it
|
|
17
|
+
(`no_color`: `--no-color` on, `--color` off). New: `cli_negated_flag_name`,
|
|
18
|
+
`NegatableFlag`, `NEGATED`, and the `conclude.cli` module.
|
|
19
|
+
- Two settings that claim the same flag (`cache` and `no_cache`) now raise
|
|
20
|
+
`ValueError` from `add_arguments`, `format_cli` and `format_invocation`.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- `format_invocation` writes a bool that is off as its negation, so a bool whose
|
|
25
|
+
default is `True` reproduces (`format_bool(False)` is now `NEGATED`, not
|
|
26
|
+
`None`); `format_cli` shows the flag that changes a bool's default.
|
|
27
|
+
- Conforms to spec version 2 (new `spec/cli.json`).
|
|
28
|
+
|
|
7
29
|
## [1.0.2] - 2026-09-26
|
|
8
30
|
|
|
9
31
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: conclude
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Set up your defaults; conclude infers the env vars, config-file keys, and CLI flags, and resolves them all into one settings object.
|
|
5
5
|
Project-URL: Homepage, https://github.com/tanakapayam/conclude
|
|
6
6
|
Project-URL: Documentation, https://github.com/tanakapayam/conclude/blob/main/docs/python/guide.md
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
A feature-by-feature comparison with five libraries people often reach
|
|
4
4
|
for instead. **Verified 2026-09-19** against each project's
|
|
5
5
|
documentation and its PyPI metadata; the versions checked are
|
|
6
|
-
conclude 1.0
|
|
6
|
+
conclude 1.1.0, ConfigArgParse 1.7.7, jsonargparse 4.52.0,
|
|
7
7
|
pydantic-settings 2.15.0, Dynaconf 3.3.5 and python-decouple 3.8.
|
|
8
8
|
|
|
9
9
|
A dash (--) means no built-in support that the project's documentation
|
|
@@ -14,29 +14,29 @@ the README; this page is the detail.
|
|
|
14
14
|
|
|
15
15
|
## What you write
|
|
16
16
|
|
|
17
|
-
| conclude
|
|
18
|
-
|
|
|
17
|
+
| conclude | ConfigArgParse | jsonargparse | pydantic-settings | Dynaconf | python-decouple |
|
|
18
|
+
| ------------------ | -------------------------- | --------------------------------------------- | ---------------------- | ---------------------------------------- | --------------------------- |
|
|
19
19
|
| a dict of defaults | an `add()` call per option | type hints on functions, classes, dataclasses | a pydantic model class | settings files, plus optional validators | a `config()` call per value |
|
|
20
20
|
|
|
21
21
|
## Capabilities
|
|
22
22
|
|
|
23
|
-
| Capability | conclude | ConfigArgParse | jsonargparse
|
|
24
|
-
| ----------------------------------------------------- | --------------------------------- | --------------------------------------- |
|
|
25
|
-
| Environment variables | yes | yes | yes
|
|
26
|
-
| Your program's CLI flags | yes | yes | yes
|
|
27
|
-
| TOML files | yes | yes (`toml` extra) | yes (`toml` extra)
|
|
28
|
-
| YAML, JSON or INI files | -- | yes (YAML, INI) | yes (YAML, JSON)
|
|
29
|
-
| `.env` files | yes | -- | --
|
|
30
|
-
| Sources layered, precedence documented | yes | yes | yes
|
|
31
|
-
| Where the config files live | system, user and project built in | you list the paths | you list the paths
|
|
32
|
-
| Names inferred from the declaration | flags, env vars and config keys | config keys | flags and config keys; env vars with `default_env` | env vars, flags and TOML keys, from field names | --
|
|
33
|
-
| Config templates or reference generated from it | env, TOML and CLI templates | `-h` lists env vars and config keys | `--print_config` (YAML)
|
|
34
|
-
| Local file above the environment, built in | yes | -- | --
|
|
35
|
-
| Verifies a private file is gitignored before reading | yes | -- | --
|
|
36
|
-
| Schema validation of values | -- (casting only) | -- (argparse `type=`, `choices`) | yes (type hints)
|
|
37
|
-
| External secret backends | -- | -- | --
|
|
38
|
-
| Named environments (development, production, ...)
|
|
39
|
-
| Hard runtime dependencies (PyPI metadata) | 0 | 0 | 1 (PyYAML)
|
|
23
|
+
| Capability | conclude | ConfigArgParse | jsonargparse | pydantic-settings | Dynaconf | python-decouple |
|
|
24
|
+
| ----------------------------------------------------- | --------------------------------- | --------------------------------------- | -------------------------------------------------- | ----------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------- |
|
|
25
|
+
| Environment variables | yes | yes | yes | yes | yes | yes |
|
|
26
|
+
| Your program's CLI flags | yes | yes | yes | yes (opt in) | -- (its CLI manages settings files) | -- |
|
|
27
|
+
| TOML files | yes | yes (`toml` extra) | yes (`toml` extra) | yes (`toml` extra) | yes | -- |
|
|
28
|
+
| YAML, JSON or INI files | -- | yes (YAML, INI) | yes (YAML, JSON) | yes (YAML, JSON) | yes (YAML, JSON, INI) | yes (INI) |
|
|
29
|
+
| `.env` files | yes | -- | -- | yes | yes | yes |
|
|
30
|
+
| Sources layered, precedence documented | yes | yes | yes | yes (reorderable) | yes | yes (env, then file, then default) |
|
|
31
|
+
| Where the config files live | system, user and project built in | you list the paths | you list the paths | you list the paths | you list the paths | searched up from your module |
|
|
32
|
+
| Names inferred from the declaration | flags, env vars and config keys | config keys | flags and config keys; env vars with `default_env` | env vars, flags and TOML keys, from field names | -- | -- |
|
|
33
|
+
| Config templates or reference generated from it | env, TOML and CLI templates | `-h` lists env vars and config keys | `--print_config` (YAML) | -- (a model can export JSON Schema) | `dynaconf init` scaffolds files | -- |
|
|
34
|
+
| Local file above the environment, built in | yes | -- | -- | possible by reordering sources | -- | -- |
|
|
35
|
+
| Verifies a private file is gitignored before reading | yes | -- | -- | -- | -- (`init` adds `.secrets.*` to `.gitignore`; no load-time check documented) | -- |
|
|
36
|
+
| Schema validation of values | -- (casting only) | -- (argparse `type=`, `choices`) | yes (type hints) | yes | yes (validators) | -- (casting only) |
|
|
37
|
+
| External secret backends | -- | -- | -- | yes (AWS, Azure, GCP) | yes (Vault, Redis) | -- |
|
|
38
|
+
| Named environments (development, production, ...) | -- | -- | -- | -- | yes | -- |
|
|
39
|
+
| Hard runtime dependencies (PyPI metadata) | 0 | 0 | 1 (PyYAML) | 3 | 0 | 0 |
|
|
40
40
|
|
|
41
41
|
## Reading the table
|
|
42
42
|
|
|
@@ -8,8 +8,8 @@ is the reference implementation ([guide](python/guide.md), [reference](python/re
|
|
|
8
8
|
other implementations follow this document and pass the conformance
|
|
9
9
|
fixtures in [`spec/`](../spec/README.md).
|
|
10
10
|
|
|
11
|
-
This is **concept version
|
|
12
|
-
(the Python
|
|
11
|
+
This is **concept version 2**, which is **spec version 2** of the fixtures
|
|
12
|
+
(the Python, TypeScript and Bash packages implement it). The words "must" and "may" are
|
|
13
13
|
meant literally: a conforming implementation does what "must" says, and
|
|
14
14
|
anything a sentence does not require is up to the implementation.
|
|
15
15
|
|
|
@@ -46,13 +46,13 @@ Settings are ordered (declaration order): generated templates keep it.
|
|
|
46
46
|
|
|
47
47
|
## 3. Declaring settings
|
|
48
48
|
|
|
49
|
-
| Token
|
|
50
|
-
|
|
|
51
|
-
| `bool`
|
|
52
|
-
| `int`
|
|
53
|
-
| `float` | a number
|
|
54
|
-
| `str`
|
|
55
|
-
| `list`
|
|
49
|
+
| Token | Meaning | Casts to |
|
|
50
|
+
| ------- | ----------------------------------------------------- | --------------------------------------------------- |
|
|
51
|
+
| `bool` | a flag | true or false |
|
|
52
|
+
| `int` | a number that prints as a whole number when it is one | an integer, or a fraction if that is what was given |
|
|
53
|
+
| `float` | a number | a number |
|
|
54
|
+
| `str` | text | text, or unset |
|
|
55
|
+
| `list` | a list of text items | a list of text, or unset |
|
|
56
56
|
|
|
57
57
|
The declared type -- not the runtime shape of the default -- decides the
|
|
58
58
|
caster and how a default is rendered in a template. A language that can
|
|
@@ -69,10 +69,23 @@ from `3.0` (JavaScript) must let the author say it.
|
|
|
69
69
|
setting may override its name.
|
|
70
70
|
- **CLI flag**: `--` and the key with `_` replaced by `-`; case is kept.
|
|
71
71
|
`filter_col` is `--filter-col`.
|
|
72
|
+
- **Negated CLI flag** (a `bool` only): the flag that sets it to false.
|
|
73
|
+
It is `--no-` and the flag's name: `debug` is `--no-debug`. A flag name
|
|
74
|
+
that already starts with `no-` and continues drops that `no-` instead of
|
|
75
|
+
stacking another, so the opposite of `--no-color` (the setting `no_color`)
|
|
76
|
+
is `--color`, not `--no-no-color`. A setting that is not a `bool` has no
|
|
77
|
+
negation.
|
|
72
78
|
|
|
73
79
|
Two application names that differ only in which non-identifier character
|
|
74
80
|
they use sanitize to the same prefix; that is a known, accepted limit.
|
|
75
81
|
|
|
82
|
+
No two settings may claim the same CLI flag, counting each `bool`'s negation
|
|
83
|
+
as a flag of its own: a `bool` `cache` and a setting `no_cache` both want
|
|
84
|
+
`--no-cache` (and, if `no_cache` is a `bool` too, `--cache` as well), and
|
|
85
|
+
two keys that differ only in `_` against `-` want one flag. An implementation
|
|
86
|
+
reports this as an error when the flags are derived, not by silently letting
|
|
87
|
+
one setting shadow the other (`cli.json`).
|
|
88
|
+
|
|
76
89
|
## 5. Casting (`casters.json`)
|
|
77
90
|
|
|
78
91
|
Every layer's value passes through the caster for its setting's declared
|
|
@@ -128,11 +141,11 @@ process environment, with an optional `.env` file beneath it (section 8);
|
|
|
128
141
|
|
|
129
142
|
## 7. Config files (`config_layers.json`, `config_tables.json`)
|
|
130
143
|
|
|
131
|
-
| Layer
|
|
132
|
-
|
|
|
133
|
-
| system
|
|
134
|
-
| user
|
|
135
|
-
| project
|
|
144
|
+
| Layer | Location | On by default? |
|
|
145
|
+
| ---------------- | ---------------------------------- | --------------------------------------------------------------- |
|
|
146
|
+
| system | `/etc/<name>/config.toml` | no (opt-in) |
|
|
147
|
+
| user | `~/.config/<name>/config.toml` | yes |
|
|
148
|
+
| project | `./.config.toml` | yes |
|
|
136
149
|
| project siblings | `./.config.*.toml`, sorted by name | yes (the pattern is configurable; it can be switched off alone) |
|
|
137
150
|
|
|
138
151
|
The files are merged key by key in that order, so a key set in a later file
|
|
@@ -279,8 +292,10 @@ and every other control character (U+0000-U+001F and U+007F) as `\uXXXX` with
|
|
|
279
292
|
upper-case hexadecimal. An unset setting is `# key =`. An empty table path
|
|
280
293
|
means no header.
|
|
281
294
|
|
|
282
|
-
**CLI reference**: one line per
|
|
283
|
-
|
|
295
|
+
**CLI reference**: one line per setting. A `bool` is the bare flag that
|
|
296
|
+
*changes* its default -- `--flag` when the default is false, the negation
|
|
297
|
+
(`--no-flag`) when it is true, and `--flag | --no-flag` when it has none, as
|
|
298
|
+
either one changes that; any other setting is the flag and `<METAVAR>` (the key upper-cased, or the override).
|
|
284
299
|
Each line ends with `(default: text)`, where an unset default reads `none`,
|
|
285
300
|
an empty text reads `""`, and the flag column is padded to the widest so the
|
|
286
301
|
`(default:` parts align, with one space after it.
|
|
@@ -289,10 +304,10 @@ an empty text reads `""`, and the flag column is padded to the widest so the
|
|
|
289
304
|
a standalone command line that gets back to it with no environment variables,
|
|
290
305
|
config files or shorthand -- what a `--print-invocation` flag prints. Settings
|
|
291
306
|
are visited in declaration order, and each is written as `--flag=value` (a bare
|
|
292
|
-
`--flag` for a `bool` that is on
|
|
307
|
+
`--flag` for a `bool` that is on, and its negation `--no-flag` for one that is
|
|
308
|
+
off) unless it is left out. A setting is left out
|
|
293
309
|
when it equals its default -- omitting a flag already reproduces its default, and
|
|
294
|
-
this covers every setting automatically -- when
|
|
295
|
-
is no flag to turn one off), when its value is unset, or when the caller skips it
|
|
310
|
+
this covers every setting automatically -- when its value is unset, or when the caller skips it
|
|
296
311
|
(`skip` beats `alwaysInclude`, which forces a setting that equals its default to
|
|
297
312
|
be written). `compareDefaults` replaces what counts as each setting's default for
|
|
298
313
|
one call, for a setting whose effective default is substituted after resolving.
|
|
@@ -328,55 +343,93 @@ for an inactive file.
|
|
|
328
343
|
|
|
329
344
|
## 13. Conformance
|
|
330
345
|
|
|
331
|
-
An implementation conforms to spec version
|
|
346
|
+
An implementation conforms to spec version 2 when it passes every fixture in
|
|
332
347
|
[`spec/`](../spec/README.md). The fixtures are data, not code; each
|
|
333
348
|
implementation writes a small adapter from a fixture file to its own API.
|
|
334
349
|
|
|
335
|
-
| Fixture
|
|
336
|
-
|
|
|
337
|
-
| `naming.json`
|
|
338
|
-
| `casters.json`
|
|
339
|
-
| `dotenv.json`
|
|
340
|
-
| `merge.json`
|
|
341
|
-
| `config_layers.json` |
|
|
342
|
-
| `config_tables.json` |
|
|
343
|
-
| `templates.json`
|
|
344
|
-
| `invocation.json`
|
|
345
|
-
| `
|
|
346
|
-
| `
|
|
347
|
-
| `
|
|
350
|
+
| Fixture | Sections | What it covers |
|
|
351
|
+
| -------------------- | -------: | ------------------------------------------------------- |
|
|
352
|
+
| `naming.json` | 4 | environment variable, flag, negated flag and key names |
|
|
353
|
+
| `casters.json` | 3, 5 | casting for every declared type |
|
|
354
|
+
| `dotenv.json` | 8 | the `.env` dialect |
|
|
355
|
+
| `merge.json` | 6 | precedence, no-opinion, empty values, errors |
|
|
356
|
+
| `config_layers.json` | 7 | merging system, user, project and sibling files; tables |
|
|
357
|
+
| `config_tables.json` | 7 | table selection and the positional shorthand |
|
|
358
|
+
| `templates.json` | 11 | the environment, TOML and CLI templates |
|
|
359
|
+
| `invocation.json` | 11 | reproducing a resolved configuration as a command line |
|
|
360
|
+
| `cli.json` | 4 | what a command line gives a setting; flag collisions |
|
|
361
|
+
| `gitignore.json` | 9 | rule matching, judged by real `git` |
|
|
362
|
+
| `guard.json` | 9 | the guard's checks and reasons |
|
|
363
|
+
| `sources.json` | 10, 12 | the report of which sources are in play |
|
|
348
364
|
|
|
349
365
|
Not yet covered by fixtures: how each binding reads its manifest (section 10;
|
|
350
366
|
the wording of a `not configured` reason is binding-specific), and how a
|
|
351
|
-
language
|
|
367
|
+
language hands its flags to its CLI parser (`cli.json` pins only what a command
|
|
368
|
+
line gives each setting, not the parser's own help or error text).
|
|
352
369
|
|
|
353
370
|
## 14. What varies by language
|
|
354
371
|
|
|
355
|
-
| Concern
|
|
356
|
-
|
|
|
357
|
-
| Declaring settings
|
|
358
|
-
| Type inference
|
|
359
|
-
| Key style in the API
|
|
360
|
-
|
|
|
361
|
-
|
|
|
362
|
-
|
|
|
363
|
-
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
372
|
+
| Concern | Python ([`python/`](../python/README.md)) | TypeScript ([`node/`](../node/README.md)) | Bash ([`bash/`](../bash/README.md)) |
|
|
373
|
+
| ----------------------------- | ----------------------------------------- | ----------------------------------------- | ----------------------------------- |
|
|
374
|
+
| Declaring settings | a dict of defaults; `opt(type)` for an unset one | an object of defaults, with `int()`, `float()`, `str()`, ... for numbers and unset settings | `conclude_define app key type [default]`; a 3-argument call (no default) and a 4-argument call with `""` are distinct |
|
|
375
|
+
| Type inference | the default's runtime type | the default's runtime type, plus explicit `int` and `float` (D2) | always explicit -- Bash has no runtime types to infer from |
|
|
376
|
+
| Key style in the API | `snake_case` | `camelCase`, converted (D1) | whatever the app declares; there is no separate API casing to convert from |
|
|
377
|
+
| Custom casters/formatters | yes, per setting or app-wide | no | no |
|
|
378
|
+
| System config / `.env` layers | built in, opt-in (`AUTO`-able) | built in, opt-in (`AUTO`-able) | not wired into `conclude_resolve`; build them from the public primitives (guide section 7) |
|
|
379
|
+
| Manifest binding | `pyproject.toml`, `[tool.conclude.developer] config = "..."` | `package.json`, `"conclude": { "developer": { "config": "..." } }` | none (D3) -- `--developer-file`/`--developer-opt-in`, optionally resolved through a personal `~/.config/conclude/control.toml` |
|
|
380
|
+
| CLI | builds `argparse` flags | flag definitions in `node:util.parseArgs`' shape, plus a thin `parseArgs` wrapper | owns parsing itself (D4) |
|
|
381
|
+
| `--help` | automatic (via `argparse`), with per-flag custom text | none built in | opt-in (`conclude_resolve --help-flag`), assembled from the same declarations, not per-flag customizable |
|
|
382
|
+
| TOML | standard library | a small dependency | hand-written; a deliberate subset (no arrays, two table levels) (D5) |
|
|
383
|
+
| Gitignore matching | the optional `pathspec` extra | an optional dependency | the real `git`, required rather than optional (D5) |
|
|
384
|
+
|
|
385
|
+
**Decisions for other bindings.** The TypeScript package follows D1, D2 and D3
|
|
386
|
+
as written; Bash's own shape is noted inline above and in D3-D5 below, since a
|
|
387
|
+
language with no object/class API and no package-manifest convention cannot
|
|
388
|
+
follow some of these the same way:
|
|
367
389
|
|
|
368
390
|
- **D1: canonical names in files and the environment.** Config-file keys and
|
|
369
391
|
environment variable names use the canonical `snake_case` of section 4, so
|
|
370
392
|
one config file or `.env` serves every implementation; a binding's API may
|
|
371
|
-
use its own casing and converts (`filterCol` is `filter_col`).
|
|
393
|
+
use its own casing and converts (`filterCol` is `filter_col`). Bash has no
|
|
394
|
+
such conversion to make -- a setting's key *is* its name everywhere, with no
|
|
395
|
+
separate in-language identifier style sitting above it.
|
|
372
396
|
- **D2: numeric kinds are explicit** where the language cannot distinguish
|
|
373
|
-
them, and templates render per declared kind (section 11).
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
- **
|
|
377
|
-
|
|
397
|
+
them, and templates render per declared kind (section 11). Bash's casters
|
|
398
|
+
take this further: every setting's type is explicit, numeric or not, since
|
|
399
|
+
Bash values carry no runtime type for a default to be inferred from.
|
|
400
|
+
- **D3: each ecosystem uses its own manifest** to name the developer file; in
|
|
401
|
+
a repository with several, they may name the same file. Bash has no
|
|
402
|
+
manifest convention to use, so it has none: an app names the file directly
|
|
403
|
+
with `--developer-file`, or opts in with no opinion of its own
|
|
404
|
+
(`--developer-opt-in`) and leaves it to a personal, per-user
|
|
405
|
+
`~/.config/conclude/control.toml` (or `.developer.toml` if even that says
|
|
406
|
+
nothing) -- see [`conclude_resolve_developer_file`](bash/reference.md) in
|
|
407
|
+
the Bash reference. This file is Bash's own addition, not a
|
|
408
|
+
cross-language concept; another binding is free to do something else
|
|
409
|
+
entirely; it is not what D3 means by "manifest."
|
|
410
|
+
- **D4: no bundled CLI parser.** Python and Node each expose derived flag
|
|
411
|
+
definitions for an external parser (`argparse`, `node:util.parseArgs`) to
|
|
412
|
+
own, rather than parsing the command line themselves. Bash has no
|
|
413
|
+
ecosystem-standard parser to defer to, so `conclude_resolve` necessarily
|
|
414
|
+
owns this step itself -- a deliberate departure from D4, not an oversight.
|
|
415
|
+
Its CLI vocabulary is narrower than `argparse`'s as a result: `--flag
|
|
416
|
+
value`, `--flag=value`, a bare `--flag` for a bool with its negation
|
|
417
|
+
`--no-flag` (section 4), and no abbreviation.
|
|
378
418
|
- **D5: dependencies are minimal.** A TOML parser is required; gitignore
|
|
379
|
-
matching is optional, and its absence is a setup error (section 9).
|
|
419
|
+
matching is optional, and its absence is a setup error (section 9). Bash
|
|
420
|
+
goes further on the first (no library at all -- a hand-written reader
|
|
421
|
+
scoped to exactly what section 7 requires, which is also why it cannot read
|
|
422
|
+
a TOML array or a table nested past two levels) and differs on the second
|
|
423
|
+
(`git` is required outright, not an optional extra, since Bash has nothing
|
|
424
|
+
to fall back to without it).
|
|
425
|
+
|
|
426
|
+
Help generation is not yet a cross-language policy, so it has no lettered
|
|
427
|
+
decision above: Python gets a full `-h`/`--help` automatically from
|
|
428
|
+
`argparse`; Node has nothing built in, and an app using it builds one from
|
|
429
|
+
`formatCli` itself; Bash's `conclude_format_help` is opt-in and assembled from
|
|
430
|
+
the same declarations every other template reads from, with no per-flag
|
|
431
|
+
custom text the way `argparse`'s `help=` allows. If another binding adds
|
|
432
|
+
something similar later, this section is where that would get written down.
|
|
380
433
|
|
|
381
434
|
## 15. Implementation-defined
|
|
382
435
|
|
|
@@ -388,10 +441,13 @@ and should document what they do:
|
|
|
388
441
|
- line breaks other than `\n`, `\r\n` and `\r` in a `.env` file;
|
|
389
442
|
- how a very small or very large `float` is written (exponent form) in a
|
|
390
443
|
template;
|
|
391
|
-
- reproducing a run for a
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
444
|
+
- reproducing a run for a non-`bool` setting whose resolved value is unset while
|
|
445
|
+
its default is not (Python writes `None`; the TypeScript binding leaves it
|
|
446
|
+
out);
|
|
447
|
+
- abbreviated flags (`--fil` for `--filter-col`: `argparse` accepts them, the
|
|
448
|
+
others do not), and a value that starts with `-` given as a separate argument
|
|
449
|
+
(`--port -5`: `argparse` takes it, `node:util.parseArgs` rejects it; the
|
|
450
|
+
`--port=-5` form, which reproducing a run writes, works everywhere);
|
|
395
451
|
- a default of empty text on a `str` setting when it is *resolved* (the
|
|
396
452
|
template shows it; the merge casts it to unset);
|
|
397
453
|
- what a boolean, a list or another non-text value does when cast to `str`,
|
|
@@ -140,6 +140,14 @@ config_file = app.load_config_files(table_path)
|
|
|
140
140
|
settings = app.resolve(cli, config_file=config_file)
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
+
A `bool` setting's flag is bare (`--repeat`) and comes with a negation, `--no-repeat`,
|
|
144
|
+
that sets it to `False` -- so a setting whose default is `True` can be turned off
|
|
145
|
+
from the command line, and the last of the two on the line wins. (A key that
|
|
146
|
+
already starts with `no_`, like `no_color`, is turned off by dropping the `no`:
|
|
147
|
+
`--color`.) Leaving both out is no opinion, so a lower layer decides. Two settings
|
|
148
|
+
may not claim the same flag -- `cache` and `no_cache` both want `--no-cache` --
|
|
149
|
+
and `add_arguments` raises `ValueError` if they do.
|
|
150
|
+
|
|
143
151
|
With this in `.config.toml`:
|
|
144
152
|
|
|
145
153
|
```toml
|
|
@@ -665,7 +673,8 @@ A few things worth knowing:
|
|
|
665
673
|
the TOML header is the app's `[default_table]` (pass `table="mom"` or
|
|
666
674
|
`table=["remind", "mom"]` for another, or `header=False` for just the
|
|
667
675
|
key lines), and the CLI lines are exactly the flags `add_arguments`
|
|
668
|
-
adds -- a bool is
|
|
676
|
+
adds -- a bool is the bare flag that *changes* its default (`--flag`, or
|
|
677
|
+
`--no-flag` for a default of true), everything else shows its `<METAVAR>`
|
|
669
678
|
(pass the same `overrides=` you gave `add_arguments` if you renamed
|
|
670
679
|
one).
|
|
671
680
|
- **No default means a placeholder.** A setting that starts out unset
|
|
@@ -47,7 +47,7 @@ Computed from the fields above, never stored: `resolved_defaults`,
|
|
|
47
47
|
|
|
48
48
|
| Group | Method | Purpose |
|
|
49
49
|
| ----------------- | --------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
50
|
-
| Build a CLI | `add_arguments(parser)` | Add one inferred flag per setting to an existing `ArgumentParser` (`skip=`/`overrides=`).
|
|
50
|
+
| Build a CLI | `add_arguments(parser)` | Add one inferred flag per setting -- and a `--no-` negation for every `bool` -- to an existing `ArgumentParser` (`skip=`/`overrides=`). |
|
|
51
51
|
| | `build_arg_parser()` | A fresh parser with those flags already added. |
|
|
52
52
|
| | `add_print_invocation_argument()` | Add the conventional `--print-invocation` flag ([section 5](guide.md#5-debugging-and-documentation---print-invocation)). |
|
|
53
53
|
| Read a layer | `load_env()` | The environment layer (and the `.env` fallback, if opted in). |
|
|
@@ -98,6 +98,7 @@ Computed from the fields above, never stored: `resolved_defaults`,
|
|
|
98
98
|
| Function | Purpose |
|
|
99
99
|
| ---------------------------- | ----------------------------------------------------------- |
|
|
100
100
|
| `cli_flag_name(key)` | `"filter_col"` -> `"--filter-col"`. |
|
|
101
|
+
| `cli_negated_flag_name(key)` | The flag that turns a `bool` off: `"debug"` -> `"--no-debug"`; `"no_color"` -> `"--color"`. |
|
|
101
102
|
| `env_var_name(app_name, key)` | `("remind", "retries")` -> `"REMIND_RETRIES"`. |
|
|
102
103
|
| `config_key_name(key)` | The config-file key -- just `key` itself. |
|
|
103
104
|
|
|
@@ -116,10 +117,17 @@ Computed from the fields above, never stored: `resolved_defaults`,
|
|
|
116
117
|
| ------------------------------------ | ------------------------------------------------------ |
|
|
117
118
|
| `infer_formatter(type_)` | The formatter for a bare type. |
|
|
118
119
|
| `infer_formatters(defaults, overrides)` | A formatter for every key, with your overrides applied. |
|
|
119
|
-
| `format_bool(value)` | `True` -> the bare flag; `False` -> not rendered.
|
|
120
|
+
| `format_bool(value)` | `True` -> the bare flag; `False` -> `NEGATED` (the flag's `--no-` form); `None` -> not rendered. |
|
|
120
121
|
| `format_list(value)` | Shell-quoted, comma-joined. |
|
|
121
122
|
| `format_scalar(value)` | Shell-quoted `str(value)`. |
|
|
122
123
|
|
|
124
|
+
**`conclude.cli`** -- the command-line surface derived from the settings.
|
|
125
|
+
|
|
126
|
+
| Name | Purpose |
|
|
127
|
+
| ---- | ------- |
|
|
128
|
+
| `NegatableFlag` | The argparse action behind a `bool`'s flag: `--flag` stores `True`, `--no-flag` stores `False`, last one wins, neither leaves it unset. |
|
|
129
|
+
| `cli_flags(defaults, skip=())` | `(key, flag, is_negation)` for every flag the settings get; raises `ValueError` if two settings claim one. |
|
|
130
|
+
|
|
123
131
|
**`conclude.casters`** -- reusable casters (inference picks from these; they also work written out by hand).
|
|
124
132
|
|
|
125
133
|
| Function | Purpose |
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Conformance fixtures
|
|
2
|
+
|
|
3
|
+
Language-neutral test data for [the conclude concept](../docs/concept.md).
|
|
4
|
+
Every file is plain JSON: inputs and expected outputs, with no code in it,
|
|
5
|
+
so any implementation -- the Python package in this repository, a
|
|
6
|
+
TypeScript one, anything else -- can run the same cases and be held to the
|
|
7
|
+
same behavior. **Spec version 2.**
|
|
8
|
+
|
|
9
|
+
The Python implementation runs them in
|
|
10
|
+
[`python/test/test_spec.py`](../python/test/test_spec.py), which is also the best
|
|
11
|
+
example of an adapter: a small function per fixture file that turns each
|
|
12
|
+
case into a call on the implementation and compares the result.
|
|
13
|
+
|
|
14
|
+
## Who runs them
|
|
15
|
+
|
|
16
|
+
| Implementation | Harness |
|
|
17
|
+
| ------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
18
|
+
| Python (the reference implementation) | [`python/test/test_spec.py`](../python/test/test_spec.py) |
|
|
19
|
+
| TypeScript / Node.js | [`node/test/spec.test.ts`](../node/test/spec.test.ts) |
|
|
20
|
+
| Bash | [`bash/test/*.bats`](../bash/test) -- one file per fixture (`cli.bats` for `cli.json`, and so on) |
|
|
21
|
+
|
|
22
|
+
The Python and TypeScript harnesses also check that they run every `*.json`
|
|
23
|
+
file here, so a new fixture cannot be added without them learning about it.
|
|
24
|
+
The Bash tests are one file per fixture, so a new fixture needs a new `.bats`
|
|
25
|
+
file by hand.
|
|
26
|
+
|
|
27
|
+
## Format
|
|
28
|
+
|
|
29
|
+
Each file is `{"spec_version": 1, "description": "...", "cases": [...]}`,
|
|
30
|
+
and every case has a unique `name`. A case that must fail carries
|
|
31
|
+
`"error": true` instead of an expected value; an expected `null` means
|
|
32
|
+
"unset".
|
|
33
|
+
|
|
34
|
+
A *setting* is `{"key": "port", "type": "int", "default": 8080}` -- `type`
|
|
35
|
+
is one of `bool`, `int`, `float`, `str`, `list`, and a `null` default means
|
|
36
|
+
the setting starts out unset. Settings are given as arrays so their order
|
|
37
|
+
is unambiguous. Declaring the type explicitly (rather than inferring it
|
|
38
|
+
from the default) keeps the fixtures usable from a language where `3` and
|
|
39
|
+
`3.0` are the same number.
|
|
40
|
+
|
|
41
|
+
| File | What it pins down | Case fields |
|
|
42
|
+
| -------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
43
|
+
| `naming.json` | env var, CLI flag, negated CLI flag and config key for a setting | `app`, `key`; `env_var`, `cli_flag`, `cli_negated_flag`, `config_key` |
|
|
44
|
+
| `casters.json` | casting a raw value to a declared type | `type`, `input`; `output` or `error` |
|
|
45
|
+
| `dotenv.json` | the `.env` dialect | `text` (the file, UTF-8); `expect` |
|
|
46
|
+
| `merge.json` | layering and precedence | `settings`, `layers` (`config`, `env`, `developer`, `cli`); `expect` or `error` |
|
|
47
|
+
| `config_layers.json` | merging system, user, project and sibling config files | `settings`, `table_path`, `files`, optional `aux_pattern`; `expect` or `error` |
|
|
48
|
+
| `config_tables.json` | choosing which TOML table to read | `op` (`parse` or `resolve`), inputs; `expect` or `error` |
|
|
49
|
+
| `templates.json` | generated env, TOML and CLI templates | `app`, `settings`, optional `options`; `expect` (`env`, `toml`, `cli`) |
|
|
50
|
+
| `gitignore.json` | whether gitignore rules match a path | `files`, `paths` (`path`, `ignored`) |
|
|
51
|
+
| `guard.json` | the gitignore guard for a private file | `files`, `target`, optional `kill_switch_var` and `env`; `expect` (`active`, `reason`) |
|
|
52
|
+
| `invocation.json` | reproducing a resolved configuration as a command line | `settings`, `resolved`, optional `options` (`prog`, `always_include`, `skip`, `compare_defaults`); `expect` (the command line) |
|
|
53
|
+
| `cli.json` | what a command line gives a setting, and flag collisions | `settings`, `argv`; `expect` (the CLI layer) or `error` |
|
|
54
|
+
| `sources.json` | the report of which sources are in play | `app`, `options` (the sources configured), `files`, optional `env`; `expect` (the whole report) |
|
|
55
|
+
|
|
56
|
+
The top-level fields of each file, and the meaning of every option, are in
|
|
57
|
+
its `description`.
|
|
58
|
+
|
|
59
|
+
## Rules for the fixtures
|
|
60
|
+
|
|
61
|
+
- **Expected values are written by hand from the intended behavior**, not
|
|
62
|
+
recorded from an implementation, so the fixtures can disagree with one --
|
|
63
|
+
that is their job. The exception is `gitignore.json`, where every
|
|
64
|
+
verdict comes from `git check-ignore --no-index` (with no global or
|
|
65
|
+
system git configuration); the Python suite re-checks them against a
|
|
66
|
+
real `git` whenever one is installed.
|
|
67
|
+
- **Only intended behavior is pinned down.** Where implementations may
|
|
68
|
+
reasonably differ (numbers beyond 2^53, `inf`, underscores in numerals,
|
|
69
|
+
exotic Unicode line breaks, exponent formatting, ...) there is
|
|
70
|
+
deliberately no case; the concept document lists them under
|
|
71
|
+
"Implementation-defined".
|
|
72
|
+
- **Reason strings are part of the spec** (`guard.json`): they are what a
|
|
73
|
+
person sees when a private file is not being used.
|
|
74
|
+
- **Adding or changing a case is a spec change.** Say so in the concept
|
|
75
|
+
document; bump the spec version only for a change that a conforming
|
|
76
|
+
implementation must react to.
|