Skip to content

Commit 7f8f0e0

Browse files
authored
Merge pull request #53 from cgay/columnist
Use columnist library to wrap help output
2 parents 2a69ce5 + 4cdf03b commit 7f8f0e0

File tree

4 files changed

+77
-76
lines changed

4 files changed

+77
-76
lines changed

dylan-package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
{
22
"name": "command-line-parser",
33
"description": "Parse command line flags and subcommands",
4-
"version": "3.2.2",
4+
"version": "3.3.0",
55
"url": "https://github.com/dylan-lang/command-line-parser",
66
"category": "utilities",
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",
14-
"testworks@2.0"
15+
"testworks"
1516
]
1617
}

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)