use std::{env, io};
use anyhow::Result;
use clap::builder::Styles;
use clap::builder::styling::{AnsiColor, Style};
use clap::{CommandFactory, Parser, ValueEnum};
use clap_complete::Generator;
use clap_verbosity_flag::Verbosity;
use supports_color::Stream;
use sysinfo::System;
#[derive(Parser)]
#[command(about, version = version_msg(), styles = get_styles())]
pub struct Args {
#[arg(long, value_enum)]
pub completion: Option<Shell>,
#[command(flatten)]
pub verbose: Verbosity,
}
shadow_rs::shadow!(shadow_build);
#[must_use]
fn version_msg() -> String {
let version = clap::crate_version!();
let author = clap::crate_authors!();
let home_page = env!("CARGO_PKG_HOMEPAGE");
let commit_date = shadow_build::COMMIT_DATE;
let commit_hash = shadow_build::COMMIT_HASH;
let build_time = shadow_build::BUILD_TIME;
let build_target = shadow_build::BUILD_TARGET;
let os_version = System::long_os_version().unwrap_or(String::from("unknow os"));
let cpu_arch = System::cpu_arch();
let current_exe_path = env::current_exe()
.expect("unable to get current executable path")
.display()
.to_string();
format!(
"\
{version}
Author: {author}
Author's Telegram: https://t.me/TerakomariGandesblood
Project home page: {home_page}
Commit date: {commit_date}
Commit hash: {commit_hash}
Build time: {build_time}
Build target: {build_target}
OS information: {os_version} [{cpu_arch}]
Executable path: {current_exe_path}"
)
}
const HEADER: Style = AnsiColor::Green.on_default().bold();
const USAGE: Style = AnsiColor::Green.on_default().bold();
const LITERAL: Style = AnsiColor::Cyan.on_default().bold();
const PLACEHOLDER: Style = AnsiColor::Cyan.on_default();
const ERROR: Style = AnsiColor::Red.on_default().bold();
const VALID: Style = AnsiColor::Cyan.on_default().bold();
const INVALID: Style = AnsiColor::Yellow.on_default().bold();
const HELP_STYLES: Styles = Styles::styled()
.header(HEADER)
.usage(USAGE)
.literal(LITERAL)
.placeholder(PLACEHOLDER)
.error(ERROR)
.valid(VALID)
.invalid(INVALID);
fn get_styles() -> Styles {
if supports_color::on(Stream::Stdout).is_some() {
HELP_STYLES
} else {
Styles::plain()
}
}
#[must_use]
#[derive(Clone, ValueEnum)]
pub enum Shell {
Bash,
Elvish,
Fish,
PowerShell,
Zsh,
Nushell,
}
impl Shell {
fn to_clap_type(&self) -> Box<dyn Generator> {
match self {
Self::Bash => Box::new(clap_complete::Shell::Bash),
Self::Elvish => Box::new(clap_complete::Shell::Elvish),
Self::Fish => Box::new(clap_complete::Shell::Fish),
Self::PowerShell => Box::new(clap_complete::Shell::PowerShell),
Self::Zsh => Box::new(clap_complete::Shell::Zsh),
Self::Nushell => Box::new(clap_complete_nushell::Nushell),
}
}
}
impl Generator for Shell {
fn file_name(&self, name: &str) -> String {
self.to_clap_type().file_name(name)
}
fn generate(&self, cmd: &clap::Command, buf: &mut dyn io::Write) {
self.to_clap_type().generate(cmd, buf);
}
}
pub fn generate_completion(shell: Shell) -> Result<()> {
let mut cmd = Args::command();
let bin_name = cmd.get_name().to_string();
clap_complete::generate(shell, &mut cmd, bin_name, &mut io::stdout());
Ok(())
}
#[cfg(test)]
mod tests {
use clap::CommandFactory;
use super::*;
#[test]
fn verify_cli() {
Args::command().debug_assert();
}
}