1*d4726bddSHONG Yifan // Copyright 2020 The Bazel Authors. All rights reserved.
2*d4726bddSHONG Yifan //
3*d4726bddSHONG Yifan // Licensed under the Apache License, Version 2.0 (the "License");
4*d4726bddSHONG Yifan // you may not use this file except in compliance with the License.
5*d4726bddSHONG Yifan // You may obtain a copy of the License at
6*d4726bddSHONG Yifan //
7*d4726bddSHONG Yifan // http://www.apache.org/licenses/LICENSE-2.0
8*d4726bddSHONG Yifan //
9*d4726bddSHONG Yifan // Unless required by applicable law or agreed to in writing, software
10*d4726bddSHONG Yifan // distributed under the License is distributed on an "AS IS" BASIS,
11*d4726bddSHONG Yifan // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*d4726bddSHONG Yifan // See the License for the specific language governing permissions and
13*d4726bddSHONG Yifan // limitations under the License.
14*d4726bddSHONG Yifan
15*d4726bddSHONG Yifan use std::collections::{BTreeMap, HashSet};
16*d4726bddSHONG Yifan use std::error::Error;
17*d4726bddSHONG Yifan use std::fmt;
18*d4726bddSHONG Yifan use std::fmt::Write;
19*d4726bddSHONG Yifan use std::iter::Peekable;
20*d4726bddSHONG Yifan use std::mem::take;
21*d4726bddSHONG Yifan
22*d4726bddSHONG Yifan #[derive(Debug, Clone)]
23*d4726bddSHONG Yifan pub(crate) enum FlagParseError {
24*d4726bddSHONG Yifan UnknownFlag(String),
25*d4726bddSHONG Yifan ValueMissing(String),
26*d4726bddSHONG Yifan ProvidedMultipleTimes(String),
27*d4726bddSHONG Yifan ProgramNameMissing,
28*d4726bddSHONG Yifan }
29*d4726bddSHONG Yifan
30*d4726bddSHONG Yifan impl fmt::Display for FlagParseError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result31*d4726bddSHONG Yifan fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32*d4726bddSHONG Yifan match self {
33*d4726bddSHONG Yifan Self::UnknownFlag(ref flag) => write!(f, "unknown flag \"{flag}\""),
34*d4726bddSHONG Yifan Self::ValueMissing(ref flag) => write!(f, "flag \"{flag}\" missing parameter(s)"),
35*d4726bddSHONG Yifan Self::ProvidedMultipleTimes(ref flag) => {
36*d4726bddSHONG Yifan write!(f, "flag \"{flag}\" can only appear once")
37*d4726bddSHONG Yifan }
38*d4726bddSHONG Yifan Self::ProgramNameMissing => {
39*d4726bddSHONG Yifan write!(f, "program name (argv[0]) missing")
40*d4726bddSHONG Yifan }
41*d4726bddSHONG Yifan }
42*d4726bddSHONG Yifan }
43*d4726bddSHONG Yifan }
44*d4726bddSHONG Yifan impl Error for FlagParseError {}
45*d4726bddSHONG Yifan
46*d4726bddSHONG Yifan struct FlagDef<'a, T> {
47*d4726bddSHONG Yifan name: String,
48*d4726bddSHONG Yifan help: String,
49*d4726bddSHONG Yifan output_storage: &'a mut Option<T>,
50*d4726bddSHONG Yifan }
51*d4726bddSHONG Yifan
52*d4726bddSHONG Yifan impl<'a, T> fmt::Display for FlagDef<'a, T> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result53*d4726bddSHONG Yifan fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54*d4726bddSHONG Yifan write!(f, "{}\t{}", self.name, self.help)
55*d4726bddSHONG Yifan }
56*d4726bddSHONG Yifan }
57*d4726bddSHONG Yifan
58*d4726bddSHONG Yifan impl<'a, T> fmt::Debug for FlagDef<'a, T> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result59*d4726bddSHONG Yifan fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60*d4726bddSHONG Yifan f.debug_struct("FlagDef")
61*d4726bddSHONG Yifan .field("name", &self.name)
62*d4726bddSHONG Yifan .field("help", &self.help)
63*d4726bddSHONG Yifan .finish()
64*d4726bddSHONG Yifan }
65*d4726bddSHONG Yifan }
66*d4726bddSHONG Yifan
67*d4726bddSHONG Yifan #[derive(Debug)]
68*d4726bddSHONG Yifan pub(crate) struct Flags<'a> {
69*d4726bddSHONG Yifan single: BTreeMap<String, FlagDef<'a, String>>,
70*d4726bddSHONG Yifan repeated: BTreeMap<String, FlagDef<'a, Vec<String>>>,
71*d4726bddSHONG Yifan }
72*d4726bddSHONG Yifan
73*d4726bddSHONG Yifan #[derive(Debug)]
74*d4726bddSHONG Yifan pub(crate) enum ParseOutcome {
75*d4726bddSHONG Yifan Help(String),
76*d4726bddSHONG Yifan Parsed(Vec<String>),
77*d4726bddSHONG Yifan }
78*d4726bddSHONG Yifan
79*d4726bddSHONG Yifan impl<'a> Flags<'a> {
new() -> Flags<'a>80*d4726bddSHONG Yifan pub(crate) fn new() -> Flags<'a> {
81*d4726bddSHONG Yifan Flags {
82*d4726bddSHONG Yifan single: BTreeMap::new(),
83*d4726bddSHONG Yifan repeated: BTreeMap::new(),
84*d4726bddSHONG Yifan }
85*d4726bddSHONG Yifan }
86*d4726bddSHONG Yifan
define_flag( &mut self, name: impl Into<String>, help: impl Into<String>, output_storage: &'a mut Option<String>, )87*d4726bddSHONG Yifan pub(crate) fn define_flag(
88*d4726bddSHONG Yifan &mut self,
89*d4726bddSHONG Yifan name: impl Into<String>,
90*d4726bddSHONG Yifan help: impl Into<String>,
91*d4726bddSHONG Yifan output_storage: &'a mut Option<String>,
92*d4726bddSHONG Yifan ) {
93*d4726bddSHONG Yifan let name = name.into();
94*d4726bddSHONG Yifan if self.repeated.contains_key(&name) {
95*d4726bddSHONG Yifan panic!("argument \"{}\" already defined as repeated flag", name)
96*d4726bddSHONG Yifan }
97*d4726bddSHONG Yifan self.single.insert(
98*d4726bddSHONG Yifan name.clone(),
99*d4726bddSHONG Yifan FlagDef::<'a, String> {
100*d4726bddSHONG Yifan name,
101*d4726bddSHONG Yifan help: help.into(),
102*d4726bddSHONG Yifan output_storage,
103*d4726bddSHONG Yifan },
104*d4726bddSHONG Yifan );
105*d4726bddSHONG Yifan }
106*d4726bddSHONG Yifan
define_repeated_flag( &mut self, name: impl Into<String>, help: impl Into<String>, output_storage: &'a mut Option<Vec<String>>, )107*d4726bddSHONG Yifan pub(crate) fn define_repeated_flag(
108*d4726bddSHONG Yifan &mut self,
109*d4726bddSHONG Yifan name: impl Into<String>,
110*d4726bddSHONG Yifan help: impl Into<String>,
111*d4726bddSHONG Yifan output_storage: &'a mut Option<Vec<String>>,
112*d4726bddSHONG Yifan ) {
113*d4726bddSHONG Yifan let name = name.into();
114*d4726bddSHONG Yifan if self.single.contains_key(&name) {
115*d4726bddSHONG Yifan panic!("argument \"{}\" already defined as flag", name)
116*d4726bddSHONG Yifan }
117*d4726bddSHONG Yifan self.repeated.insert(
118*d4726bddSHONG Yifan name.clone(),
119*d4726bddSHONG Yifan FlagDef::<'a, Vec<String>> {
120*d4726bddSHONG Yifan name,
121*d4726bddSHONG Yifan help: help.into(),
122*d4726bddSHONG Yifan output_storage,
123*d4726bddSHONG Yifan },
124*d4726bddSHONG Yifan );
125*d4726bddSHONG Yifan }
126*d4726bddSHONG Yifan
help(&self, program_name: String) -> String127*d4726bddSHONG Yifan fn help(&self, program_name: String) -> String {
128*d4726bddSHONG Yifan let single = self.single.values().map(|fd| fd.to_string());
129*d4726bddSHONG Yifan let repeated = self.repeated.values().map(|fd| fd.to_string());
130*d4726bddSHONG Yifan let mut all: Vec<String> = single.chain(repeated).collect();
131*d4726bddSHONG Yifan all.sort();
132*d4726bddSHONG Yifan
133*d4726bddSHONG Yifan let mut help_text = String::new();
134*d4726bddSHONG Yifan writeln!(
135*d4726bddSHONG Yifan &mut help_text,
136*d4726bddSHONG Yifan "Help for {program_name}: [options] -- [extra arguments]"
137*d4726bddSHONG Yifan )
138*d4726bddSHONG Yifan .unwrap();
139*d4726bddSHONG Yifan for line in all {
140*d4726bddSHONG Yifan writeln!(&mut help_text, "\t{line}").unwrap();
141*d4726bddSHONG Yifan }
142*d4726bddSHONG Yifan help_text
143*d4726bddSHONG Yifan }
144*d4726bddSHONG Yifan
parse(mut self, argv: Vec<String>) -> Result<ParseOutcome, FlagParseError>145*d4726bddSHONG Yifan pub(crate) fn parse(mut self, argv: Vec<String>) -> Result<ParseOutcome, FlagParseError> {
146*d4726bddSHONG Yifan let mut argv_iter = argv.into_iter().peekable();
147*d4726bddSHONG Yifan let program_name = argv_iter.next().ok_or(FlagParseError::ProgramNameMissing)?;
148*d4726bddSHONG Yifan
149*d4726bddSHONG Yifan // To check if a non-repeated flag has been set already.
150*d4726bddSHONG Yifan let mut seen_single_flags = HashSet::<String>::new();
151*d4726bddSHONG Yifan
152*d4726bddSHONG Yifan while let Some(flag) = argv_iter.next() {
153*d4726bddSHONG Yifan if flag == "--help" {
154*d4726bddSHONG Yifan return Ok(ParseOutcome::Help(self.help(program_name)));
155*d4726bddSHONG Yifan }
156*d4726bddSHONG Yifan if !flag.starts_with("--") {
157*d4726bddSHONG Yifan return Err(FlagParseError::UnknownFlag(flag));
158*d4726bddSHONG Yifan }
159*d4726bddSHONG Yifan let mut args = consume_args(&flag, &mut argv_iter);
160*d4726bddSHONG Yifan if flag == "--" {
161*d4726bddSHONG Yifan return Ok(ParseOutcome::Parsed(args));
162*d4726bddSHONG Yifan }
163*d4726bddSHONG Yifan if args.is_empty() {
164*d4726bddSHONG Yifan return Err(FlagParseError::ValueMissing(flag.clone()));
165*d4726bddSHONG Yifan }
166*d4726bddSHONG Yifan if let Some(flag_def) = self.single.get_mut(&flag) {
167*d4726bddSHONG Yifan if args.len() > 1 || seen_single_flags.contains(&flag) {
168*d4726bddSHONG Yifan return Err(FlagParseError::ProvidedMultipleTimes(flag.clone()));
169*d4726bddSHONG Yifan }
170*d4726bddSHONG Yifan let arg = args.first_mut().unwrap();
171*d4726bddSHONG Yifan seen_single_flags.insert(flag);
172*d4726bddSHONG Yifan *flag_def.output_storage = Some(take(arg));
173*d4726bddSHONG Yifan continue;
174*d4726bddSHONG Yifan }
175*d4726bddSHONG Yifan if let Some(flag_def) = self.repeated.get_mut(&flag) {
176*d4726bddSHONG Yifan flag_def
177*d4726bddSHONG Yifan .output_storage
178*d4726bddSHONG Yifan .get_or_insert_with(Vec::new)
179*d4726bddSHONG Yifan .append(&mut args);
180*d4726bddSHONG Yifan continue;
181*d4726bddSHONG Yifan }
182*d4726bddSHONG Yifan return Err(FlagParseError::UnknownFlag(flag));
183*d4726bddSHONG Yifan }
184*d4726bddSHONG Yifan Ok(ParseOutcome::Parsed(vec![]))
185*d4726bddSHONG Yifan }
186*d4726bddSHONG Yifan }
187*d4726bddSHONG Yifan
consume_args<I: Iterator<Item = String>>( flag: &str, argv_iter: &mut Peekable<I>, ) -> Vec<String>188*d4726bddSHONG Yifan fn consume_args<I: Iterator<Item = String>>(
189*d4726bddSHONG Yifan flag: &str,
190*d4726bddSHONG Yifan argv_iter: &mut Peekable<I>,
191*d4726bddSHONG Yifan ) -> Vec<String> {
192*d4726bddSHONG Yifan if flag == "--" {
193*d4726bddSHONG Yifan // If we have found --, the rest of the iterator is just returned as-is.
194*d4726bddSHONG Yifan argv_iter.collect()
195*d4726bddSHONG Yifan } else {
196*d4726bddSHONG Yifan let mut args = vec![];
197*d4726bddSHONG Yifan while let Some(arg) = argv_iter.next_if(|s| !s.starts_with("--")) {
198*d4726bddSHONG Yifan args.push(arg);
199*d4726bddSHONG Yifan }
200*d4726bddSHONG Yifan args
201*d4726bddSHONG Yifan }
202*d4726bddSHONG Yifan }
203*d4726bddSHONG Yifan
204*d4726bddSHONG Yifan #[cfg(test)]
205*d4726bddSHONG Yifan mod test {
206*d4726bddSHONG Yifan use super::*;
207*d4726bddSHONG Yifan
args(args: &[&str]) -> Vec<String>208*d4726bddSHONG Yifan fn args(args: &[&str]) -> Vec<String> {
209*d4726bddSHONG Yifan ["foo"].iter().chain(args).map(|&s| s.to_owned()).collect()
210*d4726bddSHONG Yifan }
211*d4726bddSHONG Yifan
212*d4726bddSHONG Yifan #[test]
test_flag_help()213*d4726bddSHONG Yifan fn test_flag_help() {
214*d4726bddSHONG Yifan let mut bar = None;
215*d4726bddSHONG Yifan let mut parser = Flags::new();
216*d4726bddSHONG Yifan parser.define_flag("--bar", "bar help", &mut bar);
217*d4726bddSHONG Yifan let result = parser.parse(args(&["--help"])).unwrap();
218*d4726bddSHONG Yifan if let ParseOutcome::Help(h) = result {
219*d4726bddSHONG Yifan assert!(h.contains("Help for foo"));
220*d4726bddSHONG Yifan assert!(h.contains("--bar\tbar help"));
221*d4726bddSHONG Yifan } else {
222*d4726bddSHONG Yifan panic!("expected that --help would invoke help, instead parsed arguments")
223*d4726bddSHONG Yifan }
224*d4726bddSHONG Yifan }
225*d4726bddSHONG Yifan
226*d4726bddSHONG Yifan #[test]
test_flag_single_repeated()227*d4726bddSHONG Yifan fn test_flag_single_repeated() {
228*d4726bddSHONG Yifan let mut bar = None;
229*d4726bddSHONG Yifan let mut parser = Flags::new();
230*d4726bddSHONG Yifan parser.define_flag("--bar", "bar help", &mut bar);
231*d4726bddSHONG Yifan let result = parser.parse(args(&["--bar", "aa", "bb"]));
232*d4726bddSHONG Yifan if let Err(FlagParseError::ProvidedMultipleTimes(f)) = result {
233*d4726bddSHONG Yifan assert_eq!(f, "--bar");
234*d4726bddSHONG Yifan } else {
235*d4726bddSHONG Yifan panic!("expected error, got {:?}", result)
236*d4726bddSHONG Yifan }
237*d4726bddSHONG Yifan let mut parser = Flags::new();
238*d4726bddSHONG Yifan parser.define_flag("--bar", "bar help", &mut bar);
239*d4726bddSHONG Yifan let result = parser.parse(args(&["--bar", "aa", "--bar", "bb"]));
240*d4726bddSHONG Yifan if let Err(FlagParseError::ProvidedMultipleTimes(f)) = result {
241*d4726bddSHONG Yifan assert_eq!(f, "--bar");
242*d4726bddSHONG Yifan } else {
243*d4726bddSHONG Yifan panic!("expected error, got {:?}", result)
244*d4726bddSHONG Yifan }
245*d4726bddSHONG Yifan }
246*d4726bddSHONG Yifan
247*d4726bddSHONG Yifan #[test]
test_repeated_flags()248*d4726bddSHONG Yifan fn test_repeated_flags() {
249*d4726bddSHONG Yifan // Test case 1) --bar something something_else should work as a repeated flag.
250*d4726bddSHONG Yifan let mut bar = None;
251*d4726bddSHONG Yifan let mut parser = Flags::new();
252*d4726bddSHONG Yifan parser.define_repeated_flag("--bar", "bar help", &mut bar);
253*d4726bddSHONG Yifan let result = parser.parse(args(&["--bar", "aa", "bb"])).unwrap();
254*d4726bddSHONG Yifan assert!(matches!(result, ParseOutcome::Parsed(_)));
255*d4726bddSHONG Yifan assert_eq!(bar, Some(vec!["aa".to_owned(), "bb".to_owned()]));
256*d4726bddSHONG Yifan // Test case 2) --bar something --bar something_else should also work as a repeated flag.
257*d4726bddSHONG Yifan bar = None;
258*d4726bddSHONG Yifan let mut parser = Flags::new();
259*d4726bddSHONG Yifan parser.define_repeated_flag("--bar", "bar help", &mut bar);
260*d4726bddSHONG Yifan let result = parser.parse(args(&["--bar", "aa", "--bar", "bb"])).unwrap();
261*d4726bddSHONG Yifan assert!(matches!(result, ParseOutcome::Parsed(_)));
262*d4726bddSHONG Yifan assert_eq!(bar, Some(vec!["aa".to_owned(), "bb".to_owned()]));
263*d4726bddSHONG Yifan }
264*d4726bddSHONG Yifan
265*d4726bddSHONG Yifan #[test]
test_extra_args()266*d4726bddSHONG Yifan fn test_extra_args() {
267*d4726bddSHONG Yifan let parser = Flags::new();
268*d4726bddSHONG Yifan let result = parser.parse(args(&["--", "bb"])).unwrap();
269*d4726bddSHONG Yifan if let ParseOutcome::Parsed(got) = result {
270*d4726bddSHONG Yifan assert_eq!(got, vec!["bb".to_owned()])
271*d4726bddSHONG Yifan } else {
272*d4726bddSHONG Yifan panic!("expected correct parsing, got {:?}", result)
273*d4726bddSHONG Yifan }
274*d4726bddSHONG Yifan }
275*d4726bddSHONG Yifan }
276