File Coverage

lib/Decl/Semantics/Commandline.pm
Criterion Covered Total %
statement 22 122 18.0
branch 0 60 0.0
condition 0 12 0.0
subroutine 8 21 38.1
pod 14 14 100.0
total 44 229 19.2


line stmt bran cond sub pod time code
1             package Decl::Semantics::Commandline;
2            
3 12     12   78 use warnings;
  12         26  
  12         462  
4 12     12   70 use strict;
  12         25  
  12         612  
5            
6 12     12   73 use base qw(Decl::Node);
  12         23  
  12         1292  
7 12     12   12806 use Getopt::Lucid qw(:all);
  12         336012  
  12         2729  
8 12     12   166 use Scalar::Util qw(refaddr);
  12         29  
  12         622  
9 12     12   81 use Decl::Semantics::Code;
  12         31  
  12         338  
10 12     12   74 use Data::Dumper;
  12         29  
  12         20630  
11            
12             =head1 NAME
13            
14             Decl::Semantics::Commandline - implements a command line parser in an event context, using Getopt::Lucid
15            
16             =head1 VERSION
17            
18             Version 0.01
19            
20             =cut
21            
22             our $VERSION = '0.01';
23            
24            
25             =head1 SYNOPSIS
26            
27             When running any script, the command line provides a first-line configuration and specification mechanism for the action to be taken.
28             Perl simply provides the usual @ARGV list that C also does (granted, Perl does some of the irritating tasks for you that C doesn't), but
29             it's up to you to do something sensible with that list.
30            
31             This module allows you to define a C tag that extracts various sorts of parameters from the command line, then takes the
32             remaining parts of the list and treats it as a command to be executed.
33            
34             It also provides a command loop (an internal command line) that can be embedded into any program to provide interactive services. A
35             default set of commands could be provided as a debugger, for instance. (TODO: write a default set of commands as a debugger.)
36            
37             Here's an example:
38            
39             command-line argv (loop)
40             switch version "version|-V"
41             counter verbose "verbose|-v"
42             param config (anycase) "config|c"
43             list libraries "lib"
44             keypair def "define"
45            
46             command start "start something" {
47             # Start something
48             }
49             command help "get help" {
50             # Print a help text
51             }
52            
53             do {
54             # Handle unknown commands
55             }
56            
57             The top five lines in our specification represent the five types of named command line argument supported. Anything left on the command line
58             after all the named parameters have been consumed is treated as a command and passed to the appropriate "on" event. The search order is the
59             usual one: if a like-named event isn't found in the command line, the parent node will be searched, and so on up to the root. If no matching
60             "on" is found, the arglist is passed to the "do" handler, if there is one.
61            
62             If a command is on the command line, it executes only after all parsing is finished; that is, the command is executed as the "start" action
63             of the program.
64            
65             If there is no command and "loop" is specified, the start action passes control to a L REPL loop using the same event logic.
66             If "loop=always" is specified, the loop will run even if an initial command is given. The shell will use the quoted labels of the events as
67             summary text for the help system; for longer texts, a "help" tag must be given, like this:
68            
69             command-line (loop)
70             command start "start something"
71             help
72             The "start" command is used to start something
73             and its help text may be multi-line. It is not
74             parsed.
75             do { # start something }
76            
77             If no command was found on the command line, and no loop is specified, then the command line will not be marked as callable, so the program
78             won't call it as its start action and will have to look elsewhere. If you never want the command line to be the start action, you can
79             flag it:
80            
81             command-line (nocall)
82            
83             Normally, if not used as a command loop, the command line is just treated as a data source by name to permit its values to be queried from
84             code elsewhere in the program. You can also specify a target data source like this:
85            
86             command-line (store=system.argv)
87             switch verbose (anycase) "verbose|V"
88            
89             There are four modifiers that can be added to named arguments:
90            
91             command-line
92             switch parm1 (anycase) "parm1"
93             keypair parm2 (required) "parm2"
94             switch parm3 (needs=parm4) "parm3"
95             param parm4 (valid="\d+") "parm4"
96            
97             The C modifier makes the parameter case-insensitive. The C modifier means that this value must be specified or an error
98             is raised. The C modifier means that if parm3 is specified, then parm4 must also be specified or an error is raised. Finally, the
99             C modifier is a regexp that must be matched or (wait for it ...) an error is raised.
100            
101             One or more named error handlers may be provided:
102            
103             command-line
104             keypair parm2 (required, needs=parm3) "parm2"
105             on error required {
106             print STDERR "You need to specify parm2.\n"
107             }
108             on error needs {
109             print STDERR "You can't specify parm2 without defining parm3.\n";
110             }
111             param parm3 (valid="\d+") "parm3"
112             on error valid {
113             print STDERR "parm3 must be numeric\n";
114             }
115            
116             You can also provide a more heavy-duty validator:
117            
118             command-line
119             param configfile "config"
120             valid { -r }
121             on error valid {
122             print STDERR "The configuration file you specified does not exist.\n";
123             }
124            
125             Almost all of that is just the semantics of L, which really is the end-all and be-all of command line parsing.
126            
127             =head2 defines(), tags_defined()
128            
129             Called by Decl::Semantics during import, to find out what xmlapi tags this plugin claims to implement.
130            
131             =cut
132 0     0 1 0 sub defines { ('command-line', 'shell'); }
133 12     12 1 125 sub tags_defined { Decl->new_data(<
134             command-line (body=vanilla)
135             shell (body=vanilla)
136             EOF
137            
138             =head2 post_build
139            
140             All the work is done in the post_build stage.
141            
142             =cut
143            
144             sub post_build {
145 0     0 1   my ($self) = @_;
146 0           $self->{callable} = 0; # Not callable by default.
147 0 0         $self->{callable} = 1 if $self->parameter('loop');
148 0 0         $self->{callable} = 1 if $self->is('shell');
149            
150 0 0         if ($self->is('command-line')) {
151 0           my @specs = ();
152 0           foreach my $child ($self->nodes()) {
153 0           my $spec = undef;
154 0 0         if ($child->is ('switch')) {
    0          
    0          
    0          
    0          
155 0           $spec = Switch ($child->label);
156             } elsif ($child->is ('counter')) {
157 0           $spec = Counter ($child->label);
158             } elsif ($child->is ('list')) {
159 0           $spec = List ($child->label);
160             } elsif ($child->is ('param')) {
161 0           $spec = Param ($child->label);
162             } elsif ($child->is ('keypair')) {
163 0           $spec = Keypair ($child->label);
164             }
165 0 0         if (defined $spec) {
166 0 0         $spec->required if $child->parameter('required');
167 0 0         $spec->anycase if $child->parameter('anycase');
168 0 0         $spec->valid($child->parameter('valid')) if $child->parameter('valid');
169 0 0         $spec->needs($child->parameter('needs')) if $child->parameter('needs');
170 0           my $validator = $child->first('valid');
171 0 0         if ($validator) {
172 0           Decl::Semantics::Code->build_payload($validator, 0);
173 0           $spec->valid($validator->sub);
174             }
175 0           push @specs, $spec;
176             }
177             }
178            
179 0           $self->{payload} = Getopt::Lucid->getopt(\@specs); # Magic!
180            
181 0 0         if (@ARGV) { # TODO: point it to something other than ARGV at some point.
182             # We have a command. Make ourselves callable.
183 0           $self->{first_command} = join ' ', @ARGV;
184 0           $self->{callable} = 1;
185             }
186             }
187            
188 0           my $shell_pname = "Shell_" . refaddr($self);
189 0           my $shell_package = <<"EOF";
190            
191             package $shell_pname;
192             use warnings;
193             use strict;
194             use base qw(Term::Shell);
195             sub prompt_str { \$_[0]->{parent}->prompt_str(); }
196            
197             EOF
198 0 0         if ($self->parameter('debug')) {
199 0           foreach my $c ('show', 'list', 'goto') {
200 0           foreach my $h ('run', 'smry', 'help') {
201 0           $shell_package .= "sub ${h}_$c { my (\$self, \@args) = \@_; \$self->{parent}->${h}_$c(\@args); }\n";
202             }
203             }
204             }
205            
206 0           foreach my $command ($self->nodes('command')) {
207             # Add custom commands here.
208             }
209            
210 0           $shell_package .= "1;\n";
211            
212 0           eval $shell_package;
213 0 0         print STDERR $@ if $@;
214            
215 0           $self->{payload} = $shell_pname->new();
216 0           $self->{payload}->{parent} = $self;
217 0           $self->{current_node} = $self->root;
218             }
219            
220             =head2 go
221            
222             Called when the element is run (that is, when the shell is invoked, if any).
223            
224             =cut
225            
226             sub go {
227 0     0 1   my $self = shift;
228 0 0         if ($self->{first_command}) {
229 0           $self->{payload}->cmd($self->{first_command});
230 0           $self->{first_command} = '';
231 0 0         return if $self->parameter('loop') ne 'always';
232             }
233 0           $self->{payload}->cmdloop();
234             }
235            
236             =head1 THE TERMINAL
237            
238             The standard shell
239            
240             =head1 STANDARD COMMANDS
241            
242             The default command line provides debugging and introspection tools - a REPL - along with whatever commands you define.
243             Those commands are defined in this section. They can be disabled for a given command line with (debug=no).
244            
245             =head2 run_show, smry_show, help_show
246            
247             The C command shows the text of the current node (pages if necessary). If you give it one argument (e.g. "show code") it will use the argument to
248             access the node's hashref and display the results. If the results are undefined, it will say so. If they're a scalar, it will print the scalar (through
249             paging). If they're a ref, it will print the output of Data::Dumper (again through paging).
250            
251             =cut
252            
253             sub run_show {
254 0     0 1   my ($self, @args) = @_;
255 0 0         if (not @args) {
256 0           $self->{payload}->page($self->{current_node}->describe(1));
257 0           return;
258             }
259 0 0         if (@args == 1) {
260 0 0         if ($args[0] eq '-') {
261 0           $self->{payload}->page(join ("\n", grep {defined $self->{current_node}->{$_}} keys(%{$self->{current_node}})) . "\n");
  0            
  0            
262 0           return;
263             }
264 0           my $display = $self->{current_node}->{$args[0]};
265 0 0         if (not defined $display) {
    0          
266 0           $self->{payload}->page("node->{" . $args[0] . "} is not defined\n");
267             } elsif (not ref $display) {
268 0           $self->{payload}->page($display . "\n");
269             } else {
270 0           $self->{payload}->page(Dumper($display));
271             }
272 0           return;
273             }
274 0           print "Don't know how to show " . join (' ', @args) . "\n";
275             }
276 0     0 1   sub smry_show { "Show the current node" }
277 0     0 1   sub help_show { <
278             The 'show' command with no argument shows the macro-expanded structure of the current node. It uses paged output, in case the node is large.
279             Use 'goto' to select a subnode of the current node.
280            
281             The 'show -' command shows a list of the hash keys of the node; 'show ' shows the contents of a hash key - as text if not a reference, or as
282             the Data::Dumper output if it is a reference.
283             EOF
284            
285             =head2 run_list, smry_list, help_list
286            
287             The C command lists the text of the current node (pages if necessary). With a single argument, lists the nodes with that tag.
288            
289             =cut
290            
291             sub run_list {
292 0     0 1   my ($self, @args) = @_;
293 0           my $return = '';
294 0           foreach ($self->{current_node}->nodes($args[0])) {
295 0           $return .= $_->myline() . "\n";
296             }
297 0           $self->{payload}->page($return);
298             }
299 0     0 1   sub smry_list { "List the children of the current node" }
300 0     0 1   sub help_list { <
301             The 'list' command lists the children of the current node, including macro expansions and collapsing groups.
302             EOF
303            
304             =head2 run_goto, smry_goto, help_goto
305            
306             The C command changes the current node by applying a "find" to the current node and switching the current node if it succeeds.
307             If you provide neither [] nor (), the command assumes [].
308            
309             =cut
310            
311             sub run_goto {
312 0     0 1   my ($self, @args) = @_;
313 0           my @loc = split /\//, (join ' ', @args);
314 0           foreach (@loc) {
315 0           s/^ *//;
316 0           s/ *$//;
317 0 0 0       if ($_ =~ ' ' and $_ !~ /\[/ and $_ !~ /\(/) {
      0        
318 0           s/ /\[/;
319 0           $_ .= ']';
320             }
321             }
322 0           my $l = join ('/', @loc);
323 0           my $possible = $self->{current_node};
324 0 0         if ($loc[0] eq '') {
325 0           shift @loc;
326 0           $possible = $possible->root;
327             }
328 0           while ($loc[0] =~ /^\.+$/) {
329 0           my $up = shift @loc;
330 0 0         $possible = $possible->parent if $up eq '..';
331             }
332 0 0 0       $possible = $possible->find(join '/', @loc) if defined $possible and @loc and $loc[0];
      0        
333 0 0         if (defined($possible)) {
334 0           $self->{current_node} = $possible;
335             } else {
336 0           print "Can't find node '$l'.\n";
337             }
338             }
339 0     0 1   sub smry_goto { "Make a node the current node" }
340 0     0 1   sub help_goto { <
341             The 'goto' command moves the node cursor by using a path notation.
342             goto / -> goes to the root node.
343             goto /node1 -> goes to the first "node1" under the root node.
344             goto .. -> goes to the current node's parent.
345             goto node1 -> goes to the first "node1" under the current node.
346             goto node1 sam -> goes to the first "node1" named sam under the current node.
347             goto node1[sam] -> same thing.
348             goto node1(2) -> goes to the third "node1" under the current node.
349             EOF
350            
351             =head2 prompt_str()
352            
353             Returns the current prompt. TODO: make this overridable from the node definition.
354            
355             =cut
356            
357             sub prompt_str {
358 0     0 1   my $self = shift;
359 0           my $l = $self->{current_node}->mylocation();
360 0 0         $l = "*root*" if $l eq '/';
361 0           $l . " > ";
362             }
363            
364             =head1 AUTHOR
365            
366             Michael Roberts, C<< >>
367            
368             =head1 BUGS
369            
370             Please report any bugs or feature requests to C, or through
371             the web interface at L. I will be notified, and then you'll
372             automatically be notified of progress on your bug as I make changes.
373            
374             =head1 LICENSE AND COPYRIGHT
375            
376             Copyright 2010 Michael Roberts.
377            
378             This program is free software; you can redistribute it and/or modify it
379             under the terms of either: the GNU General Public License as published
380             by the Free Software Foundation; or the Artistic License.
381            
382             See http://dev.perl.org/licenses/ for more information.
383            
384             =cut
385            
386             1; # End of Decl::Semantics::Commandline