A production-ready Rust command-line application template with subcommands, JSON configuration, and clean architecture.
- Subcommand Architecture: Easily extensible command structure using
clap - JSON Configuration: Persistent configuration with CLI argument override support
- Cross-Platform: Works on Linux, macOS, and Windows with proper config paths
- Clean Error Handling: Uses
anyhowfor ergonomic error propagation - Modular Design: Organized code structure for maintainability
src/
├── main.rs # Entry point, CLI parsing, and dispatch logic
├── cli.rs # Command-line argument definitions (clap)
├── config.rs # Configuration loading/saving (JSON)
└── commands/ # Subcommand implementations
├── mod.rs # Module exports
├── init.rs # Initialize configuration
├── run.rs # Main application logic
└── status.rs # Show application status
cargo build --releaseThe binary will be available at target/release/command-line-template.
Create a default configuration file:
command-line-template initForce overwrite existing configuration:
command-line-template init --forceConfiguration file locations:
- Linux/macOS:
~/.config/command-line-template/config.json - Windows:
%APPDATA%\command-line-template\config.json
Execute the main application logic:
command-line-template runWith options:
command-line-template run --input myfile.txt --output result.txtOverride config values from CLI:
command-line-template run --verbose true --timeout 60 --custom-value "test"Show application status:
command-line-template statusShow full configuration:
command-line-template status --allThese options work with any subcommand and override config file values:
-c, --config <PATH>: Use a custom config file location-v, --verbose <BOOL>: Enable verbose output-t, --timeout <SECONDS>: Set timeout duration
Example:
command-line-template --verbose true --timeout 120 run --input data.txtThe configuration file uses JSON format. Default configuration:
{
"verbose": false,
"timeout": 30,
"custom_value": ""
}Edit the configuration file to set default values. CLI arguments always override config file values.
- Define the command in
src/cli.rs:
#[derive(Subcommand, Debug)]
pub enum Commands {
// ... existing commands ...
/// Your new command description
NewCommand {
/// Command-specific argument
#[arg(short, long)]
argument: Option<String>,
},
}- Create implementation in
src/commands/newcommand.rs:
use anyhow::Result;
use crate::config::Config;
pub fn execute(config: &Config, argument: Option<String>) -> Result<()> {
println!("Executing new command");
// Your command logic here
Ok(())
}- Export the module in
src/commands/mod.rs:
pub mod newcommand;- Add dispatch logic in
src/main.rs:
match cli.command {
// ... existing commands ...
Commands::NewCommand { argument } => {
commands::newcommand::execute(&config, argument)?;
}
}- Add field to
Configstruct insrc/config.rs:
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
// ... existing fields ...
#[serde(default)]
pub new_field: String,
}- Update
Defaultimplementation:
impl Default for Config {
fn default() -> Self {
Self {
// ... existing fields ...
new_field: String::from("default_value"),
}
}
}- Add CLI argument in
src/cli.rs(if needed):
#[derive(Parser, Debug)]
pub struct Cli {
// ... existing fields ...
#[arg(long, global = true)]
pub new_field: Option<String>,
}- Update merge logic in
src/config.rs:
pub fn merge_with_cli(&mut self, /* ... */, new_field: Option<String>) {
// ... existing merges ...
if let Some(nf) = new_field {
self.new_field = nf;
}
}- clap: Command-line argument parsing with derive macros
- serde: Serialization/deserialization framework
- serde_json: JSON support for configuration files
- anyhow: Flexible error handling
- directories: Cross-platform config directory resolution
This is a template project. Choose your own license when using this template.