Skip to content

Commit b39bc3a

Browse files
committed
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.
1 parent 988d37f commit b39bc3a

3 files changed

Lines changed: 125 additions & 51 deletions

File tree

etc/api/type-cli.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ make:
2222
- source: main.rs
2323
output_dir: src
2424
cargo:
25-
build_version: "0.1.0"
25+
build_version: "0.2.0"
2626
keywords: [cli]
2727
is_executable: YES
2828
dependencies:

src/mako/cli/lib/argparse.mako

Lines changed: 123 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
<%namespace name="util" file="../../lib/util.mako"/>\
22
<%!
3-
from util import (put_and, supports_scopes, api_index, indent_by)
3+
from util import (put_and, supports_scopes, api_index, indent_by, enclose_in)
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,
66
CONFIG_DIR_FLAG, KEY_VALUE_ARG, to_docopt_arg, DEBUG_FLAG, DEBUG_AUTH_FLAG)
7+
8+
def rust_boolean(v):
9+
return v and 'true' or 'false'
710
%>\
811
<%def name="grammar(c)">\
9-
<%
10-
param_used = False
11-
struct_used = False
12-
upload_protocols_used = set()
13-
output_used = False
14-
%>\
1512
% for resource in sorted(c.rta_map.keys()):
1613
% for method in sorted(c.rta_map[resource]):
1714
<%
@@ -26,24 +23,20 @@
2623
2724
if mc.request_value:
2825
args.append('-%s %s...' % (STRUCT_FLAG, '<%s>' % KEY_VALUE_ARG))
29-
struct_used = True
3026
# end request_value
3127
3228
if mc.media_params:
3329
upload_protocols = [mp.protocol for mp in mc.media_params]
3430
mode = docopt_mode(upload_protocols)
3531
args.append('-%s %s %s %s' % (UPLOAD_FLAG, mode, FILE_ARG, MIME_ARG))
36-
upload_protocols_used = upload_protocols_used|set(upload_protocols)
3732
# end upload handling
3833
3934
if mc.optional_props or parameters is not UNDEFINED:
4035
args.append('[-%s %s...]' % (PARAM_FLAG, '<%s>' % VALUE_ARG))
41-
param_used = True
4236
# end paramters
4337
4438
if mc.response_schema or mc.m.get('supportsMediaDownload', False):
4539
args.append('[-%s %s]' % (OUTPUT_FLAG, OUT_ARG))
46-
output_used = True
4740
# handle output
4841
%>\
4942
${util.program_name()} [options] ${mangle_subcommand(resource)} ${mangle_subcommand(method)} ${' '.join(args)}
@@ -77,72 +70,153 @@ Configuration:
7770

7871
<%def name="new(c)" buffered="True">\
7972
<%
80-
param_used = False
81-
struct_used = False
82-
upload_protocols_used = set()
83-
output_used = False
73+
url_info = "All documentation details can be found at" + \
74+
cargo.doc_base_url + '/' + api_index(cargo.doc_base_url, name, version, make, check_exists=False)
75+
76+
# list of tuples
77+
# (0) = long name
78+
# (1) = description
79+
# (2) = argument name, no argument if no argument
80+
global_args = list()
81+
if supports_scopes(auth):
82+
global_args.append((
83+
SCOPE_FLAG,
84+
"Specify the authentication a method should be executed in. Each scope "
85+
"requires the user to grant this application permission to use it."
86+
"If unset, it defaults to the shortest scope url for a particular method.",
87+
'url'
88+
))
89+
# end add scope arg
90+
global_args.append((
91+
CONFIG_DIR_FLAG,
92+
"A directory into which we will store our persistent data. Defaults to "
93+
"a user-writable directory that we will create during the first invocation."
94+
"[default: ${CONFIG_DIR}]",
95+
'folder',
96+
))
97+
98+
global_args.append((
99+
DEBUG_FLAG,
100+
"Output all server communication to standard error. `tx` and `rx` are placed "
101+
"into the same stream.",
102+
None
103+
))
104+
105+
global_args.append((
106+
DEBUG_AUTH_FLAG,
107+
"Output all communication related to authentication to standard error. `tx` "
108+
"and `rx` are placed into the same stream.",
109+
None
110+
))
84111
%>\
85112
App::new("${util.program_name()}")
86-
<%block filter="indent_by(4)">
113+
<%block filter="indent_by(7)">\
114+
.author("${', '.join(cargo.authors)}")
115+
.version("${cargo.build_version}")
116+
% if description is not UNDEFINED:
117+
.about("${description}")
118+
% endif
119+
.after_help("${url_info}")
120+
% for flag, desc, arg_name in global_args:
121+
.arg(Arg::with_name("${arg_name or flag}")
122+
.long("${flag}")
123+
.help("${desc}")
124+
.takes_value(${rust_boolean(arg_name)}))
125+
% endfor
87126
% for resource in sorted(c.rta_map.keys()):
127+
.subcommand(
128+
SubCommand::new("${mangle_subcommand(resource)}")
88129
% for method in sorted(c.rta_map[resource]):
89130
<%
90131
mc = new_method_context(resource, method, c)
91132
133+
# A list of tuples
134+
# (0) = short flag, like -c
135+
# (1) = param description or None
136+
# (2) = argument name, or None if there is no argument
137+
# (3) = is required (bool)
138+
# (4) = allow multi-use
92139
args = list()
93140
for p in mc.required_props:
94141
if is_request_value_property(mc, p):
95142
continue
96-
args.append(to_docopt_arg(p))
143+
args.append((
144+
None,
145+
p.get('description'),
146+
mangle_subcommand(p.name),
147+
True,
148+
False,
149+
))
97150
# end for each required property
98151
99152
if mc.request_value:
100-
args.append('-%s %s...' % (STRUCT_FLAG, '<%s>' % KEY_VALUE_ARG))
101-
struct_used = True
153+
args.append((
154+
STRUCT_FLAG,
155+
"Set various fields of the request structure",
156+
KEY_VALUE_ARG,
157+
True,
158+
True,
159+
))
102160
# end request_value
103161
104162
if mc.media_params:
105163
upload_protocols = [mp.protocol for mp in mc.media_params]
106-
mode = docopt_mode(upload_protocols)
107-
args.append('-%s %s %s %s' % (UPLOAD_FLAG, mode, FILE_ARG, MIME_ARG))
108-
upload_protocols_used = upload_protocols_used|set(upload_protocols)
164+
# TODO: figure out how to have a group of arguments
165+
# NOTE: use possible_values() to specify 'mode'
166+
args.append((
167+
UPLOAD_FLAG,
168+
"Specify which file to upload",
169+
"mode",
170+
True,
171+
True,
172+
))
173+
## args.append('-%s %s %s %s' % (UPLOAD_FLAG, mode, FILE_ARG, MIME_ARG))
109174
# end upload handling
110175
111176
if mc.optional_props or parameters is not UNDEFINED:
112-
args.append('[-%s %s...]' % (PARAM_FLAG, '<%s>' % VALUE_ARG))
113-
param_used = True
177+
args.append((
178+
PARAM_FLAG,
179+
"Set various fields of the request structure",
180+
VALUE_ARG,
181+
False,
182+
True,
183+
))
114184
# end paramters
115185
116186
if mc.response_schema or mc.m.get('supportsMediaDownload', False):
117-
args.append('[-%s %s]' % (OUTPUT_FLAG, OUT_ARG))
118-
output_used = True
187+
args.append((
188+
OUTPUT_FLAG,
189+
"Specify the file into which to write the programs output",
190+
OUT_ARG,
191+
False,
192+
False,
193+
))
119194
# handle output
120195
%>\
121-
${util.program_name()} [options] ${mangle_subcommand(resource)} ${mangle_subcommand(method)} ${' '.join(args)}
196+
.subcommand(
197+
SubCommand::new("${mangle_subcommand(method)}")
198+
% if mc.m.get('description') is not None:
199+
.about("${mc.m.description}")
200+
% endif
201+
% for flag, desc, arg_name, required, multi in args:
202+
.arg(
203+
Arg::with_name("${arg_name or flag}")
204+
% if flag:
205+
.short("${flag}")
206+
% endif
207+
% if desc:
208+
.help("${desc}")
209+
% endif
210+
% if flag is not None:
211+
.takes_value(${rust_boolean(arg_name)})
212+
% endif
213+
.required(${rust_boolean(required)})
214+
.multiple(${rust_boolean(multi)}))
215+
% endfor
216+
)
122217
% endfor # each method
218+
)
123219
% endfor # end for each resource
124-
${util.program_name()} --help
125-
126-
All documentation details can be found at
127-
${cargo.doc_base_url + '/' + api_index(cargo.doc_base_url, name, version, make, check_exists=False)}
128-
129-
Configuration:
130-
% if supports_scopes(auth):
131-
--${SCOPE_FLAG} <url>
132-
Specify the authentication a method should be executed in. Each scope
133-
requires the user to grant this application permission to use it.
134-
If unset, it defaults to the shortest scope url for a particular method.
135-
% endif scopes
136-
--${CONFIG_DIR_FLAG} <folder>
137-
A directory into which we will store our persistent data. Defaults to
138-
a user-writable directory that we will create during the first invocation.
139-
[default: ${CONFIG_DIR}]
140-
--${DEBUG_FLAG}
141-
Output all server communication to standard error. `tx` and `rx` are placed
142-
into the same stream.
143-
--${DEBUG_AUTH_FLAG}
144-
Output all communication related to authentication to standard error. `tx`
145-
and `rx` are placed into the same stream.
146220
.get_matches();
147221
</%block>
148222
</%def>

src/mako/cli/main.rs.mako

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ extern crate ${to_extern_crate_name(library_to_crate_name(library_name(name, ver
2525

2626
use std::env;
2727
use std::io::{self, Write};
28-
use clap::{App, SubCommand};
28+
use clap::{App, SubCommand, Arg};
2929

3030
## ${engine.new(c)}\
3131

0 commit comments

Comments
 (0)