feat(CLI): did you mean for struct values

* functionality is cursor-aware, and fixes the actual string the user
  passed in. That way, it is made very clear how the suggested value
  is to be used.
* it's a known weakness of the implementation that it operates on a
  flattened list of field names, and thus may make nonsensical
  suggestions.
* added punctuation to all errors

Fixes #67
[skip ci]
This commit is contained in:
Sebastian Thiel
2015-05-02 14:03:35 +02:00
parent d2a4e2ff8b
commit 96415d17ca
6 changed files with 117 additions and 30 deletions

View File

@@ -178,7 +178,7 @@ let arg_data = [
if mc.request_value:
args.append((
STRUCT_FLAG,
"Set various fields of the request structure",
"Set various fields of the request structure, matching the key=value form",
KEY_VALUE_ARG,
True,
True,
@@ -198,7 +198,7 @@ let arg_data = [
if mc.optional_props or parameters is not UNDEFINED:
args.append((
PARAM_FLAG,
"Set various fields of the request structure",
"Set various optional parameters, matching the key=value form",
VALUE_ARG,
False,
True,

View File

@@ -82,6 +82,10 @@ def new_method_context(resource, method, c):
return MethodContext(m, response_schema, params, request_value, media_params,
required_props, optional_props, part_prop)
# Returns a string representing a string-vector of mangled names
# fields is an iterator
def field_vec(fields):
return "vec![%s]" % ', '.join('"%s"' % mangle_subcommand(f) for f in fields)
def pretty(n):
return ' '.join(s.capitalize() for s in mangle_subcommand(n).split('-'))

View File

@@ -8,7 +8,7 @@
call_method_ident, POD_TYPES, opt_value, ident, JSON_TYPE_VALUE_MAP,
KEY_VALUE_ARG, to_cli_schema, SchemaEntry, CTYPE_POD, actual_json_type, CTYPE_MAP, CTYPE_ARRAY,
application_secret_path, DEBUG_FLAG, DEBUG_AUTH_FLAG, CONFIG_DIR_FLAG, req_value, MODE_ARG,
opt_values, SCOPE_ARG, CONFIG_DIR_ARG, DEFAULT_MIME)
opt_values, SCOPE_ARG, CONFIG_DIR_ARG, DEFAULT_MIME, field_vec)
v_arg = '<%s>' % VALUE_ARG
SOPT = 'self.opt'
@@ -259,7 +259,7 @@ ${value_unwrap}\
call = call.${ADD_PARAM_FN}(map.iter().find(|t| t.0 == key).unwrap_or(&("", key)).1, ${value_unwrap})
},
% endif # handle global parameters
_ => err.issues.push(CLIError::UnknownParameter(key.to_string())),
_ => err.issues.push(CLIError::UnknownParameter(key.to_string(), ${field_vec(global_parameter_names)})),
}
}
% endif # handle call parameters
@@ -333,7 +333,7 @@ if dry_run {
<%
allow_optionals_fn = lambda s: is_schema_with_optionals(schema_markers(s, c, transitive=False))
def flatten_schema_fields(schema, res, init_fn_map, cur=list(), init_call=None):
def flatten_schema_fields(schema, res, init_fn_map, fields, cur=list(), init_call=None):
if len(cur) == 0:
init_call = ''
cur = list()
@@ -345,6 +345,7 @@ if dry_run {
opt_access = ''
for fn, f in schema.fields.iteritems():
cur.append(['%s%s' % (mangle_ident(fn), opt_access), fn])
fields.add(fn)
if isinstance(f, SchemaEntry):
cur[-1][0] = mangle_ident(fn)
res.append((init_call, schema, f, list(cur)))
@@ -367,14 +368,15 @@ if dry_run {
init_fn_map[init_fn_name] = init_fn
# end handle init
flatten_schema_fields(f, res, init_fn_map, cur, init_call)
flatten_schema_fields(f, res, init_fn_map, fields, cur, init_call)
cur.pop()
# endfor
# end utility
schema_fields = list()
init_fn_map = dict()
flatten_schema_fields(request_cli_schema, schema_fields, init_fn_map)
fields = set()
flatten_schema_fields(request_cli_schema, schema_fields, init_fn_map, fields)
%>\
let mut ${request_prop_name} = api::${request_prop_type}::default();
let mut field_cursor = FieldCursor::default();
@@ -443,7 +445,8 @@ ${opt_suffix}\
},
% endfor # each nested field
_ => {
err.issues.push(CLIError::Field(FieldError::Unknown(temp_cursor.to_string())));
let suggestion = FieldCursor::did_you_mean(key, &${field_vec(sorted(fields))});
err.issues.push(CLIError::Field(FieldError::Unknown(temp_cursor.to_string(), suggestion, value.map(|v| v.to_string()))));
}
}
}

View File

@@ -21,6 +21,7 @@ extern crate yup_hyper_mock as mock;
extern crate serde;
extern crate hyper;
extern crate mime;
extern crate strsim;
extern crate ${to_extern_crate_name(library_to_crate_name(library_name(name, version), make.depends_on_suffix))} as api;
use std::env;