#![doc = include_str!("../README.md")]
use std::str::FromStr;
use getopts::Fail;
pub trait CommandLine: Sized + Default + Eq + PartialEq {
fn add_opts(&self, prefix: Option<&str>, opts: &mut getopts::Options);
fn matches(&mut self, prefix: Option<&str>, matches: &getopts::Matches);
fn canonical_command_line(&self, prefix: Option<&str>) -> Vec<String>;
fn from_command_line(usage: &str) -> (Self, Vec<String>) {
let args: Vec<String> = std::env::args().collect();
let args: Vec<&str> = args.iter().map(AsRef::as_ref).collect();
Self::from_arguments(usage, &args[1..])
}
fn from_command_line_relaxed(usage: &str) -> (Self, Vec<String>) {
let args: Vec<String> = std::env::args().collect();
let args: Vec<&str> = args.iter().map(AsRef::as_ref).collect();
Self::from_arguments_relaxed(usage, &args[1..])
}
fn from_arguments(usage: &str, args: &[&str]) -> (Self, Vec<String>) {
let (command_line, free) = Self::from_arguments_relaxed(usage, args);
let mut reconstructed_args = command_line.canonical_command_line(None);
let mut free_p = free.clone();
reconstructed_args.append(&mut free_p);
let mut args = args.to_vec();
args.retain(|a| *a != "--");
reconstructed_args.retain(|a| *a != "--");
if args != reconstructed_args {
panic!(
"non-canonical commandline specified:
provided: {:?}
expected: {:?}
check argument order amongst other differences",
&args, reconstructed_args
);
}
(command_line, free)
}
fn from_arguments_relaxed(usage: &str, args: &[&str]) -> (Self, Vec<String>) {
let mut command_line = Self::default();
let mut opts = getopts::Options::new();
opts.parsing_style(getopts::ParsingStyle::StopAtFirstFree);
opts.long_only(true);
opts.optflag("h", "help", "Print this help menu.");
command_line.add_opts(None, &mut opts);
let matches = match opts.parse(args) {
Ok(matches) => matches,
Err(Fail::OptionMissing(which)) => {
Self::error(&mut command_line, format!("missing argument: --{which}"));
Self::usage(&mut command_line, opts, usage);
return (command_line, vec![]);
}
Err(err) => {
Self::error(
&mut command_line,
format!("could not parse command line: {err}"),
);
Self::exit(&mut command_line, 64);
return (command_line, vec![]);
}
};
if matches.opt_present("h") {
Self::usage(&mut command_line, opts, usage);
return (command_line, vec![]);
}
command_line.matches(None, &matches);
let free: Vec<String> = matches.free.to_vec();
(command_line, free)
}
fn usage(&mut self, opts: getopts::Options, brief: &str) {
self.error(opts.usage(brief));
self.exit(1);
}
fn error(&mut self, msg: impl AsRef<str>) {
eprintln!("{}", msg.as_ref());
}
fn exit(&mut self, status: i32) {
std::process::exit(status);
}
}
#[derive(Default, Eq, PartialEq)]
pub struct NoExitCommandLine<T: CommandLine>(T, Vec<String>, i32);
impl<T: CommandLine> AsRef<T> for NoExitCommandLine<T> {
fn as_ref(&self) -> &T {
&self.0
}
}
impl<T: CommandLine> NoExitCommandLine<T> {
pub fn into_inner(self) -> T {
self.0
}
pub fn into_parts(self) -> (T, Vec<String>, i32) {
(self.0, self.1, self.2)
}
}
impl<T: CommandLine> CommandLine for NoExitCommandLine<T> {
fn add_opts(&self, prefix: Option<&str>, opts: &mut getopts::Options) {
self.0.add_opts(prefix, opts);
}
fn matches(&mut self, prefix: Option<&str>, matches: &getopts::Matches) {
self.0.matches(prefix, matches);
}
fn canonical_command_line(&self, prefix: Option<&str>) -> Vec<String> {
self.0.canonical_command_line(prefix)
}
fn error(&mut self, msg: impl AsRef<str>) {
self.1.push(msg.as_ref().to_string());
}
fn exit(&mut self, status: i32) {
self.2 = status;
}
}
#[doc(hidden)]
pub fn getopt_str(prefix: Option<&str>, field_arg: &str) -> String {
match prefix {
Some(prefix) => {
format!("{prefix}-{field_arg}")
}
None => field_arg.to_string(),
}
}
#[doc(hidden)]
pub fn dashed_str(prefix: Option<&str>, field_arg: &str) -> String {
format!("--{}", getopt_str(prefix, field_arg))
}
#[doc(hidden)]
pub fn parse_field<T>(arg_str: &str, s: &str) -> T
where
T: FromStr,
<T as FromStr>::Err: std::fmt::Display,
{
match s.parse::<T>() {
Ok(t) => t,
Err(err) => {
panic!("field --{arg_str} is unparseable: {err}");
}
}
}