use std::env;
use std::io::{self, Write};
use click::{
Argument, ClickOption, Command, Context, Group, PathType,
Result, group::CommandLike,
};
#[derive(Debug, Clone)]
struct Environment {
verbose: bool,
home: String,
}
impl Environment {
fn new() -> Self {
Self {
verbose: false,
home: env::current_dir()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|_| ".".to_string()),
}
}
fn log(&self, msg: &str) {
let stderr = io::stderr();
let mut handle = stderr.lock();
let _ = writeln!(handle, "{}", msg);
}
fn vlog(&self, msg: &str) {
if self.verbose {
self.log(msg);
}
}
}
fn build_cli() -> Group {
Group::new("complex")
.help("A complex command line interface.")
.invoke_without_command(true)
.option(
ClickOption::new(&["--home"])
.type_any(PathType::new().exists(true).file_okay(false).resolve_path(true))
.help("Changes the folder to operate on.")
.envvar("COMPLEX_HOME")
.build(),
)
.option(
ClickOption::new(&["-v", "--verbose"])
.flag("true")
.help("Enables verbose mode.")
.envvar("COMPLEX_VERBOSE")
.build(),
)
.callback(cli_callback)
.command(build_init_command())
.command(build_status_command())
.build()
}
fn cli_callback(ctx: &Context) -> Result<()> {
let mut env = Environment::new();
if let Some(verbose) = ctx.get_param::<String>("verbose") {
env.verbose = verbose == "true";
}
if let Some(home) = ctx.get_param::<String>("home") {
env.home = home.clone();
}
env.vlog(&format!("Complex CLI initialized with home: {}", env.home));
Ok(())
}
fn build_init_command() -> Command {
Command::new("init")
.short_help("Initializes a repo.")
.help("Initializes a repository.")
.argument(
Argument::new("path")
.default(".")
.help("Path to initialize (defaults to current directory)")
.build(),
)
.callback(init_callback)
.build()
}
fn init_callback(ctx: &Context) -> Result<()> {
let env = get_environment_from_context(ctx);
let path = ctx
.get_param::<String>("path")
.cloned()
.unwrap_or_else(|| env.home.clone());
env.log(&format!("Initialized the repository in {}", path));
Ok(())
}
fn build_status_command() -> Command {
Command::new("status")
.short_help("Shows file changes.")
.help("Shows file changes in the current working directory.")
.callback(status_callback)
.build()
}
fn status_callback(ctx: &Context) -> Result<()> {
let env = get_environment_from_context(ctx);
env.log("Changed files: none");
env.vlog("bla bla bla, debug info");
Ok(())
}
fn get_environment_from_context(ctx: &Context) -> Environment {
let mut env = Environment::new();
if let Some(verbose) = ctx.get_param::<String>("verbose") {
env.verbose = verbose == "true";
}
if let Some(home) = ctx.get_param::<String>("home") {
env.home = home.clone();
}
if let Some(parent) = ctx.parent() {
if let Some(verbose) = parent.get_param::<String>("verbose") {
env.verbose = verbose == "true";
}
if let Some(home) = parent.get_param::<String>("home") {
env.home = home.clone();
}
}
if let Ok(home) = env::var("COMPLEX_HOME") {
if env.home == "." || env.home == env::current_dir().map(|p| p.to_string_lossy().to_string()).unwrap_or_default() {
env.home = home;
}
}
if let Ok(verbose) = env::var("COMPLEX_VERBOSE") {
if verbose == "1" || verbose.to_lowercase() == "true" {
env.verbose = true;
}
}
env
}
fn main() {
let cli = build_cli();
let args: Vec<String> = env::args().skip(1).collect();
if let Err(e) = cli.main(args) {
eprintln!("{}", e.format_full());
std::process::exit(e.exit_code());
}
}