| Crates.io | flag-rs |
| lib.rs | flag-rs |
| version | 0.8.4 |
| created_at | 2025-05-24 23:20:43.77364+00 |
| updated_at | 2025-09-08 02:59:30.297756+00 |
| description | A Cobra-inspired CLI framework with dynamic completions |
| homepage | |
| repository | https://github.com/navicore/flag-rs |
| max_upload_size | |
| id | 1687857 |
| size | 5,049,067 |
Flag-rs is a command-line interface (CLI) framework for Rust inspired by Go's Cobra library. It provides dynamic command completion, self-registering commands, and a clean, modular architecture for building sophisticated CLI applications.
Result typesFlag-rs enables dynamic completions that can query APIs, databases, or any runtime
state - just like kubectl does when completing pod names. I've struggled and
failed for a long time at modifying the leading command line processing crate to
work this way - hence this new crate.
The Flag-rs implementation may be naive - the leading crate, Clap, is very well regarded and meets the needs of a huge knowledgeable user base.
Add this to your Cargo.toml:
[dependencies]
flag-rs = "0.8"
use flag_rs::{Command, CommandBuilder, Context, Flag, FlagType, FlagValue, CompletionResult};
fn main() {
let app = CommandBuilder::new("myapp")
.short("A simple CLI application")
.long("This is a longer description of my application")
.flag(
Flag::new("verbose")
.short('v')
.usage("Enable verbose output")
.value_type(FlagType::Bool)
.default(FlagValue::Bool(false))
)
.subcommand(build_serve_command())
.build();
let args: Vec<String> = std::env::args().skip(1).collect();
if let Err(e) = app.execute(args) {
eprintln!("Error: {}", e);
std::process::exit(1);
}
}
fn build_serve_command() -> Command {
CommandBuilder::new("serve")
.short("Start the server")
.flag(
Flag::new("port")
.short('p')
.usage("Port to listen on")
.value_type(FlagType::Int)
.default(FlagValue::Int(8080))
)
.run(|ctx| {
let port = ctx.flag("port")
.and_then(|s| s.parse::<i64>().ok())
.unwrap_or(8080);
println!("Starting server on port {}", port);
Ok(())
})
.build()
}
The killer feature of Flag-rs is dynamic completions. Unlike static completions, these run at completion time and can return different values based on current state:
CommandBuilder::new("get")
.arg_completion(|ctx, prefix| {
// This runs when the user presses TAB!
let namespace = ctx.flag("namespace").unwrap_or("default");
// In a real app, you'd query an API here
let items = fetch_items_from_api(namespace);
Ok(CompletionResult::new()
.extend(items.into_iter()
.filter(|item| item.starts_with(prefix))
.collect::<Vec<_>>()))
})
.build()
Flag-rs supports rich completions with descriptions, similar to Cobra. When descriptions are provided, they are handled appropriately by each shell:
.flag_completion("environment", |_ctx, prefix| {
Ok(CompletionResult::new()
.add_with_description("dev", "Development environment - safe for testing")
.add_with_description("staging", "Staging environment - production mirror")
.add_with_description("prod", "Production environment - BE CAREFUL!"))
})
Shell-specific behavior:
value:descriptionvalue[TAB]descriptionFor Zsh and Fish, descriptions appear alongside completions in the shell's native format, providing helpful context when selecting options.
For expensive completion operations (API calls, file system scans), use the built-in caching mechanism:
use flag_rs::completion_cache::CompletionCache;
use std::time::Duration;
use std::sync::Arc;
let cache = Arc::new(CompletionCache::new(Duration::from_secs(5)));
CommandBuilder::new("get")
.arg_completion({
let cache = Arc::clone(&cache);
move |ctx, prefix| {
let key = CompletionCache::make_key(
&["get".to_string()],
prefix,
ctx.flags()
);
// Check cache first
if let Some(cached) = cache.get(&key) {
return Ok(cached);
}
// Expensive operation
let result = fetch_from_api()?;
// Cache the result
cache.put(key, result.clone());
Ok(result)
}
})
.build()
Protect against slow completion functions that could hang the shell:
use flag_rs::completion_timeout::make_timeout_completion;
use std::time::Duration;
CommandBuilder::new("search")
.arg_completion(make_timeout_completion(
Duration::from_millis(100),
|ctx, prefix| {
// This will timeout if it takes > 100ms
search_database(prefix)
}
))
.build()
For larger applications, Flag supports a modular structure where commands register themselves:
// src/cmd/mod.rs
pub fn register_commands(root: &mut Command) {
serve::register(root);
deploy::register(root);
status::register(root);
}
// src/cmd/serve.rs
pub fn register(parent: &mut Command) {
let cmd = CommandBuilder::new("serve")
.short("Start the server")
.run(|ctx| {
// Implementation
Ok(())
})
.build();
parent.add_command(cmd);
}
Generate completion scripts for popular shells:
// Add a completion command to your CLI
CommandBuilder::new("completion")
.short("Generate shell completion scripts")
.run(|ctx| {
let shell = ctx.args().first()
.ok_or("Shell name required")?;
let script = match shell.as_str() {
"bash" => app.generate_completion(Shell::Bash),
"zsh" => app.generate_completion(Shell::Zsh),
"fish" => app.generate_completion(Shell::Fish),
_ => return Err("Unsupported shell"),
};
println!("{}", script);
Ok(())
})
.build()
Then users can enable completions:
# Bash
source <(myapp completion bash)
# Zsh
source <(myapp completion zsh)
# Fish
myapp completion fish | source
Flag-rs supports multiple value types:
String - Text valuesBool - Boolean flagsInt - Integer valuesFloat - Floating point valuesStringSlice - Multiple string valuesStringArray - Array of string valuesChoice(Vec<String>) - Enumerated choices with validationRange(i64, i64) - Integer range validationFile - File path validationDirectory - Directory path validationFlag-rs supports advanced flag relationships:
use flag_rs::{Flag, FlagConstraint};
// Required if another flag is set
Flag::new("cert")
.value_type(FlagType::File)
.constraint(FlagConstraint::RequiredIf("tls".to_string()))
// Conflicts with other flags
Flag::new("json")
.value_type(FlagType::Bool)
.constraint(FlagConstraint::ConflictsWith(vec!["yaml".to_string(), "xml".to_string()]))
// Requires other flags
Flag::new("ssl-verify")
.value_type(FlagType::Bool)
.constraint(FlagConstraint::Requires(vec!["ssl".to_string()]))
Flag::new("environment")
.value_type(FlagType::Choice(vec![
"dev".to_string(),
"staging".to_string(),
"prod".to_string()
]))
Flag::new("workers")
.value_type(FlagType::Range(1, 100))
.default(FlagValue::Int(4))
Flag uses idiomatic Rust error handling with no forced dependencies:
pub enum Error {
CommandNotFound(String),
SubcommandRequired(String),
FlagParsing(String),
ArgumentParsing(String),
ValidationError(String),
Completion(String),
Io(std::io::Error),
Custom(Box<dyn std::error::Error + Send + Sync>),
}
See the examples/ directory for complete examples:
kubectl.rs - A simple kubectl-like CLI demonstrating dynamic completionskubectl_modular/ - A modular kubectl implementation showing command self-registrationadvanced_flags_demo.rs - Demonstrates advanced flag types and constraintscaching_demo.rs - Shows completion caching for expensive operationstimeout_demo.rs - Demonstrates timeout protection for slow completionsmemory_optimization_demo.rs - Shows memory optimization techniquesbenchmark.rs - Performance benchmarking suiteFlag-rs includes several performance optimizations for large-scale CLI applications:
Reduce memory usage for repeated strings:
use flag_rs::string_pool;
// Intern frequently used strings
let cmd_name = string_pool::intern("kubectl");
let flag_name = string_pool::intern("namespace");
Use memory-efficient completion structures:
use flag_rs::completion_optimized::{CompletionResultOptimized, CompletionItem};
use std::borrow::Cow;
CompletionResultOptimized::new()
.add_with_description(
Cow::Borrowed("static-value"), // No allocation
Cow::Borrowed("Static description")
)
Based on our benchmarks:
| Operation | Performance | Notes |
|---|---|---|
| Simple command creation | ~500 ns | 2 flags |
| Complex CLI (50 subcommands) | ~150 μs | 500 total commands |
| Flag parsing | ~2 μs | Per flag |
| Subcommand lookup | ~50 ns | O(1) HashMap |
| Dynamic completion | ~15 μs | 100 items |
| Cached completion | ~1 μs | Cache hit |
See examples/benchmark.rs for detailed performance measurements.
Flag is designed to be a foundational library with zero dependencies. This ensures:
Users can add their own dependencies for features like colored output, async runtime, or configuration file support without conflicts.
Flag uses opinionated clippy linting to maintain code quality. Here are the most useful commands:
# Basic clippy check
cargo clippy
# Strict clippy with all lints (what CI uses)
cargo clippy -- \
-D clippy::all \
-D clippy::pedantic \
-D clippy::nursery \
-D clippy::cargo
# Fix clippy warnings automatically
cargo clippy --fix
# Check specific target
cargo clippy --tests # Just tests
cargo clippy --examples # Just examples
cargo clippy --all # Everything
The project uses GitHub Actions for CI with:
cargo fmt)The clippy configuration in CI is very strict to catch potential issues early.
See .github/workflows/ci.yml for the full configuration.
To publish a new release to crates.io:
Cargo.tomlv0.6.0 (matching the Cargo.toml version)Setup Required:
CRATES_IO_TOKENMIT
source <(./target/debug/examples/kubectl completion zsh)