File Coverage

blib/lib/ARGV/Struct.pm
Criterion Covered Total %
statement 54 60 90.0
branch 23 24 95.8
condition n/a
subroutine 7 9 77.7
pod 1 4 25.0
total 85 97 87.6


line stmt bran cond sub pod time code
1             package ARGV::Struct;
2 3     3   204346 use Moo;
  3         33682  
  3         13  
3 3     3   6029 use Types::Standard qw/ArrayRef/;
  3         227353  
  3         32  
4              
5             our $VERSION = '0.05';
6              
7             has argv => (
8             is => 'ro',
9             isa => ArrayRef,
10             default => sub { [ @ARGV ] },
11             );
12              
13             sub argcount {
14 0     0 0 0 my $self = shift;
15 0         0 return scalar(@{ $self->argv });
  0         0  
16             }
17              
18             sub arg {
19 0     0 0 0 my ($self, $i) = @_;
20 0         0 return $self->argv->[ $i ];
21             }
22              
23             sub args {
24 23     23 0 33 my $self = shift;
25 23         29 return @{ $self->argv };
  23         105  
26             }
27              
28             sub parse {
29 23     23 1 17377 my ($self) = @_;
30 23         65 my $substruct = $self->_parse_argv($self->args);
31 16 100       47 die "Trailing values after structure" if (scalar(@{ $substruct->{ leftover } }));
  16         49  
32 15         47 return $substruct->{ struct };
33             }
34              
35             sub _parse_list {
36 13     13   40 my ($self, @args) = @_;
37 13         22 my $list = [];
38 13         34 while (my $token = shift @args) {
39 38 100       89 if ($token eq '[') {
    100          
    100          
40 3         11 my $substruct = $self->_parse_list(@args);
41 3         10 push @$list, $substruct->{ struct };
42 3         4 @args = @{ $substruct->{ leftover } };
  3         13  
43             } elsif($token eq '{') {
44 4         10 my $substruct = $self->_parse_hash(@args);
45 4         9 push @$list, $substruct->{ struct };
46 4         5 @args = @{ $substruct->{ leftover } };
  4         16  
47             } elsif ($token eq ']') {
48 12         58 return { struct => $list, leftover => [ @args ] };
49             } else {
50 19         65 push @$list, $token;
51             }
52             }
53 1         10 die "Unclosed list";
54             };
55              
56             sub _parse_hash {
57 20     20   42 my ($self, @args) = @_;
58 20         29 my $hash = {};
59 20         52 while (my $token = shift @args) {
60 38 100       76 if ($token eq '}') {
61 14         50 return { struct => $hash, leftover => [ @args ] };
62             }
63              
64 24         42 my ($k, $v) = ($token, shift @args);
65              
66 24 100       61 substr($k,-1,1) = '' if (substr($k,-1,1) eq ':');
67 24 100       59 die "Repeated $k in hash" if (exists $hash->{ $k });
68              
69 23 100       54 die "Key $k doesn't have a value" if (not defined $v);
70 22 100       61 if ($v eq '{'){
    100          
71 2         10 my $substruct = $self->_parse_hash(@args);
72 2         6 $hash->{ $k } = $substruct->{ struct };
73 2         3 @args = @{ $substruct->{ leftover } };
  2         16  
74             } elsif ($v eq '[') {
75 1         4 my $substruct = $self->_parse_list(@args);
76 1         2 $hash->{ $k } = $substruct->{ struct };
77 1         2 @args = @{ $substruct->{ leftover } };
  1         4  
78             } else {
79 19         58 $hash->{ $k } = $v;
80             }
81             }
82 4         41 die "Unclosed hash";
83             }
84              
85             sub _parse_argv {
86 23     23   60 my ($self, @args) = @_;
87              
88 23         41 my $token = shift @args;
89              
90 23 100       59 if ($token eq '[') {
    50          
91 9         29 return $self->_parse_list(@args);
92             } elsif($token eq '{') {
93 14         37 return $self->_parse_hash(@args);
94             } else {
95 0           die "Expecting { or [";
96             }
97             }
98              
99             1;
100             #################### main pod documentation begin ###################
101              
102             =head1 NAME
103              
104             ARGV::Struct - Parse complex data structures passed in ARGV
105              
106             =head1 SYNOPSIS
107              
108             use ARGV::Struct;
109             my $struct = ARGV::Struct->new->parse;
110              
111             =head1 DESCRIPTION
112              
113             Have you ever felt that you need something different than Getopt?
114              
115             Are you tired of shoehorning Getopt style arguments into your commandline scripts?
116              
117             Are you trying to express complex datastructures via command line?
118              
119             then ARGV::Struct is for you!
120              
121             It's designed so the users of your command line utilities won't hate you when things
122             get complex.
123              
124             =head1 THE PAIN
125              
126             I've had to use some command-line utilities that had to do creative stuff to transmit
127             deeply nested arguments, or datastructure-like information. Here are some strategies that
128             I've found over time:
129              
130             =head2 Complex arguments codified as JSON
131              
132             JSON is horrible for the command line because you have to escape the quotes. It's a nightmare.
133              
134             command --complex_arg "{\"key1\":\"value1\",\"key2\":\"value2\"}"
135              
136             =head2 Arguments encoded via some custom scheme
137              
138             These schemes fail when you have to make values complex (lists, or other key/values)
139              
140             command --complex_arg key1,value1:key2,value2
141              
142             =head2 Repeating Getopt arguments
143              
144             Getopt friendly, but too verbose
145              
146             command --key key1 --value value1 --key key1 --value value 2
147              
148             =head1 THE DESIGN
149              
150             The design of this module is aimed at "playing well with the shell". The main purpose is
151             to let the user transmit complex data structures, while staying compact enough for command line
152             use.
153              
154             =head2 Key/Value sets (objects)
155              
156             On the command line, the user can transmit sets of key/value pairs within curly brackets
157              
158             command { K_V_PAIR1 K_V_PAIR2 }
159              
160             The shell is expected to do some work for us, so key/value pairs are separated by spaces
161              
162             Each key/value pair is expressed as
163              
164             Key: Value
165              
166             The colon between Keys and values is optional, so
167              
168             Key Value
169              
170             is the same as above
171              
172             If the value contains spaces, the user can surround the pair with the shell metacharacters
173              
174             command { Key: " Value " }
175              
176             Values can also be objects:
177              
178             command { Key: { Nested Key } }
179              
180             or lists
181              
182             command { Key: [ 1 2 3 ] }
183              
184             If you want a key with a colon at the end, just repeat the colon:
185              
186             Key:: Value
187              
188             =head2 Lists
189              
190             command [ VALUE1 VALUE2 ]
191              
192             Each value can be a simple scalar value, or an object or list
193              
194             command [ { Name X } { Name Y } ]
195             command [ [ 1 2 3 ] [ 4 5 6 ] [ 7 8 9 ] ]
196             command [ "First Value" "Second Value" ]
197              
198             Values are never separated by commas to keep the syntax compact.
199             The shell is expected to split the different elements into tokens, so
200             the user is expected to use shell quotes to keep values together
201              
202             =head1 METHODS
203              
204             =head2 new([argv => ArrayRef])
205              
206             Return an instance of the parser. If argv is not specified, @ARGV will be
207             used.
208              
209             =head2 parse
210              
211             return the parsed data structure
212              
213             =head1 STATUS
214              
215             This module is quite experimental. I developed it while developing Paws (a
216             Perl AWS SDK). It has a commandline utility that needs to recollect all the
217             Attributes and Values for method calls, and lots of times, they get complex.
218             Since trying to pass params with Getopt was getting ugly as hell, I decided
219             that it would be better to do things in a different way, and eventually
220             thought it could be an independent module.
221              
222             I'm publishing this module to get the idea out to the public so it can be worked
223             on.
224              
225             Please bash the guts out of it. Break it and shake it till it falls apart.
226              
227             Contribute bugs and patches. All input is welcome.
228              
229             To help with the bashing, when you install this dist, you get a command line util
230             called argvstruct. It will basically print a Data::Dumper of the structure generated
231             by it's arguments
232              
233             user@host:~$ argvstruct { Hello Guys How [ Are You { Doing Today } ] }
234             $VAR1 = {
235             'Hello' => 'Guys',
236             'How' => [
237             'Are',
238             'You',
239             {
240             'Doing' => 'Today'
241             }
242             ]
243             };
244              
245             =head1
246              
247             =head1 TODO
248              
249             Try to combine with Getopt/MooseX::Getopt, so some parameters could be an ARGV::Struct. The
250             rest would be parsed Getopt style.
251              
252             =head1 CONTRIBUTE
253              
254             The source code and issues are on https://github.com/pplu/ARGV-Struct
255              
256             =head1 THANKS
257              
258             Matt S. Trout for suggesting that ARGV::Struct syntax be JSONY compatible
259              
260             =head1 AUTHOR
261              
262             Jose Luis Martinez
263             CPAN ID: JLMARTIN
264             CAPSiDE
265             jlmartinez@capside.com
266             http://www.pplusdomain.net
267              
268             =head1 COPYRIGHT
269              
270             Copyright (c) 2015 by Jose Luis Martinez Torres
271              
272             This program is free software; you can redistribute
273             it and/or modify it under the same terms as Perl itself.
274              
275             The full text of the license can be found in the
276             LICENSE file included with this module.
277              
278             =cut