start command resolution
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
3182df96c0
commit
dc3b88f50c
@ -3,7 +3,7 @@
|
||||
use std::cell::Cell;
|
||||
use std::sync::Once;
|
||||
|
||||
use failure::Error;
|
||||
use failure::{bail, Error};
|
||||
use http::Response;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
@ -41,6 +41,13 @@ impl Parameter {
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/// Parse a commnd line option: if it is None, we only saw an `--option` without value, this is
|
||||
/// fine for booleans. If we saw a value, we should try to parse it out into a json value. For
|
||||
/// string parameters this means passing them as is, for others it means using FromStr...
|
||||
pub fn parse_cli(&self, _value: Option<&str>) -> Result<Value, Error> {
|
||||
bail!("TODO");
|
||||
}
|
||||
}
|
||||
|
||||
/// Bare type info. Types themselves should also have a description, even if a method's parameter
|
||||
|
@ -2,7 +2,10 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::ApiMethodInfo;
|
||||
use failure::{bail, format_err, Error};
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{ApiMethodInfo, ApiOutput, Parameter};
|
||||
|
||||
/// A CLI root node.
|
||||
pub struct App<Body: 'static> {
|
||||
@ -49,6 +52,13 @@ impl<Body: 'static> App<Body> {
|
||||
_ => panic!("app {} cannot have subcommands!", self.name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve(&self, args: &[&str]) -> Result<ApiOutput<Body>, Error> {
|
||||
self.command
|
||||
.as_ref()
|
||||
.ok_or_else(|| format_err!("no commands available"))?
|
||||
.resolve(args.iter())
|
||||
}
|
||||
}
|
||||
|
||||
/// A node in the CLI command router. This is either
|
||||
@ -70,6 +80,13 @@ impl<Body: 'static> Command<Body> {
|
||||
pub fn new() -> Self {
|
||||
Command::SubCommands(SubCommands::new())
|
||||
}
|
||||
|
||||
fn resolve(&self, args: std::slice::Iter<&str>) -> Result<ApiOutput<Body>, Error> {
|
||||
match self {
|
||||
Command::Method(method) => method.resolve(args),
|
||||
Command::SubCommands(subcmd) => subcmd.resolve(args),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SubCommands<Body: 'static> {
|
||||
@ -100,6 +117,16 @@ impl<Body: 'static> SubCommands<Body> {
|
||||
self.add_subcommand(name, command);
|
||||
self
|
||||
}
|
||||
|
||||
fn resolve(&self, mut args: std::slice::Iter<&str>) -> Result<ApiOutput<Body>, Error> {
|
||||
match args.next() {
|
||||
None => bail!("missing subcommand"),
|
||||
Some(arg) => match self.commands.get(arg) {
|
||||
None => bail!("no such subcommand: {}", arg),
|
||||
Some(cmd) => cmd.resolve(args),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A reference to an API method. Note that when coming from the command line, it is possible to
|
||||
@ -112,6 +139,7 @@ impl<Body: 'static> SubCommands<Body> {
|
||||
pub struct Method<Body: 'static> {
|
||||
pub method: &'static (dyn ApiMethodInfo<Body> + Send + Sync),
|
||||
pub positional_args: &'static [&'static str],
|
||||
//pub formatter: Option<()>, // TODO: output formatter
|
||||
}
|
||||
|
||||
impl<Body: 'static> Method<Body> {
|
||||
@ -125,4 +153,102 @@ impl<Body: 'static> Method<Body> {
|
||||
positional_args,
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve(&self, mut args: std::slice::Iter<&str>) -> Result<ApiOutput<Body>, Error> {
|
||||
let mut params = serde_json::Map::new();
|
||||
let mut positionals = self.positional_args.iter();
|
||||
|
||||
let mut current_option = None;
|
||||
loop {
|
||||
match next_arg(&mut args) {
|
||||
Some(Arg::Opt(arg)) => {
|
||||
if let Some(arg) = current_option {
|
||||
self.add_parameter(&mut params, arg, None)?;
|
||||
}
|
||||
|
||||
current_option = Some(arg);
|
||||
}
|
||||
Some(Arg::OptArg(arg, value)) => {
|
||||
if let Some(arg) = current_option {
|
||||
self.add_parameter(&mut params, arg, None)?;
|
||||
}
|
||||
|
||||
self.add_parameter(&mut params, arg, Some(value))?;
|
||||
}
|
||||
Some(Arg::Positional(value)) => match current_option {
|
||||
Some(arg) => self.add_parameter(&mut params, arg, Some(value))?,
|
||||
None => match positionals.next() {
|
||||
Some(arg) => self.add_parameter(&mut params, arg, Some(value))?,
|
||||
None => bail!("unexpected positional parameter: '{}'", value),
|
||||
},
|
||||
},
|
||||
None => {
|
||||
if let Some(arg) = current_option {
|
||||
self.add_parameter(&mut params, arg, None)?;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
current_option.is_none(),
|
||||
"current_option must have been dealt with"
|
||||
);
|
||||
|
||||
let missing = positionals.fold(String::new(), |mut acc, more| {
|
||||
if acc.is_empty() {
|
||||
more.to_string()
|
||||
} else {
|
||||
acc.push_str(", ");
|
||||
acc.push_str(more);
|
||||
acc
|
||||
}
|
||||
});
|
||||
if !missing.is_empty() {
|
||||
bail!("missing positional parameters: {}", missing);
|
||||
}
|
||||
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
/// This should insert the parameter 'arg' with value 'value' into 'params'.
|
||||
/// This means we need to verify `arg` exists in self.method, `value` deserializes to its type,
|
||||
/// and then serialize it into the Value.
|
||||
fn add_parameter(
|
||||
&self,
|
||||
params: &mut serde_json::Map<String, Value>,
|
||||
arg: &str,
|
||||
value: Option<&str>,
|
||||
) -> Result<(), Error> {
|
||||
let param_def = self
|
||||
.find_parameter(arg)
|
||||
.ok_or_else(|| format_err!("no such parameter: '{}'", arg))?;
|
||||
params.insert(arg.to_string(), param_def.parse_cli(value)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_parameter(&self, name: &str) -> Option<&Parameter> {
|
||||
self.method.parameters().iter().find(|p| p.name == name)
|
||||
}
|
||||
}
|
||||
|
||||
enum Arg<'a> {
|
||||
Positional(&'a str),
|
||||
Opt(&'a str),
|
||||
OptArg(&'a str, &'a str),
|
||||
}
|
||||
|
||||
fn next_arg<'a>(args: &mut std::slice::Iter<&'a str>) -> Option<Arg<'a>> {
|
||||
args.next().map(|arg| {
|
||||
if arg.starts_with("--") {
|
||||
let arg = &arg[2..];
|
||||
|
||||
match arg.find('=') {
|
||||
Some(idx) => Arg::OptArg(&arg[0..idx], &arg[idx + 1..]),
|
||||
None => Arg::Opt(arg),
|
||||
}
|
||||
} else {
|
||||
Arg::Positional(arg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user