use clap::crate_authors;
use std::path::PathBuf;
use clap::Parser;
use std::sync::LazyLock;
pub static SHORT_VERSION: LazyLock<&'static str> = LazyLock::new(|| {
let v = format!(
"{}-{} ({})\nWritten by {}",
env!("CARGO_PKG_VERSION"),
option_env!("VERGEN_GIT_DESCRIBE").unwrap_or("unknown"),
option_env!("VERGEN_BUILD_DATE").unwrap_or("unknown"),
crate_authors!(),
);
Box::leak(v.into_boxed_str())
});
#[derive(Parser, Debug, Default)]
#[command(
version = *SHORT_VERSION,
about,
propagate_version = true,
disable_help_subcommand = true,
args_conflicts_with_subcommands = true
)]
pub struct Cli {
#[command(subcommand)]
pub command: Option<Commands>,
#[command(flatten)]
pub connect: ConnectArgs,
}
#[derive(clap::Subcommand, Debug, Clone)]
pub enum Commands {
Connect(ConnectArgs),
InstallManPages,
InitConfig,
InstallCompletions {
#[clap(default_value = "bash")]
shell: String,
},
}
#[derive(clap::Args, Debug, Clone, Default)]
pub struct ConnectArgs {
#[clap(index = 1)]
pub host: Option<String>,
#[clap(long, short, default_value_t = 22)]
pub port: u16,
#[clap(long, short)]
pub username: Option<String>,
#[clap(long, short = 'k')]
pub private_key: Option<PathBuf>,
#[clap(long, short = 'o')]
pub openssh_certificate: Option<PathBuf>,
#[clap(index = 2)]
pub path: Option<PathBuf>,
#[clap(short, long)]
pub from_config: bool,
}
#[cfg(test)]
mod tests {
use super::Cli;
use clap::CommandFactory;
fn section<'a>(man: &'a str, heading: &str) -> &'a str {
let body = man
.split_once(&format!(".SH {heading}\n"))
.unwrap_or_else(|| panic!("man/filessh.1 has no {heading} section"))
.1;
body.split_once("\n.SH ").map_or(body, |(body, _)| body)
}
#[test]
fn man_page_documents_every_option_and_command() {
let man = include_str!("../../man/filessh.1").replace('\\', "");
let mut cli = Cli::command();
cli.build();
let commands = section(&man, "COMMANDS");
let options = section(&man, "OPTIONS");
let mut undocumented = cli
.get_subcommands()
.map(|sub| sub.get_name().to_owned())
.filter(|name| !commands.contains(name))
.chain(
cli.get_arguments()
.chain(cli.get_subcommands().flat_map(|sub| sub.get_arguments()))
.filter_map(|arg| arg.get_long())
.map(|long| format!("--{long}"))
.filter(|flag| !options.contains(flag)),
)
.collect::<Vec<_>>();
undocumented.sort();
undocumented.dedup();
assert!(
undocumented.is_empty(),
"man/filessh.1 is out of date with the CLI; undocumented: {undocumented:?}"
);
}
}
#[derive(Debug, Clone, Default)]
pub struct ResolvedConnectArgs {
pub host: String,
pub port: u16,
pub username: Option<String>,
pub private_key: PathBuf,
pub openssh_certificate: Option<PathBuf>,
pub path: PathBuf,
}