xref: /aosp_15_r20/external/bazelbuild-rules_rust/util/process_wrapper/flags.rs (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
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