feat(CLI): struct value parsing

This works already for simple request values, but doens't generate
compiling code for structures with Parts in them.
Nonetheless, it's a big step towards finishing the overall issue.

Related to #64
This commit is contained in:
Sebastian Thiel
2015-04-16 17:44:30 +02:00
parent 1dd1fcf4b8
commit 15b78cd1ff
5 changed files with 111 additions and 34 deletions

View File

@@ -168,21 +168,24 @@ ${SPLIT_END}
for (fndfi, v) in enumerate(cursor):
if v != FIELD_SEP:
break
return '-%s %s ' % (STRUCT_FLAG, ''.join(cursor[:fndfi]) + FIELD_SEP.join(cursor[fndfi:]))
res = ''.join(cursor[:fndfi]) + FIELD_SEP.join(cursor[fndfi:])
if not res.endswith(FIELD_SEP):
res += FIELD_SEP
return res
def cursor_arg():
def cursor_arg(field):
prefix = ''
if cursor_tokens:
res = cursor_fmt(cursor_tokens)
prefix = cursor_fmt(cursor_tokens)
del cursor_tokens[:]
return res
return ''
return prefix + field
%>\
% for fn in sorted(schema.fields.keys()):
<%
f = schema.fields[fn]
%>\
% if isinstance(f, SchemaEntry):
* **${cursor_arg()}-${STRUCT_FLAG} ${mangle_subcommand(fn)}=${field_to_value(f)}**
* **-${STRUCT_FLAG} ${cursor_arg(mangle_subcommand(fn))}=${field_to_value(f)}**
- ${f.property.get('description', NO_DESC) | xml_escape, indent_all_but_first_by(2)}
% if f.container_type == CTYPE_ARRAY:
- Each invocation of this argument appends the given value to the array.

View File

@@ -14,6 +14,7 @@ STRUCT_FLAG = 'r'
UPLOAD_FLAG = 'u'
OUTPUT_FLAG = 'o'
VALUE_ARG = 'v'
KEY_VALUE_ARG = 'kv'
SCOPE_FLAG = 'scope'
CONFIG_DIR_FLAG = 'config-dir'

View File

@@ -3,9 +3,7 @@
from util import (put_and, supports_scopes)
from cli import (mangle_subcommand, new_method_context, PARAM_FLAG, STRUCT_FLAG, UPLOAD_FLAG, OUTPUT_FLAG, VALUE_ARG,
CONFIG_DIR, SCOPE_FLAG, is_request_value_property, FIELD_SEP, docopt_mode, FILE_ARG, MIME_ARG, OUT_ARG,
CONFIG_DIR_FLAG)
v_arg = '<%s>' % VALUE_ARG
CONFIG_DIR_FLAG, KEY_VALUE_ARG)
%>\
<%def name="new(c)">\
<%
@@ -29,7 +27,7 @@ Usage:
# end for each required property
if mc.request_value:
args.append('-%s %s...' % (STRUCT_FLAG, v_arg))
args.append('-%s %s...' % (STRUCT_FLAG, '<%s>' % KEY_VALUE_ARG))
struct_used = True
# end request_value
@@ -41,7 +39,7 @@ Usage:
# end upload handling
if mc.optional_props or parameters is not UNDEFINED:
args.append('[-%s %s]...' % (PARAM_FLAG, v_arg))
args.append('[-%s %s]...' % (PARAM_FLAG, '<%s>' % VALUE_ARG))
param_used = True
# end paramters

View File

@@ -4,7 +4,8 @@
upload_action_fn)
from cli import (mangle_subcommand, new_method_context, PARAM_FLAG, STRUCT_FLAG, UPLOAD_FLAG, OUTPUT_FLAG, VALUE_ARG,
CONFIG_DIR, SCOPE_FLAG, is_request_value_property, FIELD_SEP, docopt_mode, FILE_ARG, MIME_ARG, OUT_ARG,
cmd_ident, call_method_ident, arg_ident, POD_TYPES, flag_ident, ident, JSON_TYPE_VALUE_MAP)
cmd_ident, call_method_ident, arg_ident, POD_TYPES, flag_ident, ident, JSON_TYPE_VALUE_MAP,
KEY_VALUE_ARG, to_cli_schema, SchemaEntry, CTYPE_POD)
v_arg = '<%s>' % VALUE_ARG
SOPT = 'self.opt.'
@@ -26,7 +27,7 @@
%>\
mod cmn;
use cmn::{InvalidOptionsError, CLIError, JsonTokenStorage, arg_from_str, writer_from_opts, parse_kv_arg,
input_file_from_opts, input_mime_from_opts};
input_file_from_opts, input_mime_from_opts, FieldCursor, FieldError};
use std::default::Default;
use std::str::FromStr;
@@ -145,6 +146,8 @@ self.opt.${cmd_ident(method)} {
if parameters is not UNDEFINED:
global_parameter_names = list(pn for pn in sorted(parameters.keys()) if pn not in optional_prop_names)
handle_props = optional_props or parameters is not UNDEFINED
if mc.request_value:
request_cli_schema = to_cli_schema(c, mc.request_value)
%>\
## REQUIRED PARAMETERS
% for p in mc.required_props:
@@ -154,7 +157,7 @@ self.opt.${cmd_ident(method)} {
opt_ident = to_opt_arg_ident(p)
%>\
% if is_request_value_property(mc, p):
let ${prop_name}: api::${prop_type} = Default::default();
let mut ${prop_name}: api::${prop_type} = Default::default();
% elif p.type != 'string':
let ${prop_name}: ${prop_type} = arg_from_str(&${opt_ident}, err, "<${mangle_subcommand(p.name)}>", "${p.type}");
% endif # handle request value
@@ -194,7 +197,7 @@ for parg in ${SOPT + arg_ident(VALUE_ARG)}.iter() {
% endif
call = call.${mangle_ident(setter_fn_name(p))}(\
% if ptype != 'string':
arg_from_str(${value_unwrap}, err, "${mangle_subcommand(p.name)}", "${p.type}")\
arg_from_str(${value_unwrap}, err, "${mangle_subcommand(p.name)}", "${ptype}")\
% else:
${value_unwrap}\
% endif # handle conversion
@@ -233,6 +236,68 @@ ${value_unwrap}\
}
}
% endif # handle call parameters
% if mc.request_value:
<%
def flatten_schema_fields(schema, res, cur=list()):
if len(cur) == 0:
cur = list()
for fn, f in schema.fields.iteritems():
cur.append(fn)
if isinstance(f, SchemaEntry):
res.append((f, list(cur)))
else:
flatten_schema_fields(f, res, cur)
cur.pop()
# endfor
# end utility
schema_fields = list()
flatten_schema_fields(request_cli_schema, schema_fields)
%>\
let mut field_name: FieldCursor = Default::default();
for kvarg in ${SOPT + arg_ident(KEY_VALUE_ARG)}.iter() {
let (key, value) = parse_kv_arg(&*kvarg, err);
if let Err(field_err) = field_name.set(&*key) {
err.issues.push(field_err);
}
match &field_name.to_string()[..] {
% for fv, f in schema_fields:
<%
# TODO: Deduplicate !
ptype = fv.actual_property.type
if ptype == 'string' and 'Count' in f[-1]:
ptype = 'int64'
value_unwrap = 'value.unwrap_or("%s")' % JSON_TYPE_VALUE_MAP[ptype]
pname = FIELD_SEP.join(mangle_subcommand(ft) for ft in f)
struct_field = 'request.' + '.'.join('%s.as_mut().unwrap()' % mangle_ident(ft) for ft in f[:-1])
if len(f) > 1:
struct_field += '.'
struct_field += mangle_ident(f[-1])
%>\
"${pname}" => {
% if fv.container_type == CTYPE_POD:
${struct_field} = Some(\
% else:
if ${struct_field}.is_none() {
${struct_field} = Some(Default::default());
}
${struct_field}.as_mut().unwrap().push(\
% endif
% if ptype != 'string':
arg_from_str(${value_unwrap}, err, "${pname}", "${ptype}")\
% else:
${value_unwrap}.to_string()\
% endif
);
},
% endfor # each nested field
_ => {
err.issues.push(CLIError::Field(FieldError::Unknown(field_name.to_string())));
}
}
}
% endif # handle struct parsing
% if mc.media_params:
let protocol =
% for p in mc.media_params:

View File

@@ -13,7 +13,7 @@ use std::io::{Write, Read, stdout};
use std::default::Default;
const FIELD_SEP: char = 'c';
const FIELD_SEP: char = '.';
#[derive(Clone, Default)]
pub struct FieldCursor(Vec<String>);
@@ -26,6 +26,10 @@ impl ToString for FieldCursor {
impl FieldCursor {
pub fn set(&mut self, value: &str) -> Result<(), CLIError> {
if value.len() == 0 {
return Err(CLIError::Field(FieldError::Empty))
}
let mut first_is_field_sep = false;
let mut char_count: usize = 0;
let mut last_c = FIELD_SEP;
@@ -34,24 +38,24 @@ impl FieldCursor {
let mut field = String::new();
let mut fields = self.0.clone();
let push_field = |fields: &mut Vec<String>, field: &mut String| {
if field.len() > 0 {
fields.push(field.clone());
field.truncate(0);
let push_field = |fs: &mut Vec<String>, f: &mut String| {
if f.len() > 0 {
fs.push(f.clone());
f.truncate(0);
}
};
for (cid, c) in value.chars().enumerate() {
char_count = cid + 1;
char_count += 1;
if cid == 0 && c == FIELD_SEP {
first_is_field_sep = true;
}
if c == FIELD_SEP {
if cid == 0 {
first_is_field_sep = true;
}
num_conscutive_field_seps += 1;
if last_c == FIELD_SEP {
if cid > 0 && last_c == FIELD_SEP {
if fields.pop().is_none() {
return Err(CLIError::Field(FieldError::PopOnEmpty))
return Err(CLIError::Field(FieldError::PopOnEmpty(value.to_string())))
}
} else {
push_field(&mut fields, &mut field);
@@ -75,7 +79,7 @@ impl FieldCursor {
fields.truncate(0);
}
if char_count > 1 && num_conscutive_field_seps == 1 {
return Err(CLIError::Field(FieldError::TrailingFieldSep))
return Err(CLIError::Field(FieldError::TrailingFieldSep(value.to_string())))
}
self.0 = fields;
@@ -88,7 +92,7 @@ impl FieldCursor {
}
pub fn parse_kv_arg<'a>(kv: &'a str, err: &mut InvalidOptionsError)
-> (&'a str, Option<&'a str>) {
-> (&'a str, Option<&'a str>) {
let mut add_err = || err.issues.push(CLIError::InvalidKeyValueSyntax(kv.to_string()));
match kv.rfind('=') {
None => {
@@ -252,18 +256,24 @@ impl fmt::Display for InputError {
#[derive(Debug)]
pub enum FieldError {
PopOnEmpty,
TrailingFieldSep,
PopOnEmpty(String),
TrailingFieldSep(String),
Unknown(String),
Empty,
}
impl fmt::Display for FieldError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
FieldError::PopOnEmpty
=> writeln!(f, "Cannot move up on empty field cursor"),
FieldError::TrailingFieldSep
=> writeln!(f, "Single field separator may not be last character"),
FieldError::PopOnEmpty(ref field)
=> writeln!(f, "'{}': Cannot move up on empty field cursor", field),
FieldError::TrailingFieldSep(ref field)
=> writeln!(f, "'{}': Single field separator may not be last character", field),
FieldError::Unknown(ref field)
=> writeln!(f, "Field '{}' does not exist", field),
FieldError::Empty
=> writeln!(f, "Field names must not be empty"),
}
}
}