File Coverage

blib/lib/Commandable/Command.pm
Criterion Covered Total %
statement 72 72 100.0
branch 23 26 88.4
condition 8 11 72.7
subroutine 21 21 100.0
pod 4 8 50.0
total 128 138 92.7


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2021 -- leonerd@leonerd.org.uk
5              
6             package Commandable::Command 0.09;
7              
8 9     9   1245 use v5.14;
  9         37  
9 9     9   61 use warnings;
  9         34  
  9         9419  
10              
11             =head1 NAME
12              
13             C - represent metadata for an invokable command
14              
15             =cut
16              
17             sub new
18             {
19 19     19 0 37 my $class = shift;
20 19         74 my %args = @_;
21 19   100     81 $args{arguments} //= [];
22 19   100     117 $args{options} //= {};
23 19         176 bless [ @args{qw( name description arguments options package code )} ], $class;
24             }
25              
26             =head1 ACCESSORS
27              
28             The following simple methods return metadata fields about the command
29              
30             =cut
31              
32             =head2 name
33              
34             =head2 description
35              
36             $name = $command->name
37             $desc = $command->description
38              
39             Strings giving the short name (to be used on a commandline), and descriptive
40             text for the command.
41              
42             =head2 arguments
43              
44             @args = $command->arguments
45              
46             A (possibly-empty) list of argument metadata structures.
47              
48             =cut
49              
50 19     19 1 115 sub name { shift->[0] }
51 6     6 1 37 sub description { shift->[1] }
52 22     22 1 35 sub arguments { @{ shift->[2] } }
  22         73  
53 20     20 0 28 sub options { %{ shift->[3] } }
  20         133  
54 3     3 0 25 sub package { shift->[4] }
55 9     9 0 36 sub code { shift->[5] }
56              
57             =head1 METHODS
58              
59             =cut
60              
61             =head2 parse_invocation
62              
63             @vals = $command->parse_invocation( $cinv )
64              
65             Parses values out of a L instance according to the
66             specification for the command's arguments. Returns a list of perl values
67             suitable to pass into the function implementing the command.
68              
69             This method will throw an exception if mandatory arguments are missing.
70              
71             =cut
72              
73             sub parse_invocation
74             {
75 19     19 1 74 my $self = shift;
76 19         37 my ( $cinv ) = @_;
77              
78 19         31 my @args;
79              
80 19 100       42 if( my %optspec = $self->options ) {
81 11         28 push @args, my $opts = {};
82 11         19 my @remaining;
83              
84 11         31 while( defined( my $token = $cinv->pull_token ) ) {
85 11 50       26 last if $token eq "--";
86              
87 11         17 my $spec;
88             my $value_in_token;
89              
90 11 100       58 if( $token =~ s/^--([^=]+)(=|$)// ) {
    100          
91 6 50       38 $spec = $optspec{$1} or die "Unrecognised option name --$1\n";
92 6         15 $value_in_token = length $2;
93             }
94             elsif( $token =~ s/^-(.)// ) {
95 1 50       6 $spec = $optspec{$1} or die "Unrecognised option name -$1\n";
96 1         11 $value_in_token = length $token;
97             }
98             else {
99 4         6 push @remaining, $token;
100 4         12 next;
101             }
102              
103 7         12 my $value = 1;
104 7 100       27 if( $spec->mode eq "value" ) {
105 3 100 50     23 $value = $value_in_token ? $token
106             : ( $cinv->pull_token // die "Expected value for option --".$spec->name."\n" );
107             }
108              
109 7         19 $opts->{ $spec->name } = $value;
110             }
111              
112 11         54 $cinv->putback_tokens( @remaining );
113              
114 11         48 foreach my $spec ( values %optspec ) {
115 41 100 33     80 $opts->{ $spec->name } //= $spec->default if defined $spec->default;
116             }
117             }
118              
119 19         68 foreach my $argspec ( $self->arguments ) {
120 13         35 my $val = $cinv->pull_token;
121 13 100       60 if( defined $val ) {
    100          
122 7 100       24 if( $argspec->slurpy ) {
123 1         6 my @vals = ( $val );
124 1         3 while( defined( $val = $cinv->pull_token ) ) {
125 2         6 push @vals, $val;
126             }
127 1         3 $val = \@vals;
128             }
129 7         19 push @args, $val;
130             }
131             elsif( !$argspec->optional ) {
132 1         11 die "Expected a value for '".$argspec->name."' argument\n";
133             }
134             else {
135             # optional argument was missing; this is the end of the args
136 5         20 last;
137             }
138             }
139              
140 18         105 return @args;
141             }
142              
143             package # hide
144             Commandable::Command::_Argument;
145              
146             sub new
147             {
148 13     13   30 my $class = shift;
149 13         48 my %args = @_;
150 13         120 bless [ @args{qw( name description optional slurpy )} ], $class;
151             }
152              
153 4     4   33 sub name { shift->[0] }
154 2     2   23 sub description { shift->[1] }
155 7     7   52 sub optional { shift->[2] }
156 8     8   36 sub slurpy { shift->[3] }
157              
158             package # hide
159             Commandable::Command::_Option;
160              
161             sub new
162             {
163 7     7   16 my $class = shift;
164 7         24 my %args = @_;
165 7 100       51 $args{mode} = "value" if $args{name} =~ s/:$//;
166 7         26 my @names = split m/\|/, delete $args{name};
167 7   100     32 $args{mode} //= "set";
168 7         50 bless [ \@names, @args{qw( description mode default )} ], $class;
169             }
170              
171 12     12   45 sub name { shift->[0]->[0] }
172 9     9   17 sub names { @{ shift->[0] } }
  9         47  
173 2     2   8 sub description { shift->[1] }
174 7     7   25 sub mode { shift->[2] }
175 44     44   102 sub default { shift->[3] }
176              
177             =head1 AUTHOR
178              
179             Paul Evans
180              
181             =cut
182              
183             0x55AA;