Skip to content

Commit 1126238

Browse files
Merge pull request #20 from promovicz/even-more-completion
Even more completion and help work
2 parents 8f11a11 + aad8737 commit 1126238

File tree

7 files changed

+271
-44
lines changed

7 files changed

+271
-44
lines changed

command-interface/building.dylan

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,9 @@ define method build-named-parameter (command :: <command-node>, names :: <sequen
111111
keys);
112112
let syms = #();
113113
for (name in names)
114-
let sym = make(<symbol-node>,
114+
let sym = make(<parameter-symbol-node>,
115115
symbol: as(<symbol>, name),
116+
parameter: param,
116117
repeatable?: node-repeatable?(param),
117118
repeat-marker: param,
118119
successors: list(param));

command-interface/completion.dylan

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ define class <command-completion> (<object>)
1111
/* node the completion was performed for */
1212
constant slot completion-node :: <parse-node>,
1313
required-init-keyword: node:;
14+
/* Value placeholder for help */
15+
constant slot completion-help-symbol :: false-or(<string>) = #f,
16+
init-keyword: help-symbol:;
17+
/* Main help text */
18+
constant slot completion-help-text :: false-or(<string>) = #f,
19+
init-keyword: help-text:;
1420
/* token used to hint the completion, if provided */
1521
constant slot completion-token :: false-or(<command-token>) = #f,
1622
init-keyword: token:;
@@ -30,6 +36,7 @@ define method initialize (completion :: <command-completion>,
3036
#rest args, #key, #all-keys)
3137
=> ();
3238
next-method();
39+
// initialize reverse links
3340
for (option in completion.completion-options)
3441
option-completion(option) := completion;
3542
end;
@@ -44,6 +51,7 @@ end method;
4451
* values.
4552
*/
4653
define class <command-completion-option> (<object>)
54+
/* normally initialized by make(<completion>) */
4755
slot option-completion :: false-or(<command-completion>) = #f;
4856
/* string for this option */
4957
constant slot option-string :: <string>,
@@ -61,7 +69,11 @@ define function make-completion (node :: <parse-node>,
6169
#key exhaustive? :: <boolean> = #f,
6270
complete-options :: <sequence> = #(),
6371
other-options :: <sequence> = #())
64-
=> (completion :: <command-completion>);
72+
=> (completion :: <command-completion>);
73+
// get node help strings
74+
let help-symbol = node-help-symbol(node);
75+
let help-text = node-help-text(node);
76+
// apply token restrictions
6577
if (token)
6678
let tokstr = token-string(token);
6779
// filter options using token
@@ -75,11 +87,14 @@ define function make-completion (node :: <parse-node>,
7587
end;
7688
end;
7789
end;
78-
// add longest common prefix as an incomplete option
90+
// add longest common prefix as an incomplete option,
91+
// but filter it against existing options and the token
7992
let all-options = concatenate(complete-options, other-options);
8093
let lcp = longest-common-prefix(all-options);
8194
unless (empty?(lcp) | member?(lcp, all-options, test: \=))
82-
other-options := add!(other-options, lcp);
95+
unless (token & lcp = token-string(token))
96+
other-options := add!(other-options, lcp);
97+
end;
8398
end;
8499
// construct the result
85100
local method as-complete-option(string :: <string>)
@@ -91,6 +106,8 @@ define function make-completion (node :: <parse-node>,
91106
make(<command-completion>,
92107
node: node, token: token,
93108
exhaustive?: exhaustive?,
109+
help-symbol: help-symbol,
110+
help-text: help-text,
94111
options: concatenate-as(<list>,
95112
map(as-complete-option, complete-options),
96113
map(as-other-option, other-options)));

command-interface/help.dylan

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -30,53 +30,58 @@ define method show-command-help (nodes :: <sequence>, tokens :: <sequence>)
3030
let cmd-title :: <list> = #();
3131
let cmd-help :: false-or(<string>) = #f;
3232

33+
// find the last command node
3334
for (token in tokens, node in nodes)
3435
if (instance?(node, <command-node>))
35-
if (~cmd)
36-
cmd := node;
37-
cmd-title := add(cmd-title, as(<string>, node-symbol(node)));
38-
if (command-help(node))
39-
cmd-help := command-help(node);
40-
end if;
36+
cmd := node;
37+
cmd-title := add(cmd-title, as(<string>, node-symbol(node)));
38+
if (command-help(node))
39+
cmd-help := command-help(node);
4140
end if;
4241
end if;
4342
end for;
4443

44+
// complain if no command found
4545
if (~cmd)
4646
error("Incomplete command.");
4747
end;
4848

49+
// fudge the title
4950
cmd-title := reverse(cmd-title);
5051
cmd-title := map(as-lowercase, cmd-title);
5152

53+
// default help
5254
if (~cmd-help)
5355
cmd-help := "No help.";
5456
end;
5557

56-
format-out("\n");
57-
format-out(" %s\n %s\n\n", join(cmd-title, " "), cmd-help);
58+
// determine possible successor nodes
59+
let successors = node-successors(cmd);
60+
let commands = choose(rcurry(instance?, <command-node>), successors);
61+
local method is-param?(node :: <parse-node>)
62+
=> (param? :: <boolean>);
63+
instance?(node, <parameter-node>) | instance?(node, <parameter-symbol-node>)
64+
end;
65+
let params = choose(is-param?, successors);
5866

59-
for (parameter in command-parameters(cmd))
60-
let param-help = parameter-help(parameter);
61-
if (~param-help)
62-
param-help := "No help.";
67+
// print stuff
68+
format-out("\n");
69+
format-out(" %s\n %s\n", join(cmd-title, " "), cmd-help);
70+
format-out("\n");
71+
unless (empty?(commands))
72+
format-out(" Subcommands:\n");
73+
for (command in commands)
74+
format-out(" %s\n", node-help-symbol(command));
75+
format-out(" %s\n", node-help-text(command));
6376
end;
64-
select (parameter-kind(parameter))
65-
#"flag" =>
66-
begin
67-
format-out(" %s\n", parameter-name(parameter));
68-
format-out(" %s\n", param-help);
69-
end;
70-
#"simple" =>
71-
begin
72-
format-out(" <%s>\n", parameter-name(parameter));
73-
format-out(" %s\n", param-help);
74-
end;
75-
#"named" =>
76-
begin
77-
format-out(" %s <value>\n", parameter-name(parameter));
78-
format-out(" %s\n", param-help);
79-
end;
77+
format-out("\n");
78+
end;
79+
unless (empty?(params))
80+
format-out(" Parameters:\n");
81+
for (param in params)
82+
format-out(" %s\n", node-help-symbol(param));
83+
format-out(" %s\n", node-help-text(param));
8084
end;
85+
format-out("\n");
8186
end;
8287
end method;

command-interface/macros.dylan

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ define macro command-aux-definer
3333
?bindings;
3434
?implementation;
3535
end method;
36-
let %command = build-command(%root, %symbols, handler: %handler, ?keywords);
36+
let %command = build-command(%root, %symbols, ?keywords);
3737
?parameters
3838
end }
3939

@@ -71,6 +71,8 @@ define macro command-aux-definer
7171
{ } => { }
7272
{ help ?text:expression; ... }
7373
=> { help: ?text, ... }
74+
{ implementation ?:expression; ... }
75+
=> { handler: %handler, ... }
7476
{ ?other:*; ... } => { ... }
7577

7678
// definitions that define parameters

command-interface/nodes.dylan

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ define abstract class <parse-node> (<object>)
4949
init-keyword: repeat-marker:;
5050
end class;
5151

52+
define open generic node-help-symbol (node :: <parse-node>)
53+
=> (help-symbol :: <string>);
54+
55+
define open generic node-help-text (node :: <parse-node>)
56+
=> (help-text :: <string>);
57+
5258
/* Generate completions for the given node
5359
*
5460
* May or may not be provided a partial token.
@@ -69,6 +75,16 @@ define open generic node-accept ( node :: <parse-node>, parser :: <command-parse
6975
=> ();
7076

7177

78+
define method node-help-symbol (node :: <parse-node>)
79+
=> (help-symbol :: <string>);
80+
"<...>";
81+
end method;
82+
83+
define method node-help-text (node :: <parse-node>)
84+
=> (help-symbol :: <string>);
85+
"No help.";
86+
end method;
87+
7288
/* Is the node acceptable as next node in given parser state?
7389
*
7490
* This prevents non-repeatable parameters from being added again.
@@ -148,6 +164,16 @@ define method print-object(object :: <symbol-node>, stream :: <stream>) => ();
148164
format(stream, "%s", node-symbol(object));
149165
end method;
150166

167+
define method node-help-symbol (node :: <symbol-node>)
168+
=> (help-symbol :: <string>);
169+
as(<string>, node-symbol(node));
170+
end method;
171+
172+
define method node-help-text (node :: <symbol-node>)
173+
=> (help-symbol :: <string>);
174+
"";
175+
end method;
176+
151177
define method node-match (node :: <symbol-node>, parser :: <command-parser>, token :: <command-token>)
152178
=> (matched? :: <boolean>);
153179
starts-with?(as(<string>, node-symbol(node)),
@@ -186,13 +212,25 @@ define method print-object(object :: <command-node>, stream :: <stream>) => ();
186212
format(stream, "%s - %s", node-symbol(object), command-help(object));
187213
end method;
188214

215+
define method node-help-text (node :: <command-node>)
216+
=> (help-text :: <string>);
217+
command-help(node) | "Command";
218+
end method;
219+
189220
define method node-accept (node :: <command-node>, parser :: <command-parser>, token :: <command-token>)
190221
=> ();
191222
if (command-handler(node))
192223
parser-push-command(parser, node);
193224
end
194225
end method;
195226

227+
define method node-complete (node :: <command-node>, parser :: <command-parser>, token :: false-or(<command-token>))
228+
=> (completion :: <command-completion>);
229+
make-completion(node, token,
230+
exhaustive?: #t,
231+
complete-options: list(as(<string>, node-symbol(node))));
232+
end method;
233+
196234
define method command-add-parameter (node :: <command-node>, parameter :: <parameter-node>)
197235
=> ();
198236
command-parameters(node) := add!(command-parameters(node), parameter);
@@ -224,6 +262,32 @@ define method node-successors (node :: <wrapper-node>)
224262
concatenate(node-successors(wrapper-root(node)), next-method());
225263
end method;
226264

265+
266+
/* A symbol that comes before a parameter
267+
*/
268+
define class <parameter-symbol-node> (<symbol-node>)
269+
constant slot symbol-parameter :: <parameter-node>,
270+
init-keyword: parameter:;
271+
end class;
272+
273+
define method node-help-symbol (node :: <parameter-symbol-node>)
274+
=> (help-symbol :: <string>);
275+
let parameter = symbol-parameter(node);
276+
concatenate(as(<string>, node-symbol(node)),
277+
" <", as(<string>, parameter-name(parameter)),
278+
if (node-repeatable?(parameter))
279+
">..."
280+
else
281+
">"
282+
end);
283+
end method;
284+
285+
define method node-help-text (node :: <parameter-symbol-node>)
286+
=> (help-text :: <string>);
287+
node-help-text(symbol-parameter(node));
288+
end method;
289+
290+
227291
/* Syntactical kinds of parameters
228292
*/
229293
define constant <parameter-kind> = one-of(#"simple", #"named", #"flag");
@@ -245,6 +309,21 @@ define open abstract class <parameter-node> (<parse-node>)
245309
init-keyword: value-type:;
246310
end class;
247311

312+
define method node-help-symbol (node :: <parameter-node>)
313+
=> (help-symbol :: <string>);
314+
concatenate("<", as(<string>, parameter-name(node)),
315+
if (node-repeatable?(node))
316+
">..."
317+
else
318+
">"
319+
end)
320+
end method;
321+
322+
define method node-help-text (node :: <parameter-node>)
323+
=> (help-symbol :: <string>);
324+
parameter-help(node) | "Parameter";
325+
end method;
326+
248327
/* Parameters can be converted to values
249328
*
250329
* By default they convert to simple strings.
@@ -299,9 +378,19 @@ end class;
299378

300379
/* Flag parameters
301380
*/
302-
define class <flag-node> (<parameter-node>, <symbol-node>)
381+
define class <flag-node> (<parameter-node>, <parameter-symbol-node>)
303382
end class;
304383

384+
define method node-help-symbol (node :: <flag-node>)
385+
=> (help-symbol :: <string>);
386+
as(<string>, parameter-name(node));
387+
end method;
388+
389+
define method node-help-text (node :: <flag-node>)
390+
=> (help-symbol :: <string>);
391+
parameter-help(node) | "Flag";
392+
end method;
393+
305394
define method parameter-convert (parser :: <command-parser>, node :: <flag-node>, token :: <command-token>)
306395
=> (value :: <boolean>);
307396
#t;
@@ -338,6 +427,15 @@ define class <oneof-node> (<parameter-node>)
338427
required-init-keyword: alternatives:;
339428
end class;
340429

430+
define method node-help-text (node :: <oneof-node>)
431+
=> (help-symbol :: <string>);
432+
let alternatives = oneof-alternatives(node);
433+
unless (oneof-case-sensitive?(node))
434+
alternatives := map(as-lowercase, alternatives);
435+
end;
436+
parameter-help(node) | concatenate("One of: ", join(alternatives, ", "));
437+
end method;
438+
341439
define method node-match (node :: <oneof-node>, parser :: <command-parser>, token :: <command-token>)
342440
=> (matched? :: <boolean>);
343441
let string = token-string(token);

0 commit comments

Comments
 (0)