File Coverage

blib/lib/Parse/Constructor/Arguments.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Parse::Constructor::Arguments;
2             our $VERSION = '0.091570';
3              
4              
5             #ABSTRACT: Parse Moose constructor arguments using PPI
6 2     2   55906 use Moose;
  0            
  0            
7             use PPI;
8             use MooseX::Types::Moose(':all');
9              
10             BEGIN
11             {
12             *DEBUG = sub () { 0 } unless defined *DEBUG{CODE};
13             }
14              
15             has document =>
16             (
17             is => 'ro',
18             isa => 'PPI::Document',
19             lazy_build => 1,
20             );
21              
22             has current =>
23             (
24             is => 'ro',
25             isa => 'PPI::Element',
26             lazy => 1,
27             builder => '_build_current',
28             writer => '_set_current',
29             );
30              
31             has input =>
32             (
33             is => 'ro',
34             isa => Str,
35             required => 1,
36             );
37              
38             sub _build_current
39             {
40             my $self = shift;
41             # our first token should be significant
42             my $token = $self->document->first_token;
43              
44             if($token->significant)
45             {
46             return $token;
47             }
48            
49             while(1)
50             {
51             $token = $token->next_token;
52             die "No more significant tokens in stream: '$token'" if not $token;
53             return $token if $token->significant;
54             }
55             }
56              
57             sub _build_document
58             {
59             my $self = shift;
60             my $input = $self->input;
61             my $document = PPI::Document->new(\$input);
62             return $document;
63             }
64              
65              
66             # states:
67             # 0 - Looking for a Word or Literal to use as a key
68             # 1 - Looking for a comma operator
69             # 2 - Looking for a value
70             sub parse
71             {
72             my $class = shift;
73             my $str = shift;
74             my $self = $class->new(input => $str);
75              
76             # grab the current token, which should be the first significant token
77             my $token = $self->current;
78            
79             # what we are building
80             my %data;
81              
82             # state related parsing variables
83             my $key;
84             my $state = 0;
85            
86             while(1)
87             {
88             if($state == 0)
89             {
90             if($token->isa('PPI::Token::Word'))
91             {
92             DEBUG && warn "Word Key: $token";
93             $key = $token->content;
94             }
95             elsif($token->isa('PPI::Token::Quote::Single') or $token->isa('PPI::Token::Quote::Literal'))
96             {
97             DEBUG && warn "Quote Key: $token";
98             $key = $token->literal;
99             }
100             else
101             {
102             die "Invalid state: Expected a Word or Literal but got '$token'";
103             }
104            
105             $state++;
106             }
107             elsif($state == 1)
108             {
109             if($token->isa('PPI::Token::Operator') && $token->content =~ /,|=>/)
110             {
111             DEBUG && warn "Comma: $token";
112             }
113             else
114             {
115             die "Invalid state: Expected a Comma operator, but got '$token'";
116             }
117            
118             $state++;
119             }
120             elsif($state == 2)
121             {
122             if($token->isa('PPI::Token::Quote::Single') or $token->isa('PPI::Token::Quote::Literal'))
123             {
124             DEBUG && warn "Quote Value: $token";
125             $data{$key} = $token->literal;
126             }
127             elsif($token->isa('PPI::Token::Structure'))
128             {
129             my $content = $token->content;
130             die "Unsupported structure '$content'"
131             if $content ne '[' and $content ne '{';
132              
133             DEBUG && warn 'Constructor: ' . $token->parent;
134             $data{$key} = $self->process;
135             }
136             elsif($token->isa('PPI::Token::Number'))
137             {
138             DEBUG && warn "Number: $token";
139             $data{$key} = $token->literal;
140             }
141             else
142             {
143             die "Invalid state: Expected Literal, Number or Structure, but got '$token'";
144             }
145            
146             $state++;
147             $key = undef;
148             }
149             elsif($state == 3)
150             {
151             if($token->isa('PPI::Token::Operator') && $token->content =~ /,|=>/)
152             {
153             DEBUG && warn "Comma: $token";
154             }
155             else
156             {
157             die "Invalid state: Expected a Comma operator, but got '$token'";
158             }
159              
160             $state = 0;
161             }
162            
163             if(my $t = $self->peek_next_token)
164             {
165             DEBUG && warn "Peeked and took $t";
166             $token = $t;
167             $self->_set_current($token);
168             }
169             else
170             {
171             DEBUG && warn "Peeked and there were no more tokens";
172             last;
173             }
174             }
175              
176             return \%data;
177             }
178              
179             sub process
180             {
181             my $self = shift;
182             my ($data, $applicator, $terminator, $word, $token);
183            
184             if($self->current->content eq '[')
185             {
186             DEBUG && warn "Processing Array...";
187             $data = [];
188             $terminator = ']';
189             $applicator = sub { push(@{$_[0]}, $_[2]) };
190             }
191             else
192             {
193             DEBUG && warn "Processing Hash...";
194             $data = {};
195             $terminator = '}';
196             $applicator = sub { $_[0]->{$_[1]} = $_[2] };
197             }
198              
199             $token = $self->get_next_significant;
200              
201             while($token->content ne $terminator)
202             {
203             # words are stored until we know if they are a key or a value
204             if($token->isa('PPI::Token::Word'))
205             {
206             DEBUG && warn "Process Word: $token";
207             $word = $token->content;
208             $token = $self->get_next_significant;
209             }
210              
211             if($token->isa('PPI::Token::Number'))
212             {
213             DEBUG && warn "Process Number: $token";
214             $applicator->($data, $word, $token->content);
215             $word = undef;
216             }
217             elsif($token->isa('PPI::Token::Structure'))
218             {
219             DEBUG && warn "Process Structure: $token";
220             $applicator->($data, $word, $self->process);
221             $word = undef;
222             }
223             elsif($token->isa('PPI::Token::Quote::Single') || $token->isa('PPI::Token::Quote::Literal'))
224             {
225             DEBUG && warn "Process Quote: $token";
226             if(!$word && $terminator eq '}')
227             {
228             DEBUG && warn "Process Hash Key Quote: $token";
229             $word = $token->literal;
230             $token = $self->get_next_significant;
231             next;
232             }
233              
234             $applicator->($data, $word, $token->literal);
235             $word = undef;
236             }
237             elsif($token->isa('PPI::Token::QuoteLike::Words') and $terminator ne '}')
238             {
239             # This seems to be the only way to get the fuckin data from this token
240             # which is completely retarded. Need to file a bug with PPI on this
241             DEBUG && warn "Process QuoteLike Words: $token";
242            
243             my $operator = $token->{operator};
244             my $separator = $token->{separator};
245             my $content = $token->content;
246             $content =~ s/$operator|$separator//g;
247            
248             $applicator->($data, undef, $_) for split(' ', $content);
249             }
250             elsif($token->isa('PPI::Token::Operator'))
251             {
252             DEBUG && warn "Process Comma: $token";
253             if($token->content =~ /,|=>/)
254             {
255             $token = $self->get_next_significant;
256             next;
257             }
258             }
259            
260             # now we process our words if they haven't been consumed
261             DEBUG && warn "Process Add Word: $word" if $word;
262             $applicator->($data, undef, $word) if $word;
263             $word = undef;
264              
265             $token = $self->get_next_significant;
266             }
267            
268             DEBUG && warn "Returning From Processing";
269             return $data;
270             }
271              
272             sub get_next_significant
273             {
274             my $self = shift;
275             my $token = $self->current;
276            
277             DEBUG && warn "Current: $token";
278             while(1)
279             {
280             $token = $token->next_token;
281             die 'No more significant tokens in stream: '. $self->document if not $token;
282            
283             if(!$token->significant)
284             {
285             next;
286             }
287            
288             DEBUG && warn "Significant: $token";
289             $self->_set_current($token);
290             return $token;
291             }
292             }
293              
294             sub peek_next_token
295             {
296             my $self = shift;
297             my $token = $self->current;
298              
299             while(1)
300             {
301             $token = $token->next_token;
302             return 0 if not $token;
303             return $token if $token->significant;
304             }
305             }
306              
307             __PACKAGE__->meta->make_immutable;
308              
309             1;
310              
311              
312              
313             =pod
314              
315             =head1 NAME
316              
317             Parse::Constructor::Arguments - Parse Moose constructor arguments using PPI
318              
319             =head1 VERSION
320              
321             version 0.091570
322              
323             =head1 DESCRIPTION
324             Parse::Constructor::Arguments parses Moose-style constructor arguments into a
325             usable data structure using PPI to accomplish the task. It exports nothing
326             and the only public method is a class method: parse.
327              
328             =head1 METHODS
329              
330             =head2 parse(ClassName $class: Str $str)
331              
332             This is a class method used for parsing constructor arguments. It takes a
333             string that will be used as the basis of the PPI::Document. Returns a hashref
334             where the keys are the named arguments and the values are the actual values to
335             those named arguments. (eg. q|foo => ['bar']| returns { foo => ['bar'] })
336              
337             =head1 AUTHOR
338              
339             Nicholas Perez <nperez@cpan.org>
340              
341             =head1 COPYRIGHT AND LICENSE
342              
343             This software is copyright (c) 2009 by Nicholas Perez.
344              
345             This is free software; you can redistribute it and/or modify it under
346             the same terms as perl itself.
347              
348             =cut
349              
350              
351              
352             __END__