File Coverage

blib/lib/ARGV/Struct.pm
Criterion Covered Total %
statement 48 49 97.9
branch 23 24 95.8
condition n/a
subroutine 5 5 100.0
pod 1 1 100.0
total 77 79 97.4


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