use std::{
env,
io::{self, Write},
};
use argx::{Args, Defaults, Dotenv, Environment, Parser as _, Subcommand, argx, completion::Shell};
use serde::Serialize;
const VERSION: &str = "1.2.3";
const LONG_VERSION: &str = "1.2.3 (build abc123)";
#[derive(Debug, argx::Config)]
#[argx(prefix = "ARGX_COMPLETE")]
struct Settings {
#[argx(default = 4)]
workers: usize,
#[argx(default = String::from("http://localhost:8080"))]
endpoint: String,
}
#[derive(Debug, Args)]
struct Common {
#[argx(short, long, global)]
verbose: bool,
}
#[derive(Debug, Args)]
struct Get {
id: String,
#[argx(long, default = 20)]
limit: usize,
}
#[derive(Serialize)]
#[argx(schema)]
struct GetOutput {
id: String,
limit: usize,
}
#[argx(schema)]
enum GetError {
NotFound,
}
#[argx(handler = Get)]
fn get(command: Get) -> Result<GetOutput, GetError> {
if command.id == "missing" {
Err(GetError::NotFound)
} else {
Ok(GetOutput { id: command.id, limit: command.limit })
}
}
#[derive(Debug, Args)]
struct Put {
id: String,
value: String,
#[argx(long, requires = ["token"])]
endpoint: Option<String>,
#[argx(long)]
token: Option<String>,
#[argx(long, conflicts = ["force"])]
dry_run: bool,
#[argx(long)]
force: bool,
}
#[derive(Serialize)]
#[argx(schema)]
struct PutOutput {
id: String,
}
#[argx(schema)]
enum PutError {
Rejected,
}
#[argx(handler = Put)]
fn put(command: Put) -> Result<PutOutput, PutError> {
if command.value == "reject" {
Err(PutError::Rejected)
} else {
let _ = (&command.endpoint, &command.token, command.dry_run, command.force);
Ok(PutOutput { id: command.id })
}
}
#[derive(Debug, Clone, Copy, Args)]
struct Completions {
#[argx(value_enum)]
shell: Shell,
}
#[derive(Serialize)]
#[argx(schema)]
struct CompletionOutput {
script: String,
}
#[argx(schema)]
enum CompletionError {
Render,
}
#[argx(handler = Completions)]
fn completions(command: Completions) -> Result<CompletionOutput, CompletionError> {
Cli::render_completion(command.shell)
.map(|script| CompletionOutput { script })
.map_err(|_| CompletionError::Render)
}
#[derive(Debug, Subcommand)]
#[argx(schema)]
enum Command {
#[argx(alias = "show")]
Get(Get),
Put(Put),
Completions(Completions),
}
#[derive(Debug, argx::Parser)]
#[argx(name = "complete", version = VERSION, long_version = LONG_VERSION, schema)]
struct Cli {
#[argx(flatten)]
common: Common,
#[argx(subcommand)]
command: Command,
}
fn settings() -> Result<Settings, argx::ConfigError> {
let loader = Settings::loader().layer(Defaults);
#[cfg(feature = "toml")]
let loader = match env::var_os("ARGX_COMPLETE_TOML") {
Some(path) => loader.layer(argx::Toml::new(std::path::PathBuf::from(path))),
None => loader,
};
let loader = match env::var_os("ARGX_COMPLETE_DOTENV") {
Some(path) => loader.layer(Dotenv::new(std::path::PathBuf::from(path))),
None => loader,
};
loader.layer(Environment).resolve()
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
let settings = settings()?;
if cli.common.verbose {
eprintln!("workers: {}", settings.workers);
eprintln!("default endpoint: {}", settings.endpoint);
}
match cli.command {
Command::Get(command) => match get(command) {
Ok(value) => println!("get: {} (limit {})", value.id, value.limit),
Err(GetError::NotFound) => {
eprintln!("object not found");
std::process::exit(1);
}
},
Command::Put(command) => match put(command) {
Ok(value) => println!("put: {}", value.id),
Err(PutError::Rejected) => {
eprintln!("request rejected");
std::process::exit(1);
}
},
Command::Completions(command) => {
let value = match completions(command) {
Ok(value) => value,
Err(CompletionError::Render) => {
eprintln!("failed to render completion script");
std::process::exit(1);
}
};
io::stdout().lock().write_all(value.script.as_bytes())?;
}
}
Ok(())
}