Skip to content

Commit

Permalink
feat(CLI): struct value parsing
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Byron committed Apr 16, 2015
1 parent 1dd1fcf commit 15b78cd
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 34 deletions.
15 changes: 9 additions & 6 deletions src/mako/cli/docs/commands.md.mako
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/mako/cli/lib/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
UPLOAD_FLAG = 'u'
OUTPUT_FLAG = 'o'
VALUE_ARG = 'v'
KEY_VALUE_ARG = 'kv'
SCOPE_FLAG = 'scope'
CONFIG_DIR_FLAG = 'config-dir'

Expand Down
8 changes: 3 additions & 5 deletions src/mako/cli/lib/docopt.mako
Original file line number Diff line number Diff line change
Expand Up @@ -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)">\
<%
Expand All @@ -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
Expand All @@ -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
Expand Down
73 changes: 69 additions & 4 deletions src/mako/cli/lib/engine.mako
Original file line number Diff line number Diff line change
Expand Up @@ -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.'
Expand All @@ -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;
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
48 changes: 29 additions & 19 deletions src/rust/cli/cmn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>);
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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 => {
Expand Down Expand Up @@ -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"),
}
}
}
Expand Down

0 comments on commit 15b78cd

Please sign in to comment.