File Coverage

blib/lib/DateTime/Format/Builder/Parser.pm
Criterion Covered Total %
statement 173 184 94.0
branch 61 76 80.2
condition 7 15 46.6
subroutine 32 34 94.1
pod 9 20 45.0
total 282 329 85.7


line stmt bran cond sub pod time code
1             package DateTime::Format::Builder::Parser;
2             {
3             $DateTime::Format::Builder::Parser::VERSION = '0.81';
4             }
5 24     24   27150 use strict;
  24         48  
  24         820  
6 24     24   139 use warnings;
  24         51  
  24         783  
7 24     24   125 use Carp qw( croak );
  24         39  
  24         1352  
8 24         1570 use Params::Validate qw(
9             validate SCALAR CODEREF UNDEF ARRAYREF
10 24     24   1442 );
  24         11875  
11 24     24   383 use Scalar::Util qw( weaken );
  24         36  
  24         1543524  
12              
13              
14              
15              
16             sub on_fail {
17 4     4 0 12 my ( $self, $input, $parent ) = @_;
18 4         15 my $maker = $self->maker;
19 4 50 33     238 if ( $maker and $maker->can('on_fail') ) {
20 4         49 $maker->on_fail($input);
21             }
22             else {
23 0         0 croak __PACKAGE__ . ": Invalid date format: $input";
24             }
25             }
26              
27             sub no_parser {
28 0     0 0 0 croak "No parser set for this parser object.";
29             }
30              
31             sub new {
32 40     40 0 64 my $class = shift;
33 40   33     208 $class = ref($class) || $class;
34 40         69 my $i = 0;
35 40         240 my $self = bless {
36             on_fail => \&on_fail,
37             parser => \&no_parser,
38             }, $class;
39              
40 40         107 return $self;
41             }
42              
43 4     4 0 16 sub maker { $_[0]->{maker} }
44              
45             sub set_maker {
46 40     40 0 69 my $self = shift;
47 40         58 my $maker = shift;
48              
49 40         147 $self->{maker} = $maker;
50 40 50       160 weaken $self->{maker}
51             if ref $self->{maker};
52              
53 40         74 return $self;
54             }
55              
56             sub fail {
57 5     5 0 15 my ( $self, $parent, $input ) = @_;
58 5         39 $self->{on_fail}->( $self, $input, $parent );
59             }
60              
61             sub parse {
62 52     52 0 10213 my ( $self, $parent, $input, @args ) = @_;
63 52         187 my $r = $self->{parser}->( $parent, $input, @args );
64 52 100       180 $self->fail( $parent, $input ) unless defined $r;
65 49         181 $r;
66             }
67              
68             sub set_parser {
69 40     40 0 75 my ( $self, $parser ) = @_;
70 40         80 $self->{parser} = $parser;
71 40         273 $self;
72             }
73              
74             sub set_fail {
75 1     1 0 2 my ( $self, $fail ) = @_;
76 1         4 $self->{on_fail} = $fail;
77 1         2 $self;
78             }
79              
80              
81             my @callbacks = qw( on_match on_fail postprocess preprocess );
82              
83             {
84              
85              
86             my %params = (
87             common => {
88             length => {
89             type => SCALAR | ARRAYREF,
90             optional => 1,
91             callbacks => {
92             'is an int' => sub { ref $_[0] ? 1 : $_[0] !~ /\D/ },
93             'not empty' => sub { ref $_[0] ? @{ $_[0] } >= 1 : 1 },
94             }
95             },
96              
97             # Stuff used by callbacks
98             label => { type => SCALAR, optional => 1 },
99             (
100             map { $_ => { type => CODEREF | ARRAYREF, optional => 1 } }
101             @callbacks
102             ),
103             },
104             );
105              
106              
107             sub params {
108 65     65 1 99 my $self = shift;
109 65   33     293 my $caller = ref $self || $self;
110 65         153 return { map { %$_ } @params{ $caller, 'common' } };
  130         1608  
111             }
112              
113              
114             my $all_params;
115              
116             sub params_all {
117 65 100   65 1 1255 return $all_params if defined $all_params;
118 15         70 my %all_params = map { %$_ } values %params;
  75         383  
119 15         256 $_->{optional} = 1 for values %all_params;
120 15         1476615 $all_params = \%all_params;
121             }
122              
123              
124             my %inverse;
125              
126             sub valid_params {
127 96     96 1 215 my $self = shift;
128 96         329 my $from = (caller)[0];
129 96         339 my %args = @_;
130 96         245 $params{$from} = \%args;
131 96         376 for ( keys %args ) {
132              
133             # %inverse contains keys matching all the
134             # possible params; values are the class if and
135             # only if that class is the only one that uses
136             # the given param.
137 192 50       668 $inverse{$_} = exists $inverse{$_} ? undef : $from;
138             }
139 96         595 undef $all_params;
140 96         310 1;
141             }
142              
143              
144             sub whose_params {
145 76     76 1 118 my $param = shift;
146 76         186 return $inverse{$param};
147             }
148             }
149              
150              
151             sub create_single_object {
152 0     0 0 0 my ($self) = shift;
153 0         0 my $obj = $self->new;
154 0         0 my $parser = $self->create_single_parser(@_);
155              
156 0         0 $obj->set_parser($parser);
157             }
158              
159             sub create_single_parser {
160 65     65 1 98 my $class = shift;
161 65 50       196 return $_[0] if ref $_[0] eq 'CODE'; # already code
162 65 50       148 @_ = %{ $_[0] } if ref $_[0] eq 'HASH'; # turn hashref into hash
  0         0  
163             # ordinary boring sort
164 65         199 my %args = validate( @_, params_all() );
165              
166             # Determine variables for ease of reference.
167 65         460 for (@callbacks) {
168 260 100       909 $args{$_} = $class->merge_callbacks( $args{$_} ) if $args{$_};
169             }
170              
171             # Determine parser class
172 65         97 my $from;
173 65         147 for ( keys %args ) {
174 76         179 $from = whose_params($_);
175 76 100 66     376 next if ( not defined $from ) or ( $from eq 'common' );
176 65         114 last;
177             }
178 65 50       254 croak "Could not identify a parsing module to use." unless $from;
179              
180             # Find and call parser creation method
181 65 50       1129 my $method = $from->can("create_parser")
182             or croak
183             "Can't create a $_ parser (no appropriate create_parser method)";
184 65         222 my @args = %args;
185 65         315 %args = validate( @args, $from->params() );
186 65         598 $from->$method(%args);
187             }
188              
189              
190             sub merge_callbacks {
191 30     30 1 2764 my $self = shift;
192              
193 30 100       110 return unless @_; # No arguments
194 29 100       67 return unless $_[0]; # Irrelevant argument
195 28         50 my @callbacks = @_;
196 28 100       65 if ( @_ == 1 ) {
197 27 100       121 return $_[0] if ref $_[0] eq 'CODE';
198 3 100       13 @callbacks = @{ $_[0] } if ref $_[0] eq 'ARRAY';
  2         9  
199             }
200 4 100       13 return unless @callbacks;
201              
202 3         8 for (@callbacks) {
203 15 100       303 croak "All callbacks must be coderefs!" unless ref $_ eq 'CODE';
204             }
205              
206             return sub {
207 2     2   9 my $rv;
208 2         8 my %args = @_;
209 2         5 for my $cb (@callbacks) {
210 14         42 $rv = $cb->(%args);
211 14 50       68 return $rv unless $rv;
212              
213             # Ugh. Symbiotic. All but postprocessor return the date.
214 14 50       45 $args{input} = $rv unless $args{parsed};
215             }
216 2         14 $rv;
217 2         16 };
218             }
219              
220              
221             sub create_multiple_parsers {
222 40     40 1 82 my $class = shift;
223 40         93 my ( $options, @specs ) = @_;
224              
225 40         150 my $obj = $class->new;
226              
227             # Organise the specs, and transform them into parsers.
228 40         171 my ( $lengths, $others ) = $class->sort_parsers( $options, \@specs );
229              
230             # Merge callbacks if any.
231 40         120 for ('preprocess') {
232 40 100       175 $options->{$_} = $class->merge_callbacks( $options->{$_} )
233             if $options->{$_};
234             }
235              
236             # Custom fail method?
237 40 100       142 $obj->set_fail( $options->{on_fail} ) if exists $options->{on_fail};
238              
239             # Who's our maker?
240 40 50       235 $obj->set_maker( $options->{maker} ) if exists $options->{maker};
241              
242             # We don't want to save the whole options hash as a closure, since
243             # that can cause a circular reference when $options->{maker} is
244             # set.
245 40         68 my $preprocess = $options->{preprocess};
246              
247             # These are the innards of a multi-parser.
248             my $parser = sub {
249 52     52   97 my ( $self, $date, @args ) = @_;
250 52 50       180 return unless defined $date;
251              
252             # Parameters common to the callbacks. Pre-prepared.
253 52 100       270 my %param = (
254             self => $self,
255             ( @args ? ( args => \@args ) : () ),
256             );
257              
258 52         87 my %p;
259              
260             # Preprocess and potentially fill %p
261 52 100       147 if ($preprocess) {
262 7         31 $date = $preprocess->( input => $date, parsed => \%p, %param );
263             }
264              
265             # Find length parser
266 52 100       223 if (%$lengths) {
267 9         18 my $length = length $date;
268 9         18 my $parser = $lengths->{$length};
269 9 100       22 if ($parser) {
270              
271             # Found one, call it with _copy_ of %p
272 8         32 my $dt = $parser->( $self, $date, {%p}, @args );
273 8 50       41 return $dt if defined $dt;
274             }
275             }
276              
277             # Or calls all others, with _copy_ of %p
278 44         100 for my $parser (@$others) {
279 74         650 my $dt = $parser->( $self, $date, {%p}, @args );
280 74 100       418 return $dt if defined $dt;
281             }
282              
283             # Failed, return undef.
284 5         20 return;
285 40         352 };
286 40         161 $obj->set_parser($parser);
287             }
288              
289              
290             sub sort_parsers {
291 40     40 1 73 my $class = shift;
292 40         77 my ( $options, $specs ) = @_;
293 40         54 my ( %lengths, @others );
294              
295 40         103 for my $spec (@$specs) {
296              
297             # Put coderefs straight into the 'other' heap.
298 65 50       309 if ( ref $spec eq 'CODE' ) {
    50          
299 0         0 push @others, $spec;
300             }
301              
302             # Specifications...
303             elsif ( ref $spec eq 'HASH' ) {
304 65 100       177 if ( exists $spec->{length} ) {
305 12         55 my $code = $class->create_single_parser(%$spec);
306             my @lengths
307 1         4 = ref $spec->{length}
308 12 100       66 ? @{ $spec->{length} }
309             : ( $spec->{length} );
310 12         23 for my $length (@lengths) {
311 13         20 push @{ $lengths{$length} }, $code;
  13         61  
312             }
313             }
314             else {
315 53         246 push @others, $class->create_single_parser(%$spec);
316             }
317             }
318              
319             # Something else
320             else {
321 0         0 croak "Invalid specification in list.";
322             }
323             }
324              
325 40         208 while ( my ( $length, $parsers ) = each %lengths ) {
326 9         29 $lengths{$length} = $class->chain_parsers($parsers);
327             }
328              
329 40         123 return ( \%lengths, \@others );
330             }
331              
332             sub chain_parsers {
333 9     9 0 16 my ( $self, $parsers ) = @_;
334 9 100       44 return $parsers->[0] if @$parsers == 1;
335             return sub {
336 5     5   8 my $self = shift;
337 5         9 for my $parser (@$parsers) {
338 10         30 my $rv = $self->$parser(@_);
339 10 100       49 return $rv if defined $rv;
340             }
341 0         0 return undef;
342 3         19 };
343             }
344              
345              
346             sub create_parser {
347 40     40 1 78 my $class = shift;
348 40 50       150 if ( not ref $_[0] ) {
349              
350             # Simple case of single specification as a hash
351 0         0 return $class->create_single_object(@_);
352             }
353              
354             # Let's see if we were given an options block
355 40         74 my %options;
356 40         154 while ( ref $_[0] eq 'ARRAY' ) {
357 44         84 my $options = shift;
358 44         262 %options = ( %options, @$options );
359             }
360              
361             # Now, can we create a multi-parser out of the remaining arguments?
362 40 100 66     244 if ( ref $_[0] eq 'HASH' or ref $_[0] eq 'CODE' ) {
363 20         107 return $class->create_multiple_parsers( \%options, @_ );
364             }
365             else {
366             # If it wasn't a HASH or CODE, then it was (ideally)
367             # a list of pairs describing a single specification.
368 20         115 return $class->create_multiple_parsers( \%options, {@_} );
369             }
370             }
371              
372              
373             # Find all our workers
374             {
375 24     24   24431 use Class::Factory::Util 1.6;
  24         23843  
  24         155  
376              
377             foreach my $worker ( __PACKAGE__->subclasses ) {
378 24     24   16142 eval "use DateTime::Format::Builder::Parser::$worker;";
  24     24   66  
  24     24   449  
  24     24   26184  
  24     24   63  
  24         391  
  24         15611  
  24         70  
  24         409  
  24         15972  
  24         86  
  24         517  
  24         145  
  24         52  
  24         269  
379             die $@ if $@;
380             }
381             }
382              
383             1;
384              
385             # ABSTRACT: Parser creation
386              
387             __END__