start command resolution

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2019-06-14 13:54:32 +02:00
parent 3182df96c0
commit dc3b88f50c
2 changed files with 135 additions and 2 deletions

View File

@ -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

View File

@ -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)
}
})
}