Skip to content

Commit e60a2b7

Browse files
committed
Use columnist library for help output
The main purpose is to enable wrapping of the documentation text. For now there is no guarantee of a maximum output width because columnist doesn't support it yet. We aim for around 79 characters.
1 parent 2a69ce5 commit e60a2b7

File tree

4 files changed

+75
-74
lines changed

4 files changed

+75
-74
lines changed

dylan-package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"contact": "dylan-lang@googlegroups.com",
88
"keywords": ["cli", "flags"],
99
"dependencies": [
10-
"strings@1.0"
10+
"columnist",
11+
"strings@2.0"
1112
],
1213
"dev-dependencies": [
1314
"sphinx-extensions",

help.dylan

Lines changed: 64 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ Synopsis: Implements the --help flag and help subcommand
55
// TODO(cgay): Automatically display option default values. It's too easy to
66
// forget to add %default% to the help string.
77

8-
// TODO(cgay): Wrap the descriptions nicely
9-
108
define function program-name () => (name :: <string>)
119
locator-base(as(<file-locator>, application-name()))
1210
end function;
@@ -157,27 +155,22 @@ define function print-help
157155
print-options(cmd, stream);
158156
if (cmd.has-subcommands?)
159157
format(stream, "\nSubcommands:\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);
170162
end;
171-
format(stream, "\n");
172163
let help-subcommand = find-subcommand(cmd, <help-subcommand>);
173164
if (help-subcommand)
165+
new-line(stream);
174166
format(stream, "Use '%s %s <cmd> [<cmd> ...]' to see subcommand options.\n",
175167
program-name(), subcommand-name(help-subcommand));
176168
end;
177169
end;
178170
if (~instance?(cmd, <command-line-parser>))
179171
let help-option = find-option(cmd, <help-option>);
180172
if (help-option)
173+
new-line(stream);
181174
format(stream, "Use '%s %s' to see global options.\n",
182175
program-name(), help-option.canonical-name.visible-option-name);
183176
end;
@@ -186,85 +179,90 @@ end function;
186179

187180
define function print-options
188181
(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))
191186
format(stream, "\nOptions:\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);
196189
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))
199192
format(stream, "\nPositional 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);
204195
end;
205196
end function;
206197

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>);
211206
for (opt in cmd.positional-options)
212207
let name = opt.option-variable;
213208
if (opt.option-repeated?)
214209
name := concatenate(name, "...");
215210
end;
216-
add!(names, name);
217-
add!(docs, opt.option-help);
211+
add!(rows, list("", name, opt.option-help));
218212
end;
219-
values(names, docs)
213+
rows
220214
end function;
221215

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>);
229226
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));
246240
end for;
247-
values(names, docs)
241+
rows
248242
end function;
249243

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 = "")
256254
if (~empty?(subs))
257255
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));
261259
if (subcmd.has-subcommands?)
262260
loop(subcmd.command-subcommands, concatenate(indent, " "));
263261
end;
264262
loop(tail(subs), indent)
265263
end;
266264
end iterate;
267-
values(names, docs)
265+
rows
268266
end function;
269267

270268
// Generate a one-line usage message showing the order of options and arguments.

library.dylan

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ author: Eric Kidd
33
copyright: See LICENSE file in this distribution.
44
55
define library command-line-parser
6+
use columnist;
67
use common-dylan;
78
use dylan,
89
import: { dylan-extensions };
@@ -52,6 +53,7 @@ end module option-parser-protocol;
5253

5354
// Used by most programs.
5455
define module command-line-parser
56+
use columnist;
5557
use common-dylan,
5658
exclude: { format-to-string };
5759
use dylan-extensions,

tests/command-line-parser-test-suite.dylan

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,13 @@ define test test-synopsis-format ()
9696
Usage: %s [options]
9797
9898
Options:
99-
-h, --help Display this message.
100-
-v, -q, --verbose, --quiet Be more or less verbose.
101-
-f, -B, --foo, --no-foo Be more or less foonly.
102-
-Q, --quux QUUX Quuxly quacksly
103-
-O, --optimize LEVEL x
104-
-W, --warning WARNING x
105-
-D, --define DEFINE x
99+
-h --help Display this message.
100+
-v, -q --verbose --quiet Be more or less verbose.
101+
-f, -B --foo --no-foo Be more or less foonly.
102+
-Q --quux=QUUX Quuxly quacksly
103+
-O --optimize=LEVEL x
104+
-W --warning=WARNING x
105+
-D --define=DEFINE x
106106
";
107107
assert-equal(format-to-string(expected, program-name()), synopsis);
108108
end;

0 commit comments

Comments
 (0)