use crate::{
COMMAND_NAME, OnceCommand, OnceCommandWith,
command::{completions::GenerateCompletions, external::RunExternalSubCommand},
error::CliError,
};
use clap::{ArgAction, Parser, Subcommand};
use clap_complete::Shell;
use colorchoice::ColorChoice;
use colorchoice_clap::Color;
use std::process::ExitCode;
use tracing::{instrument, level_filters::LevelFilter, trace};
#[derive(Debug, Parser)]
#[command(name = COMMAND_NAME)]
#[command(about = "Rusty Ham CLI for ham radio things", long_about = None)]
pub struct Cli {
#[arg(
long,
short = 'v',
action = ArgAction::Count,
global = true,
)]
verbose: u8,
#[arg(
long,
short = 'q',
action = ArgAction::Count,
global = true,
conflicts_with = "verbose",
)]
quiet: u8,
#[command(flatten)]
color: Color,
#[command(subcommand)]
command: Commands,
}
#[derive(Debug, Subcommand)]
pub enum Commands {
#[command(subcommand)]
Antenna(AntennaCommands),
#[command(subcommand)]
BandPlan(BandPlanCommands),
#[command(subcommand)]
CallSign(CallSignCommands),
Completions {
shell: Option<Shell>,
},
#[command(subcommand)]
Config(ConfigCommands),
#[command(external_subcommand)]
External(#[arg(num_args = 1..)] Vec<String>),
}
#[derive(Debug, Default)]
pub struct Globals {
verbose_arg_count: u8,
quiet_arg_count: u8,
color_choice: ColorChoice,
}
impl OnceCommand for Cli {
type Output = ExitCode;
type Error = CliError;
#[instrument(name = "cli")]
fn execute(self) -> Result<Self::Output, Self::Error> {
trace!("Setting color globals to `{:?}`", self.color.as_choice());
self.color.write_global();
match self.color.as_choice() {
ColorChoice::Always | ColorChoice::AlwaysAnsi => colored::control::set_override(true),
ColorChoice::Never => colored::control::set_override(false),
ColorChoice::Auto => {}
}
self.command.execute_with(Globals {
verbose_arg_count: self.verbose,
quiet_arg_count: self.quiet,
color_choice: self.color.as_choice(),
})
}
}
impl Cli {
pub fn max_level_filter(&self) -> LevelFilter {
const DEFAULT_LEVEL_ERROR: u8 = 1;
let offset = self.verbose as i16 - self.quiet as i16;
match i16::from(DEFAULT_LEVEL_ERROR).saturating_add(offset) {
i16::MIN..=0 => LevelFilter::OFF,
1 => LevelFilter::ERROR,
2 => LevelFilter::WARN,
3 => LevelFilter::INFO,
4 => LevelFilter::DEBUG,
5..=i16::MAX => LevelFilter::TRACE,
}
}
}
impl OnceCommandWith for Commands {
type Output = ExitCode;
type Error = CliError;
type Value = Globals;
fn execute_with(self, globals: Globals) -> Result<Self::Output, Self::Error> {
match self {
Self::Antenna(cmds) => cmds.execute(),
Self::BandPlan(cmds) => cmds.execute(),
Self::CallSign(cmds) => cmds.execute(),
Self::Config(cmds) => cmds.execute(),
Self::Completions { shell } => GenerateCompletions::new(shell).execute(),
Self::External(args) => RunExternalSubCommand::new(
args[0].clone(),
if args.len() > 1 { &args[1..] } else { &[] },
)
.with_verbosity_level(globals.verbose_arg_count)
.with_quietness_level(globals.quiet_arg_count)
.with_color_choice(globals.color_choice)
.execute(),
}
}
}
mod antennas;
use antennas::AntennaCommands;
mod bands;
use bands::BandPlanCommands;
mod callsign;
use callsign::CallSignCommands;
mod config;
use config::ConfigCommands;