Many CLI applications use clap subcommands to perform different operations.
OrthoConfig supports per‑subcommand defaults via a dedicated cmds
namespace. The helper function load_and_merge_subcommand_for loads defaults
for a specific subcommand and merges them beneath the CLI values. The merged
struct is returned as a new instance; the original cli struct remains
unchanged. CLI fields left unset (None) do not override environment or file
defaults, avoiding accidental loss of configuration.
How it works
When a struct derives OrthoConfig, it also implements the associated
prefix() method. This method returns the configured prefix string.
load_and_merge_subcommand_for(prefix, cli_struct) uses this prefix to build a
cmds.<subcommand> section name for the configuration file and an
PREFIX_CMDS_SUBCOMMAND_ prefix for environment variables. Configuration is
loaded in the same order as global configuration (defaults → file → environment
→ CLI), but only values in the [cmds.<subcommand>] section or environment
variables beginning with PREFIX_CMDS_<SUBCOMMAND>_ are considered.
Example
Suppose an application has a pr subcommand that accepts a reference
argument and a repo global option. With OrthoConfig the argument structures
might be defined as follows:
use clap::Parser;
use ortho_config::OrthoConfig;
use ortho_config::SubcmdConfigMerge;
use serde::{Deserialize, Serialize};
#[derive(Parser, Deserialize, Serialize, Debug, OrthoConfig, Clone, Default)]
#[ortho_config(prefix = "VK")] // all variables start with VK
pub struct GlobalArgs {
pub repo: Option<String>,
}
#[derive(Parser, Deserialize, Serialize, Debug, OrthoConfig, Clone, Default)]
#[ortho_config(prefix = "VK")] // subcommands share the same prefix
pub struct PrArgs {
#[arg(required = true)]
pub reference: Option<String>, // optional for merging defaults but required on the CLI
}
fn main() -> Result<(), ortho_config::OrthoError> {
let cli_pr = PrArgs::parse();
// Merge defaults from [cmds.pr] and VK_CMDS_PR_* over CLI
let merged_pr = cli_pr.load_and_merge()?;
println!("PrArgs after merging: {:#?}", merged_pr);
Ok(())
}
A configuration file might include:
[cmds.pr]
reference = "https://github.com/leynos/mxd/pull/31"
[cmds.issue]
reference = "https://github.com/leynos/mxd/issues/7"
and environment variables could override these defaults:
VK_CMDS_PR_REFERENCE=https://github.com/owner/repo/pull/42
VK_CMDS_ISSUE_REFERENCE=https://github.com/owner/repo/issues/101
Within the vk example repository, the global --repo option is provided via
the GlobalArgs struct. A developer can set this globally using the
environment variable VK_REPO without passing --repo on every invocation.
Subcommands pr and issue load their defaults from the cmds namespace and
environment variables. If the reference field is missing in the defaults, the
tool continues using the CLI value instead of exiting with an error.
Hello world walkthrough
https://github.com/leynos/ortho-config/tree/main/examples/hello_world
The hello_world example crate demonstrates these patterns in a compact
setting. Global options such as --recipient or --salutation are parsed via
load_global_config, which layers configuration files and environment
variables beneath any CLI overrides. The greet subcommand adds optional
behaviour like a preamble (--preamble "Good morning") or custom punctuation
while reusing the merged global configuration. The take-leave subcommand
combines switches and optional arguments
(--wave, --gift, --channel email, --remind-in 15) alongside greeting
adjustments (--preamble "Until next time", --punctuation ?) to describe how
the farewell should unfold. Each subcommand struct derives OrthoConfig so
defaults from [cmds.greet] or [cmds.take-leave] merge automatically when
load_and_merge() is called.
Behavioural tests in examples/hello_world/tests exercise scenarios such as
hello_world greet --preamble "Good morning" and running
hello_world --is-excited take-leave with
--gift biscuits, --remind-in 15, --channel email, and --wave. These
end-to-end checks verify that CLI arguments override configuration files and
that validation errors surface cleanly when callers provide blank strings or
conflicting switches.
Sample configuration files live in examples/hello_world/config. The
baseline.toml defaults underpin both the automated tests and the demo
scripts, while overrides.toml extends the baseline to demonstrate inheritance
by adjusting the recipient and salutation. The paired scripts/demo.sh and
scripts/demo.cmd helpers copy these files into a temporary directory before
running cargo run -p hello_world, illustrating how file defaults, environment
variables, and CLI arguments override one another without mutating the working
tree.
Dispatching with `clap‑dispatch`
The clap‑dispatch crate can be combined with OrthoConfig to simplify
subcommand execution. Each subcommand struct implements a trait defining the
action to perform. An enum of subcommands is annotated with
#[clap_dispatch(fn run(...))], and the load_and_merge_subcommand_for
function can be called on each variant before dispatching. See the
Subcommand Configuration section of the OrthoConfig README
for a complete example.