1#![doc = include_str!("../README.md")]
2
3use std::str::FromStr;
4
5use getopts::Fail;
6
7pub trait CommandLine: Sized + Default + Eq + PartialEq {
13 fn add_opts(&self, prefix: Option<&str>, opts: &mut getopts::Options);
15
16 fn matches(&mut self, prefix: Option<&str>, matches: &getopts::Matches);
18
19 fn canonical_command_line(&self, prefix: Option<&str>) -> Vec<String>;
21
22 fn from_command_line(usage: &str) -> (Self, Vec<String>) {
25 let args: Vec<String> = std::env::args().collect();
26 let args: Vec<&str> = args.iter().map(AsRef::as_ref).collect();
27 Self::from_arguments(usage, &args[1..])
28 }
29
30 fn from_command_line_relaxed(usage: &str) -> (Self, Vec<String>) {
33 let args: Vec<String> = std::env::args().collect();
34 let args: Vec<&str> = args.iter().map(AsRef::as_ref).collect();
35 Self::from_arguments_relaxed(usage, &args[1..])
36 }
37
38 fn from_arguments(usage: &str, args: &[&str]) -> (Self, Vec<String>) {
41 let (command_line, free) = Self::from_arguments_relaxed(usage, args);
42 let mut reconstructed_args = command_line.canonical_command_line(None);
43 let mut free_p = free.clone();
44 reconstructed_args.append(&mut free_p);
45 let mut args = args.to_vec();
46 args.retain(|a| *a != "--");
47 reconstructed_args.retain(|a| *a != "--");
48 if args != reconstructed_args {
49 panic!(
50 "non-canonical commandline specified:
51provided: {:?}
52expected: {:?}
53check argument order amongst other differences",
54 &args, reconstructed_args
55 );
56 }
57 (command_line, free)
58 }
59
60 fn from_arguments_relaxed(usage: &str, args: &[&str]) -> (Self, Vec<String>) {
63 let mut command_line = Self::default();
64 let mut opts = getopts::Options::new();
65 opts.parsing_style(getopts::ParsingStyle::StopAtFirstFree);
66 opts.long_only(true);
67 opts.optflag("h", "help", "Print this help menu.");
68 command_line.add_opts(None, &mut opts);
69
70 let matches = match opts.parse(args) {
71 Ok(matches) => matches,
72 Err(Fail::OptionMissing(which)) => {
73 Self::error(&mut command_line, format!("missing argument: --{which}"));
74 Self::usage(&mut command_line, opts, usage);
75 return (command_line, vec![]);
76 }
77 Err(err) => {
78 Self::error(
79 &mut command_line,
80 format!("could not parse command line: {err}"),
81 );
82 Self::exit(&mut command_line, 64);
83 return (command_line, vec![]);
84 }
85 };
86 if matches.opt_present("h") {
87 Self::usage(&mut command_line, opts, usage);
88 return (command_line, vec![]);
89 }
90 command_line.matches(None, &matches);
91 let free: Vec<String> = matches.free.to_vec();
92 (command_line, free)
93 }
94
95 fn usage(&mut self, opts: getopts::Options, brief: &str) {
97 self.error(opts.usage(brief));
98 self.exit(1);
99 }
100
101 fn error(&mut self, msg: impl AsRef<str>) {
103 eprintln!("{}", msg.as_ref());
104 }
105
106 fn exit(&mut self, status: i32) {
108 std::process::exit(status);
109 }
110}
111
112#[derive(Default, Eq, PartialEq)]
117pub struct NoExitCommandLine<T: CommandLine>(T, Vec<String>, i32);
118
119impl<T: CommandLine> AsRef<T> for NoExitCommandLine<T> {
120 fn as_ref(&self) -> &T {
121 &self.0
122 }
123}
124
125impl<T: CommandLine> NoExitCommandLine<T> {
126 pub fn into_inner(self) -> T {
127 self.0
128 }
129
130 pub fn into_parts(self) -> (T, Vec<String>, i32) {
131 (self.0, self.1, self.2)
132 }
133}
134
135impl<T: CommandLine> CommandLine for NoExitCommandLine<T> {
136 fn add_opts(&self, prefix: Option<&str>, opts: &mut getopts::Options) {
137 self.0.add_opts(prefix, opts);
138 }
139
140 fn matches(&mut self, prefix: Option<&str>, matches: &getopts::Matches) {
141 self.0.matches(prefix, matches);
142 }
143
144 fn canonical_command_line(&self, prefix: Option<&str>) -> Vec<String> {
145 self.0.canonical_command_line(prefix)
146 }
147
148 fn error(&mut self, msg: impl AsRef<str>) {
149 self.1.push(msg.as_ref().to_string());
150 }
151
152 fn exit(&mut self, status: i32) {
153 self.2 = status;
154 }
155}
156
157#[doc(hidden)]
160pub fn getopt_str(prefix: Option<&str>, field_arg: &str) -> String {
161 match prefix {
162 Some(prefix) => {
163 format!("{prefix}-{field_arg}")
164 }
165 None => field_arg.to_string(),
166 }
167}
168
169#[doc(hidden)]
170pub fn dashed_str(prefix: Option<&str>, field_arg: &str) -> String {
171 format!("--{}", getopt_str(prefix, field_arg))
172}
173
174#[doc(hidden)]
175pub fn parse_field<T>(arg_str: &str, s: &str) -> T
176where
177 T: FromStr,
178 <T as FromStr>::Err: std::fmt::Display,
179{
180 match s.parse::<T>() {
181 Ok(t) => t,
182 Err(err) => {
183 panic!("field --{arg_str} is unparseable: {err}");
184 }
185 }
186}