Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit f39c33e

Browse files
committedAug 27, 2021
feat(cpio): add rust argument parsing library from crosvm
Crosvm's rust argument library is very small and simple, while still providing helpful functionality. It will be consumed by dracut-cpio in a subsequent commit. The unmodified, BSD licensed argument.rs source is lifted as-is from https://chromium.googlesource.com/chromiumos/platform/crosvm (release-R92-13982.B b6ae6517aeef9ae1e3a39c55b52f9ac6de8edb31). The one-line crosvm.rs wrapper is needed to ensure that crosvm::argument imports continue to work. Signed-off-by: David Disseldorp <[email protected]>
1 parent b2d310f commit f39c33e

File tree

4 files changed

+586
-0
lines changed

4 files changed

+586
-0
lines changed
 
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "crosvm"
3+
version = "0.1.0"
4+
authors = ["The Chromium OS Authors"]
5+
edition = "2018"
6+
7+
[lib]
8+
path = "crosvm.rs"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2017 The Chromium OS Authors. All rights reserved.
2+
//
3+
// Redistribution and use in source and binary forms, with or without
4+
// modification, are permitted provided that the following conditions are
5+
// met:
6+
//
7+
// * Redistributions of source code must retain the above copyright
8+
// notice, this list of conditions and the following disclaimer.
9+
// * Redistributions in binary form must reproduce the above
10+
// copyright notice, this list of conditions and the following disclaimer
11+
// in the documentation and/or other materials provided with the
12+
// distribution.
13+
// * Neither the name of Google Inc. nor the names of its
14+
// contributors may be used to endorse or promote products derived from
15+
// this software without specific prior written permission.
16+
//
17+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18+
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19+
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20+
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21+
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22+
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23+
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24+
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25+
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26+
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Lines changed: 549 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,549 @@
1+
// Copyright 2017 The Chromium OS Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
//! Handles argument parsing.
6+
//!
7+
//! # Example
8+
//!
9+
//! ```
10+
//! # use crosvm::argument::{Argument, Error, print_help, set_arguments};
11+
//! # let args: std::slice::Iter<String> = [].iter();
12+
//! let arguments = &[
13+
//! Argument::positional("FILES", "files to operate on"),
14+
//! Argument::short_value('p', "program", "PROGRAM", "Program to apply to each file"),
15+
//! Argument::short_value('c', "cpus", "N", "Number of CPUs to use. (default: 1)"),
16+
//! Argument::flag("unmount", "Unmount the root"),
17+
//! Argument::short_flag('h', "help", "Print help message."),
18+
//! ];
19+
//!
20+
//! let match_res = set_arguments(args, arguments, |name, value| {
21+
//! match name {
22+
//! "" => println!("positional arg! {}", value.unwrap()),
23+
//! "program" => println!("gonna use program {}", value.unwrap()),
24+
//! "cpus" => {
25+
//! let v: u32 = value.unwrap().parse().map_err(|_| {
26+
//! Error::InvalidValue {
27+
//! value: value.unwrap().to_owned(),
28+
//! expected: String::from("this value for `cpus` needs to be integer"),
29+
//! }
30+
//! })?;
31+
//! }
32+
//! "unmount" => println!("gonna unmount"),
33+
//! "help" => return Err(Error::PrintHelp),
34+
//! _ => unreachable!(),
35+
//! }
36+
//! unreachable!();
37+
//! });
38+
//!
39+
//! match match_res {
40+
//! Ok(_) => println!("running with settings"),
41+
//! Err(Error::PrintHelp) => print_help("best_program", "FILES", arguments),
42+
//! Err(e) => println!("{}", e),
43+
//! }
44+
//! ```
45+
46+
use std::fmt::{self, Display};
47+
use std::result;
48+
49+
/// An error with argument parsing.
50+
#[derive(Debug)]
51+
pub enum Error {
52+
/// There was a syntax error with the argument.
53+
Syntax(String),
54+
/// The argument's name is unused.
55+
UnknownArgument(String),
56+
/// The argument was required.
57+
ExpectedArgument(String),
58+
/// The argument's given value is invalid.
59+
InvalidValue { value: String, expected: String },
60+
/// The argument was already given and none more are expected.
61+
TooManyArguments(String),
62+
/// The argument expects a value.
63+
ExpectedValue(String),
64+
/// The argument does not expect a value.
65+
UnexpectedValue(String),
66+
/// The help information was requested
67+
PrintHelp,
68+
}
69+
70+
impl Display for Error {
71+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72+
use self::Error::*;
73+
74+
match self {
75+
Syntax(s) => write!(f, "syntax error: {}", s),
76+
UnknownArgument(s) => write!(f, "unknown argument: {}", s),
77+
ExpectedArgument(s) => write!(f, "expected argument: {}", s),
78+
InvalidValue { value, expected } => {
79+
write!(f, "invalid value {:?}: {}", value, expected)
80+
}
81+
TooManyArguments(s) => write!(f, "too many arguments: {}", s),
82+
ExpectedValue(s) => write!(f, "expected parameter value: {}", s),
83+
UnexpectedValue(s) => write!(f, "unexpected parameter value: {}", s),
84+
PrintHelp => write!(f, "help was requested"),
85+
}
86+
}
87+
}
88+
89+
/// Result of a argument parsing.
90+
pub type Result<T> = result::Result<T, Error>;
91+
92+
#[derive(Debug, PartialEq)]
93+
pub enum ArgumentValueMode {
94+
/// Specifies that an argument requires a value and that an error should be generated if
95+
/// no value is provided during parsing.
96+
Required,
97+
98+
/// Specifies that an argument does not allow a value and that an error should be returned
99+
/// if a value is provided during parsing.
100+
Disallowed,
101+
102+
/// Specifies that an argument may have a value during parsing but is not required to.
103+
Optional,
104+
}
105+
106+
/// Information about an argument expected from the command line.
107+
///
108+
/// # Examples
109+
///
110+
/// To indicate a flag style argument:
111+
///
112+
/// ```
113+
/// # use crosvm::argument::Argument;
114+
/// Argument::short_flag('f', "flag", "enable awesome mode");
115+
/// ```
116+
///
117+
/// To indicate a parameter style argument that expects a value:
118+
///
119+
/// ```
120+
/// # use crosvm::argument::Argument;
121+
/// // "VALUE" and "NETMASK" are placeholder values displayed in the help message for these
122+
/// // arguments.
123+
/// Argument::short_value('v', "val", "VALUE", "how much do you value this usage information");
124+
/// Argument::value("netmask", "NETMASK", "hides your netface");
125+
/// ```
126+
///
127+
/// To indicate an argument with no short version:
128+
///
129+
/// ```
130+
/// # use crosvm::argument::Argument;
131+
/// Argument::flag("verbose", "this option is hard to type quickly");
132+
/// ```
133+
///
134+
/// To indicate a positional argument:
135+
///
136+
/// ```
137+
/// # use crosvm::argument::Argument;
138+
/// Argument::positional("VALUES", "these are positional arguments");
139+
/// ```
140+
pub struct Argument {
141+
/// The name of the value to display in the usage information.
142+
pub value: Option<&'static str>,
143+
/// Specifies how values should be handled for this this argument.
144+
pub value_mode: ArgumentValueMode,
145+
/// Optional single character shortened argument name.
146+
pub short: Option<char>,
147+
/// The long name of this argument.
148+
pub long: &'static str,
149+
/// Helpfuly usage information for this argument to display to the user.
150+
pub help: &'static str,
151+
}
152+
153+
impl Argument {
154+
pub fn positional(value: &'static str, help: &'static str) -> Argument {
155+
Argument {
156+
value: Some(value),
157+
value_mode: ArgumentValueMode::Required,
158+
short: None,
159+
long: "",
160+
help,
161+
}
162+
}
163+
164+
pub fn value(long: &'static str, value: &'static str, help: &'static str) -> Argument {
165+
Argument {
166+
value: Some(value),
167+
value_mode: ArgumentValueMode::Required,
168+
short: None,
169+
long,
170+
help,
171+
}
172+
}
173+
174+
pub fn short_value(
175+
short: char,
176+
long: &'static str,
177+
value: &'static str,
178+
help: &'static str,
179+
) -> Argument {
180+
Argument {
181+
value: Some(value),
182+
value_mode: ArgumentValueMode::Required,
183+
short: Some(short),
184+
long,
185+
help,
186+
}
187+
}
188+
189+
pub fn flag(long: &'static str, help: &'static str) -> Argument {
190+
Argument {
191+
value: None,
192+
value_mode: ArgumentValueMode::Disallowed,
193+
short: None,
194+
long,
195+
help,
196+
}
197+
}
198+
199+
pub fn short_flag(short: char, long: &'static str, help: &'static str) -> Argument {
200+
Argument {
201+
value: None,
202+
value_mode: ArgumentValueMode::Disallowed,
203+
short: Some(short),
204+
long,
205+
help,
206+
}
207+
}
208+
209+
pub fn flag_or_value(long: &'static str, value: &'static str, help: &'static str) -> Argument {
210+
Argument {
211+
value: Some(value),
212+
value_mode: ArgumentValueMode::Optional,
213+
short: None,
214+
long,
215+
help,
216+
}
217+
}
218+
}
219+
220+
fn parse_arguments<I, R, F>(args: I, mut f: F) -> Result<()>
221+
where
222+
I: Iterator<Item = R>,
223+
R: AsRef<str>,
224+
F: FnMut(&str, Option<&str>) -> Result<()>,
225+
{
226+
enum State {
227+
// Initial state at the start and after finishing a single argument/value.
228+
Top,
229+
// The remaining arguments are all positional.
230+
Positional,
231+
// The next string is the value for the argument `name`.
232+
Value { name: String },
233+
}
234+
let mut s = State::Top;
235+
for arg in args {
236+
let arg = arg.as_ref();
237+
loop {
238+
let mut arg_consumed = true;
239+
s = match s {
240+
State::Top => {
241+
if arg == "--" {
242+
State::Positional
243+
} else if arg.starts_with("--") {
244+
let param = arg.trim_start_matches('-');
245+
if param.contains('=') {
246+
let mut iter = param.splitn(2, '=');
247+
let name = iter.next().unwrap();
248+
let value = iter.next().unwrap();
249+
if name.is_empty() {
250+
return Err(Error::Syntax(
251+
"expected parameter name before `=`".to_owned(),
252+
));
253+
}
254+
if value.is_empty() {
255+
return Err(Error::Syntax(
256+
"expected parameter value after `=`".to_owned(),
257+
));
258+
}
259+
f(name, Some(value))?;
260+
State::Top
261+
} else {
262+
State::Value {
263+
name: param.to_owned(),
264+
}
265+
}
266+
} else if arg.starts_with('-') {
267+
if arg.len() == 1 {
268+
return Err(Error::Syntax(
269+
"expected argument short name after `-`".to_owned(),
270+
));
271+
}
272+
let name = &arg[1..2];
273+
let value = if arg.len() > 2 { Some(&arg[2..]) } else { None };
274+
if let Err(e) = f(name, value) {
275+
if let Error::ExpectedValue(_) = e {
276+
State::Value {
277+
name: name.to_owned(),
278+
}
279+
} else {
280+
return Err(e);
281+
}
282+
} else {
283+
State::Top
284+
}
285+
} else {
286+
f("", Some(&arg))?;
287+
State::Positional
288+
}
289+
}
290+
State::Positional => {
291+
f("", Some(&arg))?;
292+
State::Positional
293+
}
294+
State::Value { name } => {
295+
if arg.starts_with('-') {
296+
arg_consumed = false;
297+
f(&name, None)?;
298+
} else if let Err(e) = f(&name, Some(&arg)) {
299+
arg_consumed = false;
300+
f(&name, None).map_err(|_| e)?;
301+
}
302+
State::Top
303+
}
304+
};
305+
306+
if arg_consumed {
307+
break;
308+
}
309+
}
310+
}
311+
312+
// If we ran out of arguments while parsing the last parameter, which may be either a
313+
// value parameter or a flag, try to parse it as a flag. This will produce "missing value"
314+
// error if the parameter is in fact a value parameter, which is the desired outcome.
315+
match s {
316+
State::Value { name } => f(&name, None),
317+
_ => Ok(()),
318+
}
319+
}
320+
321+
/// Parses the given `args` against the list of know arguments `arg_list` and calls `f` with each
322+
/// present argument and value if required.
323+
///
324+
/// This function guarantees that only valid long argument names from `arg_list` are sent to the
325+
/// callback `f`. It is also guaranteed that if an arg requires a value (i.e.
326+
/// `arg.value.is_some()`), the value will be `Some` in the callbacks arguments. If the callback
327+
/// returns `Err`, this function will end parsing and return that `Err`.
328+
///
329+
/// See the [module level](index.html) example for a usage example.
330+
pub fn set_arguments<I, R, F>(args: I, arg_list: &[Argument], mut f: F) -> Result<()>
331+
where
332+
I: Iterator<Item = R>,
333+
R: AsRef<str>,
334+
F: FnMut(&str, Option<&str>) -> Result<()>,
335+
{
336+
parse_arguments(args, |name, value| {
337+
let mut matches = None;
338+
for arg in arg_list {
339+
if let Some(short) = arg.short {
340+
if name.len() == 1 && name.starts_with(short) {
341+
if value.is_some() != arg.value.is_some() {
342+
return Err(Error::ExpectedValue(short.to_string()));
343+
}
344+
matches = Some(arg.long);
345+
}
346+
}
347+
if matches.is_none() && arg.long == name {
348+
if value.is_none() && arg.value_mode == ArgumentValueMode::Required {
349+
return Err(Error::ExpectedValue(arg.long.to_owned()));
350+
}
351+
if value.is_some() && arg.value_mode == ArgumentValueMode::Disallowed {
352+
return Err(Error::UnexpectedValue(arg.long.to_owned()));
353+
}
354+
matches = Some(arg.long);
355+
}
356+
}
357+
match matches {
358+
Some(long) => f(long, value),
359+
None => Err(Error::UnknownArgument(name.to_owned())),
360+
}
361+
})
362+
}
363+
364+
/// Prints command line usage information to stdout.
365+
///
366+
/// Usage information is printed according to the help fields in `args` with a leading usage line.
367+
/// The usage line is of the format "`program_name` \[ARGUMENTS\] `required_arg`".
368+
pub fn print_help(program_name: &str, required_arg: &str, args: &[Argument]) {
369+
println!(
370+
"Usage: {} {}{}\n",
371+
program_name,
372+
if args.is_empty() { "" } else { "[ARGUMENTS] " },
373+
required_arg
374+
);
375+
if args.is_empty() {
376+
return;
377+
}
378+
println!("Argument{}:", if args.len() > 1 { "s" } else { "" });
379+
for arg in args {
380+
match arg.short {
381+
Some(s) => print!(" -{}, ", s),
382+
None => print!(" "),
383+
}
384+
if arg.long.is_empty() {
385+
print!(" ");
386+
} else {
387+
print!("--");
388+
}
389+
print!("{:<12}", arg.long);
390+
if let Some(v) = arg.value {
391+
if arg.long.is_empty() {
392+
print!(" ");
393+
} else {
394+
print!("=");
395+
}
396+
print!("{:<10}", v);
397+
} else {
398+
print!("{:<11}", "");
399+
}
400+
println!("{}", arg.help);
401+
}
402+
}
403+
404+
#[cfg(test)]
405+
mod tests {
406+
use super::*;
407+
408+
#[test]
409+
fn request_help() {
410+
let arguments = [Argument::short_flag('h', "help", "Print help message.")];
411+
412+
let match_res = set_arguments(["-h"].iter(), &arguments[..], |name, _| {
413+
match name {
414+
"help" => return Err(Error::PrintHelp),
415+
_ => unreachable!(),
416+
};
417+
});
418+
match match_res {
419+
Err(Error::PrintHelp) => {}
420+
_ => unreachable!(),
421+
}
422+
}
423+
424+
#[test]
425+
fn mixed_args() {
426+
let arguments = [
427+
Argument::positional("FILES", "files to operate on"),
428+
Argument::short_value('p', "program", "PROGRAM", "Program to apply to each file"),
429+
Argument::short_value('c', "cpus", "N", "Number of CPUs to use. (default: 1)"),
430+
Argument::flag("unmount", "Unmount the root"),
431+
Argument::short_flag('h', "help", "Print help message."),
432+
];
433+
434+
let mut unmount = false;
435+
let match_res = set_arguments(
436+
["--cpus", "3", "--program", "hello", "--unmount", "file"].iter(),
437+
&arguments[..],
438+
|name, value| {
439+
match name {
440+
"" => assert_eq!(value.unwrap(), "file"),
441+
"program" => assert_eq!(value.unwrap(), "hello"),
442+
"cpus" => {
443+
let c: u32 = value.unwrap().parse().map_err(|_| Error::InvalidValue {
444+
value: value.unwrap().to_owned(),
445+
expected: String::from("this value for `cpus` needs to be integer"),
446+
})?;
447+
assert_eq!(c, 3);
448+
}
449+
"unmount" => unmount = true,
450+
"help" => return Err(Error::PrintHelp),
451+
_ => unreachable!(),
452+
};
453+
Ok(())
454+
},
455+
);
456+
assert!(match_res.is_ok());
457+
assert!(unmount);
458+
}
459+
460+
#[test]
461+
fn name_value_pair() {
462+
let arguments = [Argument::short_value(
463+
'c',
464+
"cpus",
465+
"N",
466+
"Number of CPUs to use. (default: 1)",
467+
)];
468+
let match_res = set_arguments(
469+
["-c", "5", "--cpus", "5", "-c5", "--cpus=5"].iter(),
470+
&arguments[..],
471+
|name, value| {
472+
assert_eq!(name, "cpus");
473+
assert_eq!(value, Some("5"));
474+
Ok(())
475+
},
476+
);
477+
assert!(match_res.is_ok());
478+
let not_match_res = set_arguments(
479+
["-c", "5", "--cpus"].iter(),
480+
&arguments[..],
481+
|name, value| {
482+
assert_eq!(name, "cpus");
483+
assert_eq!(value, Some("5"));
484+
Ok(())
485+
},
486+
);
487+
assert!(not_match_res.is_err());
488+
}
489+
490+
#[test]
491+
fn flag_or_value() {
492+
let run_case = |args| -> Option<String> {
493+
let arguments = [
494+
Argument::positional("FILES", "files to operate on"),
495+
Argument::flag_or_value("gpu", "[2D|3D]", "Enable or configure gpu"),
496+
Argument::flag("foo", "Enable foo."),
497+
Argument::value("bar", "stuff", "Configure bar."),
498+
];
499+
500+
let mut gpu_value: Option<String> = None;
501+
let match_res =
502+
set_arguments(args, &arguments[..], |name: &str, value: Option<&str>| {
503+
match name {
504+
"" => assert_eq!(value.unwrap(), "file1"),
505+
"foo" => assert!(value.is_none()),
506+
"bar" => assert_eq!(value.unwrap(), "stuff"),
507+
"gpu" => match value {
508+
Some(v) => match v {
509+
"2D" | "3D" => {
510+
gpu_value = Some(v.to_string());
511+
}
512+
_ => {
513+
return Err(Error::InvalidValue {
514+
value: v.to_string(),
515+
expected: String::from("2D or 3D"),
516+
})
517+
}
518+
},
519+
None => {
520+
gpu_value = None;
521+
}
522+
},
523+
_ => unreachable!(),
524+
};
525+
Ok(())
526+
});
527+
528+
assert!(match_res.is_ok());
529+
gpu_value
530+
};
531+
532+
// Used as flag and followed by positional
533+
assert_eq!(run_case(["--gpu", "file1"].iter()), None);
534+
// Used as flag and followed by flag
535+
assert_eq!(run_case(["--gpu", "--foo", "file1",].iter()), None);
536+
// Used as flag and followed by value
537+
assert_eq!(run_case(["--gpu", "--bar=stuff", "file1"].iter()), None);
538+
539+
// Used as value and followed by positional
540+
assert_eq!(run_case(["--gpu=2D", "file1"].iter()).unwrap(), "2D");
541+
// Used as value and followed by flag
542+
assert_eq!(run_case(["--gpu=2D", "--foo"].iter()).unwrap(), "2D");
543+
// Used as value and followed by value
544+
assert_eq!(
545+
run_case(["--gpu=2D", "--bar=stuff", "file1"].iter()).unwrap(),
546+
"2D"
547+
);
548+
}
549+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// ensure that existing crosvm::argument imports work without modification...
2+
pub mod argument;

0 commit comments

Comments
 (0)
Please sign in to comment.