use anyhow::Result;
use clap::Parser;
use clap::builder::TypedValueParser;
#[derive(Parser)]
#[command(name = "honk")]
#[command(author, version, about)]
#[command(long_about = "Make your computer honk like an old-school car.\n\n\
Run without a subcommand to honk. Drop it at the end of a long command \
(`cargo build && honk`) and your computer tells you when it is done.")]
#[command(propagate_version = true)]
#[command(args_conflicts_with_subcommands = true)]
pub struct Cli {
#[arg(short, long, action = clap::ArgAction::Count, global = true)]
pub verbose: u8,
#[arg(short, long, global = true)]
pub quiet: bool,
#[arg(long, global = true)]
pub no_color: bool,
#[command(flatten)]
pub honk: HonkArgs,
#[command(subcommand)]
pub command: Option<Commands>,
}
#[derive(clap::Args, Debug, Default)]
pub struct HonkArgs {
#[arg(long, value_parser = clap::builder::PossibleValuesParser::new(["bulb", "awooga", "car", "truck", "clown"])
.map(|s| s.parse::<honk_core::Style>().expect("possible values are valid styles")))]
pub style: Option<honk_core::Style>,
#[arg(long, value_parser = clap::value_parser!(u32).range(1..=10))]
pub times: Option<u32>,
#[arg(long)]
pub long: bool,
#[arg(long, value_parser = parse_pitch)]
pub pitch: Option<f32>,
#[arg(long, value_parser = parse_volume)]
pub volume: Option<f32>,
#[arg(long, allow_negative_numbers = true, conflicts_with = "command")]
pub status: Option<i32>,
#[arg(long, value_name = "FILE")]
pub wav: Option<std::path::PathBuf>,
#[arg(last = true, value_name = "COMMAND")]
pub command: Vec<String>,
}
fn parse_in(s: &str, range: std::ops::RangeInclusive<f32>, what: &str) -> Result<f32, String> {
let v: f32 = s.parse().map_err(|_| format!("'{s}' is not a number"))?;
if range.contains(&v) {
Ok(v)
} else {
Err(format!(
"{what} must be between {} and {}",
range.start(),
range.end()
))
}
}
fn parse_pitch(s: &str) -> Result<f32, String> {
parse_in(s, honk_core::spec::PITCH_RANGE, "pitch")
}
fn parse_volume(s: &str) -> Result<f32, String> {
parse_in(s, honk_core::spec::VOLUME_RANGE, "volume")
}
#[derive(clap::Subcommand)]
pub enum Commands {
Completion {
#[arg(value_enum)]
shell: Shell,
},
Version,
}
#[derive(Clone, Copy, clap::ValueEnum)]
pub enum Shell {
Bash,
Zsh,
Fish,
Powershell,
}
impl Cli {
pub async fn run(self) -> Result<i32> {
match self.command {
Some(Commands::Completion { shell }) => {
crate::commands::completion::generate_completions(shell);
Ok(0)
}
Some(Commands::Version) => {
crate::banner::print_banner_with_version();
Ok(0)
}
None => {
let quiet = self.quiet;
if !self.honk.command.is_empty() {
return crate::commands::run::run(&self.honk, quiet);
}
let mood = self
.honk
.status
.map(honk_core::Mood::from_exit_code)
.unwrap_or_default();
crate::commands::honk::honk(&self.honk, mood, quiet)?;
Ok(0)
}
}
}
}