@@ -5,8 +5,6 @@ Synopsis: Implements the --help flag and help subcommand
5
5
// TODO(cgay): Automatically display option default values. It's too easy to
6
6
// forget to add %default% to the help string.
7
7
8
- // TODO(cgay): Wrap the descriptions nicely
9
-
10
8
define function program-name () => (name :: <string> )
11
9
locator-base(as (<file-locator>, application-name()))
12
10
end function ;
@@ -157,27 +155,22 @@ define function print-help
157
155
print-options(cmd, stream);
158
156
if (cmd.has-subcommands?)
159
157
format(stream, "\n Subcommands:\n " );
160
- let (names, docs) = subcommand-columns(cmd);
161
- if (~empty? (names))
162
- let name-width = reduce1 (max , map (size , names));
163
- for (name in names, doc in docs)
164
- if (empty? (doc))
165
- format(stream, "%s\n " , name);
166
- else
167
- format(stream, "%s %s\n " , pad-right(name, name-width), doc);
168
- end ;
169
- end ;
158
+ let rows = subcommand-rows(cmd);
159
+ if (~empty? (rows))
160
+ columnize(stream, $subcommand-columns, rows);
161
+ new-line(stream);
170
162
end ;
171
- format(stream, "\n " );
172
163
let help-subcommand = find-subcommand(cmd, <help-subcommand>);
173
164
if (help-subcommand)
165
+ new-line(stream);
174
166
format(stream, "Use '%s %s <cmd> [<cmd> ...]' to see subcommand options.\n " ,
175
167
program-name(), subcommand-name(help-subcommand));
176
168
end ;
177
169
end ;
178
170
if (~instance? (cmd, <command-line-parser>))
179
171
let help-option = find-option(cmd, <help-option>);
180
172
if (help-option)
173
+ new-line(stream);
181
174
format(stream, "Use '%s %s' to see global options.\n " ,
182
175
program-name(), help-option.canonical-name.visible-option-name);
183
176
end ;
@@ -186,85 +179,90 @@ end function;
186
179
187
180
define function print-options
188
181
(cmd :: <command>, stream :: <stream>) => ()
189
- let (names, docs) = option-columns(cmd);
190
- if (~empty? (names))
182
+ // Column widths are chosen to have a max table width of 79 until columnist can
183
+ // determine the screen width.
184
+ let o-rows = option-rows(cmd);
185
+ if (~empty? (o-rows))
191
186
format(stream, "\n Options:\n " );
192
- let name-width = reduce1 (max , map (size , names));
193
- for (name in names, doc in docs)
194
- format(stream, " %s %s\n " , pad-right(name, name-width), doc);
195
- end ;
187
+ columnize(stream, $optional-options-columns, o-rows);
188
+ new-line(stream);
196
189
end ;
197
- let (names, docs) = positional-columns (cmd);
198
- if (~empty? (names ))
190
+ let p-rows = positional-option-rows (cmd);
191
+ if (~empty? (p-rows ))
199
192
format(stream, "\n Positional arguments:\n " );
200
- let name-width = reduce1 (max , map (size , names));
201
- for (name in names, doc in docs)
202
- format(stream, " %s %s\n " , pad-right(name, name-width), doc);
203
- end ;
193
+ columnize(stream, $positional-option-columns, p-rows);
194
+ new-line(stream);
204
195
end ;
205
196
end function ;
206
197
207
- define function positional-columns
208
- (cmd :: <command>) => (names :: <sequence> , docs :: <sequence> )
209
- let names = make (<stretchy-vector> );
210
- let docs = make (<stretchy-vector> );
198
+ define constant $positional-option-columns
199
+ = list (make (<column>),
200
+ make (<column>, maximum-width: 25 ),
201
+ make (<column>, maximum-width: 50 , pad?: #f ));
202
+
203
+ define function positional-option-rows
204
+ (cmd :: <command>) => (rows :: <sequence> )
205
+ let rows = make (<stretchy-vector> );
211
206
for (opt in cmd.positional-options)
212
207
let name = opt.option-variable;
213
208
if (opt.option-repeated?)
214
209
name := concatenate (name, "..." );
215
210
end ;
216
- add! (names, name);
217
- add! (docs, opt.option-help);
211
+ add! (rows, list ("" , name, opt.option-help));
218
212
end ;
219
- values (names, docs)
213
+ rows
220
214
end function ;
221
215
222
- define function option-columns
223
- (parser :: <command>)
224
- => (names :: <sequence> , docs :: <sequence> )
225
- let names = make (<stretchy-vector> );
226
- let docs = make (<stretchy-vector> );
227
- let any-shorts? = any? (method (opt) ~empty? (opt.short-names) end ,
228
- parser.command-options);
216
+ define constant $optional-options-columns
217
+ = list (make (<column>), // empty string, creates column border
218
+ make (<column>), // short option names
219
+ make (<column>, maximum-width: 25 ), // long option names
220
+ make (<column>, maximum-width: 50 , pad?: #f )); // docs
221
+
222
+ // Return rows of #[short-names, long-names, documentation]
223
+ define function option-rows
224
+ (parser :: <command>) => (rows :: <sequence> )
225
+ let rows = make (<stretchy-vector> );
229
226
for (option in parser.pass-by-name-options)
230
- let longs = map (visible-option-name, option.long-names);
231
- let shorts = map (visible-option-name, option.short-names);
232
- let name = concatenate (join(concatenate (shorts, longs), ", " ),
233
- " " ,
234
- if (instance? (option, <flag-option>))
235
- ""
236
- else
237
- option.option-variable | canonical-name(option);
238
- end );
239
- let indent = if (empty? (shorts) & any-shorts?)
240
- " " // Makes long options align (usually).
241
- else
242
- ""
243
- end ;
244
- add! (names, concatenate (indent, name));
245
- add! (docs, option.option-help);
227
+ let flag? = instance? (option, <flag-option>);
228
+ add! (rows,
229
+ vector ("" , // causes a two space indent
230
+ join(map (visible-option-name, option.short-names), ", " ),
231
+ join(map (method (name)
232
+ concatenate (visible-option-name(name),
233
+ flag? & "" | "=" ,
234
+ flag? & "" | (option.option-variable
235
+ | canonical-name(option)))
236
+ end ,
237
+ option.long-names),
238
+ " " ),
239
+ option.option-help));
246
240
end for ;
247
- values (names, docs)
241
+ rows
248
242
end function ;
249
243
250
- define function subcommand-columns
251
- (cmd :: <command>)
252
- => (names :: <sequence> , docs :: <sequence> )
253
- let names = make (<stretchy-vector> );
254
- let docs = make (<stretchy-vector> );
255
- iterate loop (subs = as (<list> , cmd.command-subcommands), indent = " " )
244
+ define constant $subcommand-columns
245
+ = list (make (<column>), // empty string, creates column border
246
+ make (<column>), // subcommand name
247
+ make (<column>, // subcommand doc
248
+ maximum-width: 50 , pad?: #f ));
249
+
250
+ define function subcommand-rows
251
+ (cmd :: <command>) => (rows :: <sequence> )
252
+ let rows = make (<stretchy-vector> );
253
+ iterate loop (subs = as (<list> , cmd.command-subcommands), indent = "" )
256
254
if (~empty? (subs))
257
255
let subcmd = subs[0 ];
258
- add! (names, concatenate (indent, subcmd.subcommand-name));
259
- // TODO(cgay): Wrap doc text.
260
- add! (docs, subcmd.command-help);
256
+ add! (rows, list ( "" ,
257
+ concatenate (indent, subcmd.subcommand-name),
258
+ subcmd.command-help) );
261
259
if (subcmd.has-subcommands?)
262
260
loop(subcmd.command-subcommands, concatenate (indent, " " ));
263
261
end ;
264
262
loop(tail (subs), indent)
265
263
end ;
266
264
end iterate;
267
- values (names, docs)
265
+ rows
268
266
end function ;
269
267
270
268
// Generate a one-line usage message showing the order of options and arguments.
0 commit comments