1 use std
::collections
::HashMap
;
3 use anyhow
::format_err
;
11 Argument { value: String }
,
12 Option { name: String, value: Option<String> }
,
15 fn parse_argument(arg
: &str) -> RawArgument
{
16 let bytes
= arg
.as_bytes();
18 let length
= bytes
.len();
20 if length
< 2 || bytes
[0] != b'
-'
{
21 return RawArgument
::Argument
{
22 value
: arg
.to_string(),
26 let first
= if bytes
[1] == b'
-'
{
28 return RawArgument
::Separator
;
35 if let Some(i
) = bytes
[first
..length
].iter().position(|b
| *b
== b'
='
) {
36 let start
= i
+ first
;
37 // Since we take a &str, we know the contents of it are valid utf8.
38 // Since bytes[start] == b'=', we know the byte beginning at start is a single-byte
39 // code pointer. We also know that 'first' points exactly after a single-byte code
40 // point as it points to the first byte after a hyphen.
41 // Therefore we know arg[first..start] is valid utf-8, therefore it is safe to use
42 // get_unchecked() to speed things up.
43 return RawArgument
::Option
{
44 name
: unsafe { arg.get_unchecked(first..start).to_string() }
,
45 value
: Some(unsafe { arg.get_unchecked((start + 1)..).to_string() }
),
50 name
: unsafe { arg.get_unchecked(first..).to_string() }
,
55 /// parse as many arguments as possible into a Vec<String, String>. This does not
56 /// verify the schema.
57 /// Returns parsed data and the remaining arguments as two separate array
58 pub(crate) fn parse_argument_list
<T
: AsRef
<str>>(
60 schema
: ParameterSchema
,
61 errors
: &mut ParameterError
,
62 ) -> (Vec
<(String
, String
)>, Vec
<String
>) {
63 let mut data
: Vec
<(String
, String
)> = vec
![];
64 let mut remaining
: Vec
<String
> = vec
![];
68 while pos
< args
.len() {
69 match parse_argument(args
[pos
].as_ref()) {
70 RawArgument
::Separator
=> {
73 RawArgument
::Option { name, value }
=> match value
{
75 let mut want_bool
= false;
76 let mut can_default
= false;
77 if let Some((_opt
, Schema
::Boolean(boolean_schema
))) = schema
.lookup(&name
) {
79 match boolean_schema
.default {
80 Some(false) | None
=> can_default
= true,
85 let mut next_is_argument
= false;
86 let mut next_is_bool
= false;
88 if (pos
+ 1) < args
.len() {
89 let next
= args
[pos
+ 1].as_ref();
90 if let RawArgument
::Argument { .. }
= parse_argument(next
) {
91 next_is_argument
= true;
92 if parse_boolean(next
).is_ok() {
101 data
.push((name
, args
[pos
].as_ref().to_string()));
102 } else if can_default
{
103 data
.push((name
, "true".to_string()));
105 errors
.push(name
.to_string(), format_err
!("missing boolean value."));
107 } else if next_is_argument
{
109 data
.push((name
, args
[pos
].as_ref().to_string()));
111 errors
.push(name
.to_string(), format_err
!("missing parameter value."));
115 data
.push((name
, v
));
118 RawArgument
::Argument { value }
=> {
119 remaining
.push(value
);
126 remaining
.reserve(args
.len() - pos
);
127 for i
in &args
[pos
..] {
128 remaining
.push(i
.as_ref().to_string());
134 /// Parses command line arguments using a `Schema`
136 /// Returns parsed options as json object, together with the
137 /// list of additional command line arguments.
138 pub fn parse_arguments
<T
: AsRef
<str>>(
141 fixed_param
: &HashMap
<&'
static str, String
>,
142 schema
: ParameterSchema
,
143 ) -> Result
<(Value
, Vec
<String
>), ParameterError
> {
144 let mut errors
= ParameterError
::new();
146 // first check if all arg_param exists in schema
148 let mut last_arg_param_is_optional
= false;
149 let mut last_arg_param_is_array
= false;
151 for i
in 0..arg_param
.len() {
152 let name
= arg_param
[i
];
153 if let Some((optional
, param_schema
)) = schema
.lookup(name
) {
154 if i
== arg_param
.len() - 1 {
155 last_arg_param_is_optional
= optional
;
156 if let Schema
::Array(_
) = param_schema
{
157 last_arg_param_is_array
= true;
160 panic
!("positional argument '{}' may not be optional", name
);
163 panic
!("no such property '{}' in schema", name
);
167 let (mut data
, mut remaining
) = parse_argument_list(args
, schema
, &mut errors
);
169 for i
in 0..arg_param
.len() {
170 let name
= arg_param
[i
];
171 let is_last_arg_param
= i
== (arg_param
.len() - 1);
173 if remaining
.is_empty() {
174 if !(is_last_arg_param
&& last_arg_param_is_optional
) {
175 errors
.push(name
.to_string(), format_err
!("missing argument"));
177 } else if is_last_arg_param
&& last_arg_param_is_array
{
178 for value
in remaining
{
179 data
.push((name
.to_string(), value
));
183 data
.push((name
.to_string(), remaining
.remove(0)));
187 if !errors
.is_empty() {
191 for (name
, value
) in fixed_param
.iter() {
192 data
.push((name
.to_string(), value
.to_string()));
195 let options
= schema
.parse_parameter_strings(&data
, true)?
;
197 Ok((options
, remaining
))
201 fn test_boolean_arg() {
202 const PARAMETERS
: ObjectSchema
= ObjectSchema
::new(
204 &[("enable", false, &BooleanSchema
::new("Enable").schema())],
207 let mut variants
: Vec
<(Vec
<&str>, bool
)> = vec
![];
208 variants
.push((vec
!["-enable"], true));
209 variants
.push((vec
!["-enable=1"], true));
210 variants
.push((vec
!["-enable", "yes"], true));
211 variants
.push((vec
!["-enable", "Yes"], true));
212 variants
.push((vec
!["--enable", "1"], true));
213 variants
.push((vec
!["--enable", "ON"], true));
214 variants
.push((vec
!["--enable", "true"], true));
216 variants
.push((vec
!["--enable", "0"], false));
217 variants
.push((vec
!["--enable", "no"], false));
218 variants
.push((vec
!["--enable", "off"], false));
219 variants
.push((vec
!["--enable", "false"], false));
221 for (args
, expect
) in variants
{
222 let res
= parse_arguments(
226 ParameterSchema
::from(&PARAMETERS
),
228 assert
!(res
.is_ok());
229 if let Ok((options
, remaining
)) = res
{
230 assert
!(options
["enable"] == expect
);
231 assert
!(remaining
.is_empty());
237 fn test_argument_paramenter() {
238 use proxmox_schema
::*;
240 const PARAMETERS
: ObjectSchema
= ObjectSchema
::new(
243 ("enable", false, &BooleanSchema
::new("Enable.").schema()),
244 ("storage", false, &StringSchema
::new("Storage.").schema()),
248 let args
= vec
!["-enable", "local"];
249 let res
= parse_arguments(
253 ParameterSchema
::from(&PARAMETERS
),
255 assert
!(res
.is_ok());
256 if let Ok((options
, remaining
)) = res
{
257 assert
!(options
["enable"] == true);
258 assert
!(options
["storage"] == "local");
259 assert
!(remaining
.is_empty());