From bac4e1a82fa331370c20a7c4843989f11974600c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 29 Apr 2015 21:04:50 +0200 Subject: [PATCH] fix(clap): adjust option usage to changed API Discovery API now builds and seems to work even ! More testing will have to be done though to be sure. Related #81 --- src/mako/cli/lib/cli.py | 10 ++--- src/mako/cli/lib/engine.mako | 83 +++++++++++++++++------------------- src/mako/cli/main.rs.mako | 38 ++++++++--------- src/rust/cli/cmn.rs | 14 +++--- 4 files changed, 69 insertions(+), 76 deletions(-) diff --git a/src/mako/cli/lib/cli.py b/src/mako/cli/lib/cli.py index 43c53ad43d..bb84854942 100644 --- a/src/mako/cli/lib/cli.py +++ b/src/mako/cli/lib/cli.py @@ -91,16 +91,12 @@ def mangle_subcommand(name): def ident(name): return mangle_subcommand(name).replace('-', '_') -# return the identifier of a command for the given name, suitable to address the command field in the docopt structure -def cmd_ident(name): - return 'cmd_' + ident(name) - # Similar to cmd_ident, but for arguments def arg_ident(name): - return 'arg_' + ident(name) + return opt_value(name) -def flag_ident(name): - return 'flag_' + ident(name) +def opt_value(name, opt='opt'): + return opt + '.value_of("' + mangle_subcommand(name) + '").unwrap_or("")' def application_secret_path(program_name): return program_name + '-secret.json' diff --git a/src/mako/cli/lib/engine.mako b/src/mako/cli/lib/engine.mako index c62c4d3058..c4fe2f9131 100644 --- a/src/mako/cli/lib/engine.mako +++ b/src/mako/cli/lib/engine.mako @@ -5,19 +5,19 @@ ADD_SCOPE_FN, TREF) 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, + call_method_ident, arg_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) + application_secret_path, DEBUG_FLAG, DEBUG_AUTH_FLAG, CONFIG_DIR_FLAG) v_arg = '<%s>' % VALUE_ARG - SOPT = 'self.opt.' + SOPT = 'self.opt' def to_opt_arg_ident(p): - return SOPT + arg_ident(p.name) + return opt_value(p.name) def borrow_prefix(p): ptype = p.get('type', None) borrow = '' - if (ptype not in POD_TYPES or ptype in ('string', None) or p.get('repeated', False)) and ptype is not None: + if (ptype not in POD_TYPES or ptype is None or p.get('repeated', False)) and ptype is not None: borrow = '&' return borrow @@ -35,17 +35,18 @@ use std::str::FromStr; use oauth2::{Authenticator, DefaultAuthenticatorDelegate}; use rustc_serialize::json; +use clap::ArgMatches; -struct Engine { - opt: Options, +struct Engine<'n, 'a> { + opt: ArgMatches<'n, 'a>, hub: ${hub_type_name}>, } -impl Engine { +impl<'n, 'a> Engine<'n, 'a> { % for resource in sorted(c.rta_map.keys()): % for method in sorted(c.rta_map[resource]): - fn ${call_method_ident(resource, method)}(&self, dry_run: bool, err: &mut InvalidOptionsError) + fn ${call_method_ident(resource, method)}(&self, opt: &ArgMatches<'n, 'a>, dry_run: bool, err: &mut InvalidOptionsError) -> Option { ${self._method_call_impl(c, resource, method) | indent_all_but_first_by(2)} } @@ -54,35 +55,27 @@ impl Engine { % endfor fn _doit(&self, dry_run: bool) -> (Option, Option) { let mut err = InvalidOptionsError::new(); - let mut call_result: Option; + let mut call_result: Option = None; let mut err_opt: Option = None; ## RESOURCE LOOP: check for set primary subcommand + match ${SOPT + '.subcommand()'} { % for resource in sorted(c.rta_map.keys()): - - % if loop.first: - if \ - % else: - else if \ - % endif -self.opt.${cmd_ident(resource)} { - ## METHOD LOOP: Check for method subcommand - % for method in sorted(c.rta_map[resource]): - % if loop.first: - if \ - % else: - else if \ - % endif -self.opt.${cmd_ident(method)} { - call_result = self.${call_method_ident(resource, method)}(dry_run, &mut err); - }\ - % endfor # each method - else { - unreachable!(); - } - }\ + ("${mangle_subcommand(resource)}", Some(opt)) => { + match opt.subcommand() { + % for method in sorted(c.rta_map[resource]): + ("${mangle_subcommand(method)}", Some(opt)) => { + call_result = self.${call_method_ident(resource, method)}(opt, dry_run, &mut err); + }, + % endfor # each method + _ => { + err.issues.push(CLIError::MissingMethodError("${mangle_subcommand(resource)}".to_string())); + } + } + }, % endfor # each resource - else { - unreachable!(); + _ => { + err.issues.push(CLIError::MissingCommandError); + } } if dry_run { @@ -94,9 +87,9 @@ self.opt.${cmd_ident(method)} { } // Please note that this call will fail if any part of the opt can't be handled - fn new(opt: Options) -> Result { + fn new(opt: ArgMatches<'a, 'n>) -> Result, InvalidOptionsError> { let (config_dir, secret) = { - let config_dir = match cmn::assure_config_dir_exists(&opt.flag_config_dir) { + let config_dir = match cmn::assure_config_dir_exists(opt.value_of("${CONFIG_DIR_FLAG}").unwrap_or("${CONFIG_DIR}")) { Err(e) => return Err(InvalidOptionsError::single(e, 3)), Ok(p) => p, }; @@ -137,7 +130,7 @@ self.opt.${cmd_ident(method)} { <%def name="_debug_client(flag_name)" buffered="True">\ -if opt.flag_${mangle_ident(flag_name)} { +if opt.is_present("${flag_name}") { hyper::Client::with_connector(mock::TeeConnector { connector: hyper::net::HttpConnector(None) }) @@ -179,7 +172,7 @@ ${self._request_value_impl(c, request_cli_schema, prop_name, request_prop_type)} % elif p.type != 'string': % if p.get('repeated', False): let ${prop_name}: Vec<${prop_type} = Vec::new(); -for (arg_id, arg) in ${opt_ident}.iter().enumerate() { +for (arg_id, arg) in opt.values_of().unwrap_or(Vec::new()).iter().enumerate() { ${prop_name}.push(arg_from_str(&arg, err, "<${mangle_subcommand(p.name)}>", arg_id), "${p.type}")); } % else: @@ -204,7 +197,7 @@ let mut download_mode = false; % endif let mut call = self.hub.${mangle_ident(resource)}().${mangle_ident(method)}(${', '.join(call_args)}); % if handle_props: -for parg in ${SOPT + arg_ident(VALUE_ARG)}.iter() { +for parg in opt.values_of("${ident(VALUE_ARG)}").unwrap_or(Vec::new()).iter() { let (key, value) = parse_kv_arg(&*parg, err, false); match key { % for p in optional_props: @@ -267,14 +260,14 @@ let protocol = % else: } else if \ % endif -${SOPT + cmd_ident(p.protocol)} { +${opt_value(p.protocol)} { "${p.protocol}" % endfor # each media param } else { unreachable!() }; -let mut input_file = input_file_from_opts(&${SOPT + arg_ident(FILE_ARG[1:-1])}, err); -let mime_type = input_mime_from_opts(&${SOPT + arg_ident(MIME_ARG[1:-1])}, err); +let mut input_file = input_file_from_opts(&${opt_value(FILE_ARG)}, err); +let mime_type = input_mime_from_opts(&${opt_value(MIME_ARG)}, err); % else: let protocol = "${STANDARD}"; % endif # support upload @@ -283,7 +276,7 @@ if dry_run { } else { assert!(err.issues.len() == 0); % if method_default_scope(mc.m): -<% scope_opt = SOPT + flag_ident('scope') %>\ +<% scope_opt = opt_value('scope', SOPT) %>\ if ${scope_opt}.len() > 0 { call = call.${ADD_SCOPE_FN}(&${scope_opt}); } @@ -291,7 +284,7 @@ if dry_run { ## Make the call, handle uploads, handle downloads (also media downloads|json decoding) ## TODO: unify error handling % if handle_output: - let mut ostream = writer_from_opts(${SOPT + flag_ident(OUTPUT_FLAG)}, &${SOPT + arg_ident(OUT_ARG[1:-1])}); + let mut ostream = writer_from_opts(opt.value_of("${(OUT_ARG)}")); % endif # handle output match match protocol { % if mc.media_params: @@ -383,7 +376,7 @@ if dry_run { %>\ let mut ${request_prop_name} = api::${request_prop_type}::default(); let mut field_cursor = FieldCursor::default(); -for kvarg in ${SOPT + arg_ident(KEY_VALUE_ARG)}.iter() { +for kvarg in ${opt_value(KEY_VALUE_ARG)}.iter() { let last_errc = err.issues.len(); let (key, value) = parse_kv_arg(&*kvarg, err, false); let mut temp_cursor = field_cursor.clone(); diff --git a/src/mako/cli/main.rs.mako b/src/mako/cli/main.rs.mako index 06ced6f932..406c7a0c0c 100644 --- a/src/mako/cli/main.rs.mako +++ b/src/mako/cli/main.rs.mako @@ -4,6 +4,7 @@ <% from util import (new_context, rust_comment, to_extern_crate_name, library_to_crate_name, library_name, indent_all_but_first_by) + from cli import DEBUG_FLAG c = new_context(schemas, resources, context.get('methods')) default_user_agent = "google-cli-rust-client/" + cargo.build_version @@ -29,27 +30,26 @@ use clap::{App, SubCommand, Arg}; mod cmn; -## ${engine.new(c)}\ +${engine.new(c)}\ fn main() { ${argparse.new(c) | indent_all_but_first_by(1)}\ - ## let opts: Options = Options::docopt().decode().unwrap_or_else(|e| e.exit()); - ## let debug = opts.flag_debug; - ## match Engine::new(opts) { - ## Err(err) => { - ## writeln!(io::stderr(), "{}", err).ok(); - ## env::set_exit_status(err.exit_code); - ## }, - ## Ok(engine) => { - ## if let Some(err) = engine.doit() { - ## if debug { - ## writeln!(io::stderr(), "{:?}", err).ok(); - ## } else { - ## writeln!(io::stderr(), "{}", err).ok(); - ## } - ## env::set_exit_status(1); - ## } - ## } - ## } + let debug = matches.is_present("${DEBUG_FLAG}"); + match Engine::new(matches) { + Err(err) => { + writeln!(io::stderr(), "{}", err).ok(); + env::set_exit_status(err.exit_code); + }, + Ok(engine) => { + if let Some(err) = engine.doit() { + if debug { + writeln!(io::stderr(), "{:?}", err).ok(); + } else { + writeln!(io::stderr(), "{}", err).ok(); + } + env::set_exit_status(1); + } + } + } } \ No newline at end of file diff --git a/src/rust/cli/cmn.rs b/src/rust/cli/cmn.rs index f5edc98c15..1855e5289f 100644 --- a/src/rust/cli/cmn.rs +++ b/src/rust/cli/cmn.rs @@ -149,11 +149,11 @@ pub fn input_mime_from_opts(mime: &str, err: &mut InvalidOptionsError) -> Option // May panic if we can't open the file - this is anticipated, we can't currently communicate this // kind of error: TODO: fix this architecture :) -pub fn writer_from_opts(flag: bool, arg: &str) -> Box { - if !flag || arg == "-" { - Box::new(stdout()) - } else { - Box::new(fs::OpenOptions::new().create(true).write(true).open(arg).unwrap()) +pub fn writer_from_opts(arg: Option<&str>) -> Box { + let f = arg.unwrap_or("-"); + match f { + "-" => Box::new(stdout()), + _ => Box::new(fs::OpenOptions::new().create(true).write(true).open(f).unwrap()) } } @@ -331,6 +331,8 @@ pub enum CLIError { InvalidKeyValueSyntax(String, bool), Input(InputError), Field(FieldError), + MissingCommandError, + MissingMethodError(String), } impl fmt::Display for CLIError { @@ -348,6 +350,8 @@ impl fmt::Display for CLIError { let hashmap_info = if is_hashmap { "hashmap " } else { "" }; writeln!(f, "'{}' does not match {}pattern =", kv, hashmap_info) }, + CLIError::MissingCommandError => writeln!(f, "Please specify the main sub-command"), + CLIError::MissingMethodError(ref cmd) => writeln!(f, "Please specify the method to call on the {} command", cmd), } } }