diff --git a/src/mako/cli/docs/commands.md.mako b/src/mako/cli/docs/commands.md.mako index 28aa596ae8..c2a493473f 100644 --- a/src/mako/cli/docs/commands.md.mako +++ b/src/mako/cli/docs/commands.md.mako @@ -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. diff --git a/src/mako/cli/lib/cli.py b/src/mako/cli/lib/cli.py index c354381f68..143b67e3f3 100644 --- a/src/mako/cli/lib/cli.py +++ b/src/mako/cli/lib/cli.py @@ -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' diff --git a/src/mako/cli/lib/docopt.mako b/src/mako/cli/lib/docopt.mako index 7d0c77f376..e74aece73a 100644 --- a/src/mako/cli/lib/docopt.mako +++ b/src/mako/cli/lib/docopt.mako @@ -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 diff --git a/src/mako/cli/lib/engine.mako b/src/mako/cli/lib/engine.mako index eabe170eb0..556410b691 100644 --- a/src/mako/cli/lib/engine.mako +++ b/src/mako/cli/lib/engine.mako @@ -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: diff --git a/src/rust/cli/cmn.rs b/src/rust/cli/cmn.rs index 999d501696..9ee53dc2d2 100644 --- a/src/rust/cli/cmn.rs +++ b/src/rust/cli/cmn.rs @@ -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); @@ -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, field: &mut String| { - if field.len() > 0 { - fields.push(field.clone()); - field.truncate(0); + let push_field = |fs: &mut Vec, 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"), } } }