From b39bc3a9cd165db8f9ea3fa536697ca80d36628e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 28 Apr 2015 19:34:24 +0200 Subject: [PATCH] feat(clap): initial version of command generation It compiles and works, even though there are many things we want to improve. One big question is how to define multi-arguments, like -u foo bar baz. --- etc/api/type-cli.yaml | 2 +- src/mako/cli/lib/argparse.mako | 172 +++++++++++++++++++++++---------- src/mako/cli/main.rs.mako | 2 +- 3 files changed, 125 insertions(+), 51 deletions(-) diff --git a/etc/api/type-cli.yaml b/etc/api/type-cli.yaml index 0d5b64edb1..06abe608b0 100644 --- a/etc/api/type-cli.yaml +++ b/etc/api/type-cli.yaml @@ -22,7 +22,7 @@ make: - source: main.rs output_dir: src cargo: - build_version: "0.1.0" + build_version: "0.2.0" keywords: [cli] is_executable: YES dependencies: diff --git a/src/mako/cli/lib/argparse.mako b/src/mako/cli/lib/argparse.mako index ac4a5f5556..483cb5abd6 100644 --- a/src/mako/cli/lib/argparse.mako +++ b/src/mako/cli/lib/argparse.mako @@ -1,17 +1,14 @@ <%namespace name="util" file="../../lib/util.mako"/>\ <%! - from util import (put_and, supports_scopes, api_index, indent_by) + from util import (put_and, supports_scopes, api_index, indent_by, enclose_in) 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, KEY_VALUE_ARG, to_docopt_arg, DEBUG_FLAG, DEBUG_AUTH_FLAG) + + def rust_boolean(v): + return v and 'true' or 'false' %>\ <%def name="grammar(c)">\ -<% - param_used = False - struct_used = False - upload_protocols_used = set() - output_used = False -%>\ % for resource in sorted(c.rta_map.keys()): % for method in sorted(c.rta_map[resource]): <% @@ -26,24 +23,20 @@ if mc.request_value: args.append('-%s %s...' % (STRUCT_FLAG, '<%s>' % KEY_VALUE_ARG)) - struct_used = True # end request_value if mc.media_params: upload_protocols = [mp.protocol for mp in mc.media_params] mode = docopt_mode(upload_protocols) args.append('-%s %s %s %s' % (UPLOAD_FLAG, mode, FILE_ARG, MIME_ARG)) - upload_protocols_used = upload_protocols_used|set(upload_protocols) # end upload handling if mc.optional_props or parameters is not UNDEFINED: args.append('[-%s %s...]' % (PARAM_FLAG, '<%s>' % VALUE_ARG)) - param_used = True # end paramters if mc.response_schema or mc.m.get('supportsMediaDownload', False): args.append('[-%s %s]' % (OUTPUT_FLAG, OUT_ARG)) - output_used = True # handle output %>\ ${util.program_name()} [options] ${mangle_subcommand(resource)} ${mangle_subcommand(method)} ${' '.join(args)} @@ -77,72 +70,153 @@ Configuration: <%def name="new(c)" buffered="True">\ <% - param_used = False - struct_used = False - upload_protocols_used = set() - output_used = False + url_info = "All documentation details can be found at" + \ + cargo.doc_base_url + '/' + api_index(cargo.doc_base_url, name, version, make, check_exists=False) + + # list of tuples + # (0) = long name + # (1) = description + # (2) = argument name, no argument if no argument + global_args = list() + if supports_scopes(auth): + global_args.append(( + SCOPE_FLAG, + "Specify the authentication a method should be executed in. Each scope " + "requires the user to grant this application permission to use it." + "If unset, it defaults to the shortest scope url for a particular method.", + 'url' + )) + # end add scope arg + global_args.append(( + CONFIG_DIR_FLAG, + "A directory into which we will store our persistent data. Defaults to " + "a user-writable directory that we will create during the first invocation." + "[default: ${CONFIG_DIR}]", + 'folder', + )) + + global_args.append(( + DEBUG_FLAG, + "Output all server communication to standard error. `tx` and `rx` are placed " + "into the same stream.", + None + )) + + global_args.append(( + DEBUG_AUTH_FLAG, + "Output all communication related to authentication to standard error. `tx` " + "and `rx` are placed into the same stream.", + None + )) %>\ App::new("${util.program_name()}") -<%block filter="indent_by(4)"> +<%block filter="indent_by(7)">\ +.author("${', '.join(cargo.authors)}") +.version("${cargo.build_version}") +% if description is not UNDEFINED: +.about("${description}") +% endif +.after_help("${url_info}") +% for flag, desc, arg_name in global_args: +.arg(Arg::with_name("${arg_name or flag}") + .long("${flag}") + .help("${desc}") + .takes_value(${rust_boolean(arg_name)})) +% endfor % for resource in sorted(c.rta_map.keys()): +.subcommand( + SubCommand::new("${mangle_subcommand(resource)}") % for method in sorted(c.rta_map[resource]): <% mc = new_method_context(resource, method, c) + # A list of tuples + # (0) = short flag, like -c + # (1) = param description or None + # (2) = argument name, or None if there is no argument + # (3) = is required (bool) + # (4) = allow multi-use args = list() for p in mc.required_props: if is_request_value_property(mc, p): continue - args.append(to_docopt_arg(p)) + args.append(( + None, + p.get('description'), + mangle_subcommand(p.name), + True, + False, + )) # end for each required property if mc.request_value: - args.append('-%s %s...' % (STRUCT_FLAG, '<%s>' % KEY_VALUE_ARG)) - struct_used = True + args.append(( + STRUCT_FLAG, + "Set various fields of the request structure", + KEY_VALUE_ARG, + True, + True, + )) # end request_value if mc.media_params: upload_protocols = [mp.protocol for mp in mc.media_params] - mode = docopt_mode(upload_protocols) - args.append('-%s %s %s %s' % (UPLOAD_FLAG, mode, FILE_ARG, MIME_ARG)) - upload_protocols_used = upload_protocols_used|set(upload_protocols) + # TODO: figure out how to have a group of arguments + # NOTE: use possible_values() to specify 'mode' + args.append(( + UPLOAD_FLAG, + "Specify which file to upload", + "mode", + True, + True, + )) + ## args.append('-%s %s %s %s' % (UPLOAD_FLAG, mode, FILE_ARG, MIME_ARG)) # end upload handling if mc.optional_props or parameters is not UNDEFINED: - args.append('[-%s %s...]' % (PARAM_FLAG, '<%s>' % VALUE_ARG)) - param_used = True + args.append(( + PARAM_FLAG, + "Set various fields of the request structure", + VALUE_ARG, + False, + True, + )) # end paramters if mc.response_schema or mc.m.get('supportsMediaDownload', False): - args.append('[-%s %s]' % (OUTPUT_FLAG, OUT_ARG)) - output_used = True + args.append(( + OUTPUT_FLAG, + "Specify the file into which to write the programs output", + OUT_ARG, + False, + False, + )) # handle output %>\ - ${util.program_name()} [options] ${mangle_subcommand(resource)} ${mangle_subcommand(method)} ${' '.join(args)} + .subcommand( + SubCommand::new("${mangle_subcommand(method)}") + % if mc.m.get('description') is not None: + .about("${mc.m.description}") + % endif + % for flag, desc, arg_name, required, multi in args: + .arg( + Arg::with_name("${arg_name or flag}") + % if flag: + .short("${flag}") + % endif + % if desc: + .help("${desc}") + % endif + % if flag is not None: + .takes_value(${rust_boolean(arg_name)}) + % endif + .required(${rust_boolean(required)}) + .multiple(${rust_boolean(multi)})) + % endfor + ) % endfor # each method + ) % endfor # end for each resource - ${util.program_name()} --help - -All documentation details can be found at -${cargo.doc_base_url + '/' + api_index(cargo.doc_base_url, name, version, make, check_exists=False)} - -Configuration: -% if supports_scopes(auth): - --${SCOPE_FLAG} - Specify the authentication a method should be executed in. Each scope - requires the user to grant this application permission to use it. - If unset, it defaults to the shortest scope url for a particular method. -% endif scopes - --${CONFIG_DIR_FLAG} - A directory into which we will store our persistent data. Defaults to - a user-writable directory that we will create during the first invocation. - [default: ${CONFIG_DIR}] - --${DEBUG_FLAG} - Output all server communication to standard error. `tx` and `rx` are placed - into the same stream. - --${DEBUG_AUTH_FLAG} - Output all communication related to authentication to standard error. `tx` - and `rx` are placed into the same stream. .get_matches(); \ No newline at end of file diff --git a/src/mako/cli/main.rs.mako b/src/mako/cli/main.rs.mako index 442c1ba941..0f234cc2a1 100644 --- a/src/mako/cli/main.rs.mako +++ b/src/mako/cli/main.rs.mako @@ -25,7 +25,7 @@ extern crate ${to_extern_crate_name(library_to_crate_name(library_name(name, ver use std::env; use std::io::{self, Write}; -use clap::{App, SubCommand}; +use clap::{App, SubCommand, Arg}; ## ${engine.new(c)}\