Skip to content

config: --init-config CLI redesign + documentation rewrite (CLI half of #13) - #15

Merged
sanohiro merged 3 commits into
sanohiro:mainfrom
kay-ws:feature/config-cli-redesign
May 7, 2026
Merged

config: --init-config CLI redesign + documentation rewrite (CLI half of #13)#15
sanohiro merged 3 commits into
sanohiro:mainfrom
kay-ws:feature/config-cli-redesign

Conversation

@kay-ws

@kay-ws kay-ws commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary

CLI redesign + documentation rewrite — the second half of the two-PR
split agreed in #13. Pairs with #14.

--init-config previously decided between /etc/bcon/ and ~/.config/bcon/
via dirs::config_dir() of the calling user. Under sudo, this silently
writes to /root/.config/bcon/config.toml rather than /etc/bcon/...
the production trap described in #13 (sec. 2 "init-config silently routes
to the caller's home"). This PR replaces the implicit destination with an
explicit target token, and rewrites docs/configuration.{md,ja.md} to
cover the layered model and the new CLI surface.

CLI: target token

The first comma-separated argument of --init-config=... is interpreted
as a WriteTarget when it is user, system, or starts with / or ~/.
Otherwise the entire list is treated as preset names — preserving every
pre-existing destination (legacy --init-config=vim,jp still writes to
~/.config/bcon/config.toml).

Form Destination
bcon --init-config=system /etc/bcon/config.toml
bcon --init-config=user ~/.config/bcon/config.toml
bcon --init-config=/tmp/x.toml /tmp/x.toml (load via BCON_CONFIG)
bcon --init-config=~/foo.toml $HOME/foo.toml (tilde-expanded; load via BCON_CONFIG)

sudo bcon --init-config=system now writes to /etc/bcon/config.toml
deterministically, regardless of the caller's $HOME.

Library API

  • pub enum WriteTarget { User, System, Path(PathBuf) }
  • pub fn parse_init_config_arg(arg: &str) -> (WriteTarget, Vec<String>)
  • Config::write_config_with_preset(target: &WriteTarget, presets: &[&str])
    target no longer threaded through the preset list
  • Config::path_for_target(&WriteTarget) (renamed from
    get_config_path_for_preset)
  • private expand_tilde helper

WriteTarget and parse_init_config_arg are pub so distributors using
Config::default_template() (introduced in #14) can pick a destination
without re-implementing the CLI parser.

Documentation

docs/configuration.{md,ja.md} rewritten end-to-end to cover:

  • the layered XDG model from config: layered XDG-style config discovery (engine + distributors API) #14 (built-in defaults → /etc/bcon/...
    ~/.config/bcon/..., with helix/mpv-style merge semantics)
  • the --init-config target-token syntax, presented as a Destinations
    table (4 forms) and a separate Examples subsection (typical
    invocations that complete with the standard layered load)
  • the BCON_CONFIG env-var bypass (introduced in config: layered XDG-style config discovery (engine + distributors API) #14), documented with
    two use cases: loading an ad-hoc config generated by a path-target
    --init-config, and BCON_CONFIG=/dev/null bcon for clean-defaults
    A/B-comparison
  • a heading rename (Example ConfigConfiguration File Example) so
    the upstream-content section name no longer collides with the new
    ### Examples subsection

CLAUDE.md's Configuration section gains a short pointer to the new
behavior.

Wording in src/config/mod.rs (introduced via #14) is forward-compat'd:
the load_layered_from_paths doc comment ("up to three layers") and the
test name (test_load_three_layer_merge) are renamed to "multi-layer"
versions so future overlays (per-host, per-project, etc.) don't make the
prose stale.

Backward compatibility

  • --init-config (no argument): user destination, unchanged.
  • Preset-only forms (e.g. --init-config=vim,jp): the detection rule
    short-circuits to WriteTarget::User when the first token is not a
    recognized target, so the destination stays at
    ~/.config/bcon/config.toml.
  • --force semantics unchanged.
  • One public-API rename (get_config_path_for_presetpath_for_target)
    and one signature change (write_config_with_preset takes
    &WriteTarget).

Test plan

cargo test --bin bcon config::tests: 11/11 passing (9 inherited
from #14, 2 new).

New test Covers
test_parse_init_config_arg_cases target detection over all CLI shapes (user, system, absolute path, ~/)
test_expand_tilde_basics tilde expansion edge cases

Full cargo test: 53 passed; 0 failed; 1 ignored (seatd,
env-dependent). cargo build --release: clean, no new warnings.

Manual verification

End-to-end verified on a separate Arch VM (PR2 binary deployed alongside
the AUR-installed v1.3.4 as /usr/local/bin/bcon-pr2, original config
files preserved by mv-aside):

  • sudo bcon --init-config=system writes to /etc/bcon/config.toml with
    root:root ownership; no /root/.config/bcon/config.toml is created
    — the production trap from Proposal: Layered XDG-style config discovery (/etc/ + ~/.config/ merge) #13 (sec. 2) is eliminated.
  • bcon --init-config=user,vim,jp writes to
    $XDG_CONFIG_HOME/bcon/config.toml.
  • bcon --init-config=/tmp/x.toml,vim writes to the requested absolute
    path.
  • bcon --init-config=~/foo.toml,vim tilde-expands inside the binary
    (the shell does not expand ~ in =... context, so the literal
    ~/foo.toml reaches parse_init_config_arg and expand_tilde resolves
    it to /home/<user>/foo.toml).

Refs

Refs #13 (CLI half). Pairs with #14.

kay-ws added 3 commits May 4, 2026 18:28
…ig CLI

Adds explicit destination semantics to --init-config so that root-run
invocations don't silently route to /root/.config based on the calling
user's $HOME. Implements the destination-token spec from sanohiro#13: the first
comma-token of --init-config=... is interpreted as a target ("system",
"user", or an absolute or ~/-prefixed path); remaining tokens are preset
names. When the first token is none of those, the destination defaults
to "user", so the legacy form --init-config=vim,jp keeps writing to
~/.config/bcon/config.toml.

Library changes (src/config/mod.rs):
- New `WriteTarget {User, System, Path(PathBuf)}` enum (pub).
- New `parse_init_config_arg(arg: &str) -> (WriteTarget, Vec<String>)`
  free function (pub).
- New `expand_tilde(s: &str) -> PathBuf` private helper.
- `Config::write_config_with_preset` now takes
  `(target: &WriteTarget, presets: &[&str])`. Target is no longer
  threaded through the preset list; display name is `presets.join`
  without the previous "system" filter.
- `Config::get_config_path_for_preset` renamed to
  `Config::path_for_target(target: &WriteTarget)`.
- `Config::write_default_config()` updated for the new signature.

Binary changes (src/main.rs):
- --init-config parser routed through `parse_init_config_arg`.
- --help EXAMPLES and the post-init-config usage hint enumerate the
  four target-token forms (system / user / absolute / tilde) in the
  order matching the spec; preset-only and no-token rows are omitted
  from the example listing as they do not exercise the target path.

Tests (src/config/mod.rs):
- test_parse_init_config_arg_cases: target detection over user, system,
  absolute path, ~/-prefixed path, and preset-only (legacy) input.
- test_expand_tilde_basics: passthrough for absolute paths and ~/
  prepending (guarded when home_dir is None).

cargo test --bin bcon config::tests: 11/11 passing.
…et token

Updates user-facing documentation to describe the layered config
discovery model from sanohiro#14 and the new --init-config target/preset split.

docs/configuration.md and configuration.ja.md:
- "Config File Locations" rewritten as a layered XDG merge (built-in
  defaults <- /etc/bcon/config.toml <- ~/.config/bcon/config.toml)
  with helix/mpv-style semantics (table-recursive, array-replace).
- New "Generating a Config File: --init-config" section with two
  subsections:
    Destinations - 4-row table for system / user / absolute path /
                   tilde-prefixed path (path rows note BCON_CONFIG for
                   loading the resulting file).
    Examples     - typical invocations whose generated file is picked
                   up by the standard layered load.
- New "Single-file bypass: BCON_CONFIG" section, placed after
  --init-config, with two use cases: loading an ad-hoc config
  generated by a path target, and BCON_CONFIG=/dev/null bcon for
  clean-defaults A/B-comparison.
- "system" removed from the preset list since it is now a target,
  not a preset.
- "Example Config" renamed to "Configuration File Example" so the
  upstream-content section name no longer collides with the new
  --init-config "Examples" subsection.

CLAUDE.md:
- Configuration section expanded to mention the layered merge,
  BCON_CONFIG, and the explicit target form of --init-config.

docs/installation.md and installation.ja.md are intentionally
unchanged: their existing --init-config=system,vim,jp invocations
remain valid under the new parser (the comma-list first-token
scheme).
…patibility

The implementation has exactly three layers today (builtin defaults +
/etc/bcon/config.toml + ~/.config/bcon/config.toml), but calling out
the literal number in user-facing docs and identifiers locks us in:
future expansion to XDG_CONFIG_DIRS, conf.d-style splits, or vendor
overlays would make "three" a lie. The more abstract "multi-layer" /
"multiple layers" wording communicates the model without committing
to a fixed count.

- docs/configuration.md: "in three layers (XDG-style merge)" ->
  "across multiple layers (XDG-style merge)".
- docs/configuration.ja.md: 「3 レイヤー」-> 「複数のレイヤー」.
- src/config/mod.rs: load_layered_from_paths doc comment changed
  from "up to three layers" to "multiple layers"; the unit test
  renamed test_load_three_layer_merge -> test_load_multi_layer_merge
  (its body comment updated to match).

cargo test --bin bcon config::tests: 11/11 passing.
No new clippy lints introduced.
@sanohiro
sanohiro merged commit a1cb0ef into sanohiro:main May 7, 2026
1 check passed
@sanohiro

sanohiro commented May 7, 2026

Copy link
Copy Markdown
Owner

Thanks — the root-shadow trap is properly eliminated now. Docs rewrite is thorough too. This closes the #13 proposal cleanly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants