Skip to content

Commit 15b78cd

Browse files
committed
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
1 parent 1dd1fcf commit 15b78cd

5 files changed

Lines changed: 111 additions & 34 deletions

File tree

src/mako/cli/docs/commands.md.mako

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,21 +168,24 @@ ${SPLIT_END}
168168
for (fndfi, v) in enumerate(cursor):
169169
if v != FIELD_SEP:
170170
break
171-
return '-%s %s ' % (STRUCT_FLAG, ''.join(cursor[:fndfi]) + FIELD_SEP.join(cursor[fndfi:]))
171+
res = ''.join(cursor[:fndfi]) + FIELD_SEP.join(cursor[fndfi:])
172+
if not res.endswith(FIELD_SEP):
173+
res += FIELD_SEP
174+
return res
172175
173-
def cursor_arg():
176+
def cursor_arg(field):
177+
prefix = ''
174178
if cursor_tokens:
175-
res = cursor_fmt(cursor_tokens)
179+
prefix = cursor_fmt(cursor_tokens)
176180
del cursor_tokens[:]
177-
return res
178-
return ''
181+
return prefix + field
179182
%>\
180183
% for fn in sorted(schema.fields.keys()):
181184
<%
182185
f = schema.fields[fn]
183186
%>\
184187
% if isinstance(f, SchemaEntry):
185-
* **${cursor_arg()}-${STRUCT_FLAG} ${mangle_subcommand(fn)}=${field_to_value(f)}**
188+
* **-${STRUCT_FLAG} ${cursor_arg(mangle_subcommand(fn))}=${field_to_value(f)}**
186189
- ${f.property.get('description', NO_DESC) | xml_escape, indent_all_but_first_by(2)}
187190
% if f.container_type == CTYPE_ARRAY:
188191
- Each invocation of this argument appends the given value to the array.

src/mako/cli/lib/cli.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
UPLOAD_FLAG = 'u'
1515
OUTPUT_FLAG = 'o'
1616
VALUE_ARG = 'v'
17+
KEY_VALUE_ARG = 'kv'
1718
SCOPE_FLAG = 'scope'
1819
CONFIG_DIR_FLAG = 'config-dir'
1920

src/mako/cli/lib/docopt.mako

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
from util import (put_and, supports_scopes)
44
from cli import (mangle_subcommand, new_method_context, PARAM_FLAG, STRUCT_FLAG, UPLOAD_FLAG, OUTPUT_FLAG, VALUE_ARG,
55
CONFIG_DIR, SCOPE_FLAG, is_request_value_property, FIELD_SEP, docopt_mode, FILE_ARG, MIME_ARG, OUT_ARG,
6-
CONFIG_DIR_FLAG)
7-
8-
v_arg = '<%s>' % VALUE_ARG
6+
CONFIG_DIR_FLAG, KEY_VALUE_ARG)
97
%>\
108
<%def name="new(c)">\
119
<%
@@ -29,7 +27,7 @@ Usage:
2927
# end for each required property
3028
3129
if mc.request_value:
32-
args.append('-%s %s...' % (STRUCT_FLAG, v_arg))
30+
args.append('-%s %s...' % (STRUCT_FLAG, '<%s>' % KEY_VALUE_ARG))
3331
struct_used = True
3432
# end request_value
3533
@@ -41,7 +39,7 @@ Usage:
4139
# end upload handling
4240
4341
if mc.optional_props or parameters is not UNDEFINED:
44-
args.append('[-%s %s]...' % (PARAM_FLAG, v_arg))
42+
args.append('[-%s %s]...' % (PARAM_FLAG, '<%s>' % VALUE_ARG))
4543
param_used = True
4644
# end paramters
4745

src/mako/cli/lib/engine.mako

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
upload_action_fn)
55
from cli import (mangle_subcommand, new_method_context, PARAM_FLAG, STRUCT_FLAG, UPLOAD_FLAG, OUTPUT_FLAG, VALUE_ARG,
66
CONFIG_DIR, SCOPE_FLAG, is_request_value_property, FIELD_SEP, docopt_mode, FILE_ARG, MIME_ARG, OUT_ARG,
7-
cmd_ident, call_method_ident, arg_ident, POD_TYPES, flag_ident, ident, JSON_TYPE_VALUE_MAP)
7+
cmd_ident, call_method_ident, arg_ident, POD_TYPES, flag_ident, ident, JSON_TYPE_VALUE_MAP,
8+
KEY_VALUE_ARG, to_cli_schema, SchemaEntry, CTYPE_POD)
89
910
v_arg = '<%s>' % VALUE_ARG
1011
SOPT = 'self.opt.'
@@ -26,7 +27,7 @@
2627
%>\
2728
mod cmn;
2829
use cmn::{InvalidOptionsError, CLIError, JsonTokenStorage, arg_from_str, writer_from_opts, parse_kv_arg,
29-
input_file_from_opts, input_mime_from_opts};
30+
input_file_from_opts, input_mime_from_opts, FieldCursor, FieldError};
3031
3132
use std::default::Default;
3233
use std::str::FromStr;
@@ -145,6 +146,8 @@ self.opt.${cmd_ident(method)} {
145146
if parameters is not UNDEFINED:
146147
global_parameter_names = list(pn for pn in sorted(parameters.keys()) if pn not in optional_prop_names)
147148
handle_props = optional_props or parameters is not UNDEFINED
149+
if mc.request_value:
150+
request_cli_schema = to_cli_schema(c, mc.request_value)
148151
%>\
149152
## REQUIRED PARAMETERS
150153
% for p in mc.required_props:
@@ -154,7 +157,7 @@ self.opt.${cmd_ident(method)} {
154157
opt_ident = to_opt_arg_ident(p)
155158
%>\
156159
% if is_request_value_property(mc, p):
157-
let ${prop_name}: api::${prop_type} = Default::default();
160+
let mut ${prop_name}: api::${prop_type} = Default::default();
158161
% elif p.type != 'string':
159162
let ${prop_name}: ${prop_type} = arg_from_str(&${opt_ident}, err, "<${mangle_subcommand(p.name)}>", "${p.type}");
160163
% endif # handle request value
@@ -194,7 +197,7 @@ for parg in ${SOPT + arg_ident(VALUE_ARG)}.iter() {
194197
% endif
195198
call = call.${mangle_ident(setter_fn_name(p))}(\
196199
% if ptype != 'string':
197-
arg_from_str(${value_unwrap}, err, "${mangle_subcommand(p.name)}", "${p.type}")\
200+
arg_from_str(${value_unwrap}, err, "${mangle_subcommand(p.name)}", "${ptype}")\
198201
% else:
199202
${value_unwrap}\
200203
% endif # handle conversion
@@ -233,6 +236,68 @@ ${value_unwrap}\
233236
}
234237
}
235238
% endif # handle call parameters
239+
% if mc.request_value:
240+
<%
241+
def flatten_schema_fields(schema, res, cur=list()):
242+
if len(cur) == 0:
243+
cur = list()
244+
for fn, f in schema.fields.iteritems():
245+
cur.append(fn)
246+
if isinstance(f, SchemaEntry):
247+
res.append((f, list(cur)))
248+
else:
249+
flatten_schema_fields(f, res, cur)
250+
cur.pop()
251+
# endfor
252+
# end utility
253+
254+
schema_fields = list()
255+
flatten_schema_fields(request_cli_schema, schema_fields)
256+
%>\
257+
let mut field_name: FieldCursor = Default::default();
258+
for kvarg in ${SOPT + arg_ident(KEY_VALUE_ARG)}.iter() {
259+
let (key, value) = parse_kv_arg(&*kvarg, err);
260+
if let Err(field_err) = field_name.set(&*key) {
261+
err.issues.push(field_err);
262+
}
263+
match &field_name.to_string()[..] {
264+
% for fv, f in schema_fields:
265+
<%
266+
# TODO: Deduplicate !
267+
ptype = fv.actual_property.type
268+
if ptype == 'string' and 'Count' in f[-1]:
269+
ptype = 'int64'
270+
value_unwrap = 'value.unwrap_or("%s")' % JSON_TYPE_VALUE_MAP[ptype]
271+
pname = FIELD_SEP.join(mangle_subcommand(ft) for ft in f)
272+
273+
struct_field = 'request.' + '.'.join('%s.as_mut().unwrap()' % mangle_ident(ft) for ft in f[:-1])
274+
if len(f) > 1:
275+
struct_field += '.'
276+
struct_field += mangle_ident(f[-1])
277+
%>\
278+
"${pname}" => {
279+
% if fv.container_type == CTYPE_POD:
280+
${struct_field} = Some(\
281+
% else:
282+
if ${struct_field}.is_none() {
283+
${struct_field} = Some(Default::default());
284+
}
285+
${struct_field}.as_mut().unwrap().push(\
286+
% endif
287+
% if ptype != 'string':
288+
arg_from_str(${value_unwrap}, err, "${pname}", "${ptype}")\
289+
% else:
290+
${value_unwrap}.to_string()\
291+
% endif
292+
);
293+
},
294+
% endfor # each nested field
295+
_ => {
296+
err.issues.push(CLIError::Field(FieldError::Unknown(field_name.to_string())));
297+
}
298+
}
299+
}
300+
% endif # handle struct parsing
236301
% if mc.media_params:
237302
let protocol =
238303
% for p in mc.media_params:

src/rust/cli/cmn.rs

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use std::io::{Write, Read, stdout};
1313

1414
use std::default::Default;
1515

16-
const FIELD_SEP: char = 'c';
16+
const FIELD_SEP: char = '.';
1717

1818
#[derive(Clone, Default)]
1919
pub struct FieldCursor(Vec<String>);
@@ -26,6 +26,10 @@ impl ToString for FieldCursor {
2626

2727
impl FieldCursor {
2828
pub fn set(&mut self, value: &str) -> Result<(), CLIError> {
29+
if value.len() == 0 {
30+
return Err(CLIError::Field(FieldError::Empty))
31+
}
32+
2933
let mut first_is_field_sep = false;
3034
let mut char_count: usize = 0;
3135
let mut last_c = FIELD_SEP;
@@ -34,24 +38,24 @@ impl FieldCursor {
3438
let mut field = String::new();
3539
let mut fields = self.0.clone();
3640

37-
let push_field = |fields: &mut Vec<String>, field: &mut String| {
38-
if field.len() > 0 {
39-
fields.push(field.clone());
40-
field.truncate(0);
41+
let push_field = |fs: &mut Vec<String>, f: &mut String| {
42+
if f.len() > 0 {
43+
fs.push(f.clone());
44+
f.truncate(0);
4145
}
4246
};
4347

4448
for (cid, c) in value.chars().enumerate() {
45-
char_count = cid + 1;
49+
char_count += 1;
4650

47-
if cid == 0 && c == FIELD_SEP {
48-
first_is_field_sep = true;
49-
}
5051
if c == FIELD_SEP {
52+
if cid == 0 {
53+
first_is_field_sep = true;
54+
}
5155
num_conscutive_field_seps += 1;
52-
if last_c == FIELD_SEP {
56+
if cid > 0 && last_c == FIELD_SEP {
5357
if fields.pop().is_none() {
54-
return Err(CLIError::Field(FieldError::PopOnEmpty))
58+
return Err(CLIError::Field(FieldError::PopOnEmpty(value.to_string())))
5559
}
5660
} else {
5761
push_field(&mut fields, &mut field);
@@ -75,7 +79,7 @@ impl FieldCursor {
7579
fields.truncate(0);
7680
}
7781
if char_count > 1 && num_conscutive_field_seps == 1 {
78-
return Err(CLIError::Field(FieldError::TrailingFieldSep))
82+
return Err(CLIError::Field(FieldError::TrailingFieldSep(value.to_string())))
7983
}
8084

8185
self.0 = fields;
@@ -88,7 +92,7 @@ impl FieldCursor {
8892
}
8993

9094
pub fn parse_kv_arg<'a>(kv: &'a str, err: &mut InvalidOptionsError)
91-
-> (&'a str, Option<&'a str>) {
95+
-> (&'a str, Option<&'a str>) {
9296
let mut add_err = || err.issues.push(CLIError::InvalidKeyValueSyntax(kv.to_string()));
9397
match kv.rfind('=') {
9498
None => {
@@ -252,18 +256,24 @@ impl fmt::Display for InputError {
252256

253257
#[derive(Debug)]
254258
pub enum FieldError {
255-
PopOnEmpty,
256-
TrailingFieldSep,
259+
PopOnEmpty(String),
260+
TrailingFieldSep(String),
261+
Unknown(String),
262+
Empty,
257263
}
258264

259265

260266
impl fmt::Display for FieldError {
261267
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
262268
match *self {
263-
FieldError::PopOnEmpty
264-
=> writeln!(f, "Cannot move up on empty field cursor"),
265-
FieldError::TrailingFieldSep
266-
=> writeln!(f, "Single field separator may not be last character"),
269+
FieldError::PopOnEmpty(ref field)
270+
=> writeln!(f, "'{}': Cannot move up on empty field cursor", field),
271+
FieldError::TrailingFieldSep(ref field)
272+
=> writeln!(f, "'{}': Single field separator may not be last character", field),
273+
FieldError::Unknown(ref field)
274+
=> writeln!(f, "Field '{}' does not exist", field),
275+
FieldError::Empty
276+
=> writeln!(f, "Field names must not be empty"),
267277
}
268278
}
269279
}

0 commit comments

Comments
 (0)