diff --git a/CHANGELOG.md b/CHANGELOG.md index c7a8623f6..50e080af8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] +## [0.22.2] - 2024-01-28 + +## Added + +* Generate completion & manpage #357 - @cyqsimon + ## [0.22.1] - 2024-01-28 ## Fixed diff --git a/Cargo.lock b/Cargo.lock index 029e70a6b..975047443 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -168,6 +168,8 @@ dependencies = [ "chrono", "clap", "clap-verbosity-flag", + "clap_complete", + "clap_mangen", "crossterm", "derivative", "http_req", @@ -340,6 +342,15 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_complete" +version = "4.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df631ae429f6613fcd3a7c1adbdb65f637271e561b03680adaa6573015dfb106" +dependencies = [ + "clap", +] + [[package]] name = "clap_derive" version = "4.4.7" @@ -358,6 +369,16 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +[[package]] +name = "clap_mangen" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a7c2b01e5e779c19f46a94bbd398f33ae63b0f78c07108351fb4536845bb7fd" +dependencies = [ + "clap", + "roff", +] + [[package]] name = "colorchoice" version = "1.0.0" @@ -1514,6 +1535,12 @@ dependencies = [ "quick-error", ] +[[package]] +name = "roff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" + [[package]] name = "rstest" version = "0.18.2" diff --git a/Cargo.toml b/Cargo.toml index 3d028306c..e79555474 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,15 @@ pnet_base = "0.34.0" regex = "1.10.3" rstest = "0.18.2" -[target.'cfg(target_os = "windows")'.build-dependencies] +[build-dependencies] anyhow = "1.0.79" +clap = { version = "4.4.18", features = ["derive"] } +clap-verbosity-flag = "2.1.2" +clap_complete = "4.4.9" +clap_mangen = "0.2.17" +derivative = "2.2.0" +strum = { version = "0.25.0", features = ["derive"] } + +[target.'cfg(target_os = "windows")'.build-dependencies] http_req = "0.10.2" zip = "0.6.6" diff --git a/build.rs b/build.rs index d20e5b568..748121496 100644 --- a/build.rs +++ b/build.rs @@ -1,8 +1,41 @@ +use std::{env, fs::File}; + +use anyhow::anyhow; +use clap::CommandFactory; +use clap_complete::Shell; +use clap_mangen::Man; + fn main() { + build_completion_manpage().unwrap(); + #[cfg(target_os = "windows")] download_windows_npcap_sdk().unwrap(); } +include!("src/cli.rs"); + +fn build_completion_manpage() -> anyhow::Result<()> { + let mut cmd = Opt::command(); + + // build into `BANDWHICH_GEN_DIR` with a fallback to `OUT_DIR` + let gen_dir: PathBuf = env::var_os("BANDWHICH_GEN_DIR") + .or_else(|| env::var_os("OUT_DIR")) + .ok_or(anyhow!("OUT_DIR is unset"))? + .into(); + + // completion + for &shell in Shell::value_variants() { + clap_complete::generate_to(shell, &mut cmd, "bandwhich", &gen_dir)?; + } + + // manpage + let mut manpage_out = File::create(gen_dir.join("bandwhich.1"))?; + let manpage = Man::new(cmd); + manpage.render(&mut manpage_out)?; + + Ok(()) +} + #[cfg(target_os = "windows")] fn download_windows_npcap_sdk() -> anyhow::Result<()> { use std::{ diff --git a/src/cli.rs b/src/cli.rs index 644a500ad..7a2fbc359 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,10 +1,9 @@ use std::{net::Ipv4Addr, path::PathBuf}; -use clap::{Args, Parser}; +use clap::{Args, Parser, ValueEnum, ValueHint}; use clap_verbosity_flag::{InfoLevel, Verbosity}; use derivative::Derivative; - -use crate::display::BandwidthUnitFamily; +use strum::EnumIter; #[derive(Clone, Debug, Derivative, Parser)] #[derivative(Default)] @@ -30,7 +29,7 @@ pub struct Opt { /// A dns server ip to use instead of the system default pub dns_server: Option, - #[arg(long)] + #[arg(long, value_hint = ValueHint::FilePath)] /// Enable debug logging to a file pub log_to: Option, @@ -58,9 +57,24 @@ pub struct RenderOpts { #[arg(short, long, value_enum, default_value_t)] /// Choose a specific family of units - pub unit_family: BandwidthUnitFamily, + pub unit_family: UnitFamily, #[arg(short, long)] /// Show total (cumulative) usages pub total_utilization: bool, } + +// IMPRV: it would be nice if we can `#[cfg_attr(not(build), derive(strum::EnumIter))]` this +// unfortunately there is no configuration option for build script detection +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, ValueEnum, EnumIter)] +pub enum UnitFamily { + #[default] + /// bytes, in powers of 2^10 + BinBytes, + /// bits, in powers of 2^10 + BinBits, + /// bytes, in powers of 10^3 + SiBytes, + /// bits, in powers of 10^3 + SiBits, +} diff --git a/src/display/components/display_bandwidth.rs b/src/display/components/display_bandwidth.rs index 5d7a510d2..bf2b526ba 100644 --- a/src/display/components/display_bandwidth.rs +++ b/src/display/components/display_bandwidth.rs @@ -1,7 +1,8 @@ use std::fmt; -use clap::ValueEnum; -use strum::EnumIter; +use derivative::Derivative; + +use crate::cli::UnitFamily; #[derive(Copy, Clone, Debug)] pub struct DisplayBandwidth { @@ -16,17 +17,14 @@ impl fmt::Display for DisplayBandwidth { } } -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, ValueEnum, EnumIter)] -pub enum BandwidthUnitFamily { - #[default] - /// bytes, in powers of 2^10 - BinBytes, - /// bits, in powers of 2^10 - BinBits, - /// bytes, in powers of 10^3 - SiBytes, - /// bits, in powers of 10^3 - SiBits, +/// Type wrapper around [`UnitFamily`] to provide extra functionality. +#[derive(Copy, Clone, Derivative, Default, Eq, PartialEq)] +#[derivative(Debug = "transparent")] +pub struct BandwidthUnitFamily(UnitFamily); +impl From for BandwidthUnitFamily { + fn from(value: UnitFamily) -> Self { + Self(value) + } } impl BandwidthUnitFamily { #[inline] @@ -39,9 +37,9 @@ impl BandwidthUnitFamily { /// Binary base: 2^10. const BB: f64 = 1024.0; - use BandwidthUnitFamily as F; + use UnitFamily as F; // probably could macro this stuff, but I'm too lazy - match self { + match self.0 { F::BinBytes => [ (1.0, BB * STEP_UP_FRAC, "B"), (BB, BB.powi(2) * STEP_UP_FRAC, "KiB"), @@ -99,11 +97,12 @@ mod tests { use itertools::Itertools; use strum::IntoEnumIterator; - use crate::display::{BandwidthUnitFamily, DisplayBandwidth}; + use crate::{cli::UnitFamily, display::DisplayBandwidth}; #[test] fn bandwidth_formatting() { - let test_bandwidths_formatted = BandwidthUnitFamily::iter() + let test_bandwidths_formatted = UnitFamily::iter() + .map_into() .cartesian_product( // I feel like this is a decent selection of values (-6..60) diff --git a/src/display/ui.rs b/src/display/ui.rs index 4b6d71698..4a2866e81 100644 --- a/src/display/ui.rs +++ b/src/display/ui.rs @@ -33,7 +33,7 @@ where let state = { let mut state = UIState::default(); state.interface_name = opts.interface.clone(); - state.unit_family = opts.render_opts.unit_family; + state.unit_family = opts.render_opts.unit_family.into(); state.cumulative_mode = opts.render_opts.total_utilization; state };