File Coverage

blib/lib/Text/Parser/Rule.pm
Criterion Covered Total %
statement 124 124 100.0
branch 56 56 100.0
condition 12 12 100.0
subroutine 31 31 100.0
pod 2 3 66.6
total 225 226 99.5


line stmt bran cond sub pod time code
1 30     30   389737 use strict;
  30         219  
  30         1316  
2 30     30   166 use warnings;
  30         61  
  30         1342  
3              
4             package Text::Parser::Rule 0.927;
5              
6             # ABSTRACT: Makes it possible to write AWK-style parsing rules for Text::Parser
7              
8 30     30   578 use Moose;
  30         404315  
  30         244  
9 30     30   194772 use Text::Parser::Errors;
  30         70  
  30         3907  
10 30     30   217 use Scalar::Util 'blessed', 'looks_like_number';
  30         65  
  30         1465  
11 30     30   1290 use String::Util ':all';
  30         5762  
  30         68952  
12              
13              
14             has condition => (
15                 is => 'rw',
16                 isa => 'Str',
17                 predicate => '_has_condition',
18                 init_arg => 'if',
19                 trigger => \&_set_condition,
20             );
21              
22             sub _set_condition {
23 32     32   59     my $self = shift;
24 32         808     $self->_has_blank_condition(0);
25 32         85     $self->_set_highest_nf;
26 32         712     $self->_cond_sub_str( _gen_sub_str( $self->condition ) );
27 32         749     $self->_cond_sub(
28                     _set_cond_sub( $self->condition, $self->_cond_sub_str ) );
29             }
30              
31             has _has_blank_condition => (
32                 is => 'rw',
33                 isa => 'Bool',
34                 lazy => 1,
35                 default => 1,
36             );
37              
38             sub _set_highest_nf {
39 74     74   108     my $self = shift;
40 74         148     my $nf = _get_min_req_fields( $self->_gen_joined_str );
41 74         2213     $self->_set_min_nf($nf);
42             }
43              
44             sub _gen_joined_str {
45 74     74   105     my $self = shift;
46 74         132     my (@strs) = ();
47 74 100       2061     push @strs, $self->condition if $self->_has_condition;
48 74 100       1881     push @strs, $self->action if $self->_has_action;
49 74 100       2138     push @strs, $self->_join_preconds('; ') if not $self->_no_preconds;
50 74         359     my $str = join '; ', @strs;
51             }
52              
53             sub _get_min_req_fields {
54 74     74   111     my $str = shift;
55                 my @indx
56 74         1507         = $str =~ /\$([0-9]+)|\$[{]([-][0-9]+)[}]|[$@][{]([-]?[0-9]+)[+][}]/g;
57 74         189     my @inds = sort { $b <=> $a } ( grep { defined $_ } @indx );
  59         183  
  294         512  
58 74 100       181     return 0 if not @inds;
59 46 100       175     ( $inds[0] >= -$inds[-1] ) ? $inds[0] : -$inds[-1];
60             }
61              
62             my $SUB_BEGIN = 'sub {
63             my $this = shift;
64             my $__ = $this->_ExAWK_symbol_table;
65             local $_ = $this->this_line;
66             ';
67              
68             my $SUB_END = '
69             }';
70              
71             sub _gen_sub_str {
72 74     74   113     my $str = shift;
73 74         160     my $anon = $SUB_BEGIN . _replace_awk_vars($str) . $SUB_END;
74 74         1767     return $anon;
75             }
76              
77             sub _replace_awk_vars {
78 74     74   115     local $_ = shift;
79 74         155     _replace_positional_indicators();
80 74         173     _replace_range_shortcut();
81 74 100       176     _replace_exawk_vars() if m/[~][a-z_][a-z0-9_]+/i;
82 74         263     return $_;
83             }
84              
85             sub _replace_positional_indicators {
86 74     74   144     s/\$0/\$this->this_line/g;
87 74         113     s/\$[{]([-][0-9]+)[}]/\$this->field($1)/g;
88 74         290     s/\$([0-9]+)/\$this->field($1-1)/g;
89             }
90              
91             sub _replace_range_shortcut {
92 74     74   109     s/\$[{]([-][0-9]+)[+][}]/\$this->join_range($1)/g;
93 74         121     s/\$[{]([0-9]+)[+][}]/\$this->join_range($1-1)/g;
94 74         126     s/\\\@[{]([-][0-9]+)[+][}]/[\$this->field_range($1)]/g;
95 74         100     s/\\\@[{]([0-9]+)[+][}]/[\$this->field_range($1-1)]/g;
96 74         113     s/\@[{]([-][0-9]+)[+][}]/\$this->field_range($1)/g;
97 74         94     s/\@[{]([0-9]+)[+][}]/\$this->field_range($1-1)/g;
98             }
99              
100             sub _replace_exawk_vars {
101 7     7   30     my (@varnames) = _uniq( $_ =~ /[~]([a-z_][a-z0-9_]+)/ig );
102 7         16     foreach my $var (@varnames) {
103 10         20         my $v = '~' . $var;
104 10         102         s/$v/\$__->{$var}/g;
105                 }
106             }
107              
108             sub _uniq {
109 7     7   14     my (%elem) = map { $_ => 1 } @_;
  10         37  
110 7         26     return ( keys %elem );
111             }
112              
113             has _cond_sub_str => (
114                 is => 'rw',
115                 isa => 'Str',
116                 init_arg => undef,
117             );
118              
119             sub _set_cond_sub {
120 74     74   157     my ( $rstr, $sub_str ) = @_;
121 74         7293     my $sub = eval $sub_str;
122 74 100       231     _throw_bad_cond( $rstr, $sub_str, $@ ) if not defined $sub;
123 73         1843     return $sub;
124             }
125              
126             sub _throw_bad_cond {
127 1     1   3     my ( $code, $sub_str, $msg ) = @_;
128 1         6     die bad_rule_syntax(
129                     code => $code,
130                     msg => $msg,
131                     subroutine => $sub_str,
132                 );
133             }
134              
135             has _cond_sub => (
136                 is => 'rw',
137                 isa => 'CodeRef',
138                 init_arg => undef,
139             );
140              
141              
142             has min_nf => (
143                 is => 'ro',
144                 isa => 'Num',
145                 traits => ['Number'],
146                 init_arg => undef,
147                 default => 0,
148                 lazy => 1,
149                 handles => { _set_min_nf => 'set', }
150             );
151              
152              
153             has action => (
154                 is => 'rw',
155                 isa => 'Str',
156                 init_arg => 'do',
157                 predicate => '_has_action',
158                 trigger => \&_set_action,
159             );
160              
161             sub _set_action {
162 36     36   73     my $self = shift;
163 36         339     $self->_set_highest_nf;
164 36         804     $self->_act_sub_str( _gen_sub_str( $self->action ) );
165 36         813     $self->_act_sub( _set_cond_sub( $self->action, $self->_act_sub_str ) );
166             }
167              
168             has _act_sub => (
169                 is => 'rw',
170                 isa => 'CodeRef',
171                 init_arg => undef,
172             );
173              
174             has _act_sub_str => (
175                 is => 'rw',
176                 isa => 'Str',
177                 init_arg => undef,
178             );
179              
180              
181             has dont_record => (
182                 is => 'rw',
183                 isa => 'Bool',
184                 default => 0,
185                 lazy => 1,
186                 trigger => \&_check_continue_to_next,
187             );
188              
189             sub _check_continue_to_next {
190 15     15   29     my $self = shift;
191 15 100       363     return if not $self->continue_to_next;
192 5 100       118     die illegal_rule_cont if not $self->dont_record;
193             }
194              
195              
196             has continue_to_next => (
197                 is => 'rw',
198                 isa => 'Bool',
199                 default => 0,
200                 lazy => 1,
201                 trigger => \&_check_continue_to_next,
202             );
203              
204              
205             sub BUILD {
206 33     33 0 61     my $self = shift;
207 33 100 100     867     die illegal_rule_no_if_no_act
208                     if not $self->_has_condition and not $self->_has_action;
209 32 100       772     $self->action('return $0;') if not $self->_has_action;
210 32 100       823     $self->_constr_condition if not $self->_has_condition;
211             }
212              
213             sub _constr_condition {
214 13     13   29     my $self = shift;
215 13         292     $self->condition(1);
216 13         287     $self->_has_blank_condition(1);
217             }
218              
219              
220             has _preconditions => (
221                 is => 'ro',
222                 isa => 'ArrayRef[Str]',
223                 init_arg => undef,
224                 default => sub { [] },
225                 lazy => 1,
226                 traits => ['Array'],
227                 handles => {
228                     _preconds => 'elements',
229                     add_precondition => 'push',
230                     _join_preconds => 'join',
231                     _get_precond => 'get',
232                     _no_preconds => 'is_empty',
233                 },
234             );
235              
236             after add_precondition => sub {
237                 my $self = shift;
238                 $self->_set_highest_nf;
239                 my $str = $self->_get_precond(-1);
240                 my $substr = _gen_sub_str($str);
241                 $self->_add_precond_substr($substr);
242                 $self->_add_precond_sub( _set_cond_sub( $str, $substr ) );
243             };
244              
245             has _precondition_substrs => (
246                 is => 'ro',
247                 isa => 'ArrayRef[Str]',
248                 init_arg => undef,
249                 default => sub { [] },
250                 lazy => 1,
251                 traits => ['Array'],
252                 handles => {
253                     _precond_substrs => 'elements',
254                     _add_precond_substr => 'push',
255                 }
256             );
257              
258             has _precond_subroutines => (
259                 is => 'ro',
260                 isa => 'ArrayRef[CodeRef]',
261                 init_arg => undef,
262                 default => sub { [] },
263                 lazy => 1,
264                 traits => ['Array'],
265                 handles => {
266                     _precond_subs => 'elements',
267                     _add_precond_sub => 'push',
268                 }
269             );
270              
271              
272             sub test {
273 34     34 1 2279     my $self = shift;
274 34 100       62     return 0 if not _check_parser_arg(@_);
275 31         49     my $parser = shift;
276 31 100       671     return 0 if not $parser->auto_split;
277 29         61     return $self->_test($parser);
278             }
279              
280             sub _check_parser_arg {
281 43 100   43   82     return 0 if not @_;
282 40         53     my $parser = shift;
283 40 100       111     return 0 if not defined blessed($parser);
284 39         138     $parser->isa('Text::Parser');
285             }
286              
287             sub _test {
288 306     306   461     my ( $self, $parser ) = ( shift, shift );
289 306 100       8894     return 0 if $parser->NF < $self->min_nf;
290 245 100 100     6864     return 0
291                     if not( $self->_no_preconds or $self->_test_preconditions($parser) );
292 239 100       5353     return 1 if $self->_has_blank_condition;
293 191         397     return $self->_test_cond_sub($parser);
294             }
295              
296             sub _test_preconditions {
297 7     7   13     my ( $self, $parser ) = @_;
298 7         233     foreach my $cond ( $self->_precond_subs ) {
299 7         105         my $val = $cond->($parser);
300 7 100 100     44         return 0 if not defined $val or not $val;
301                 }
302 1         10     return 1;
303             }
304              
305             sub _test_cond_sub {
306 191     191   325     my ( $self, $parser ) = @_;
307 191         3955     my $cond = $self->_cond_sub;
308 191 100       4178     return 0 if not defined $parser->this_line;
309 190         2810     my $val = $cond->($parser);
310 190 100       1137     defined $val and $val;
311             }
312              
313              
314             sub run {
315 9     9 1 84     my $self = shift;
316 9 100       20     die rule_run_improperly if not _check_parser_arg(@_);
317 8 100       182     return if not $_[0]->auto_split;
318 7 100       23     push @_, 1 if @_ < 2;
319 7         14     $self->_run(@_);
320             }
321              
322             sub _run {
323 108     108   192     my ( $self, $parser ) = ( shift, shift );
324 108 100       2362     return if nocontent( $self->action );
325 96         1033     my (@res) = $self->_call_act_sub( $parser, @_ );
326 96 100       2137     return if $self->dont_record;
327 80         2160     $parser->push_records(@res);
328             }
329              
330             sub _call_act_sub {
331 96     96   178     my ( $self, $parser, $test_line ) = @_;
332 96 100 100     344     return if $test_line and not defined $parser->this_line;
333 94         1993     my $act = $self->_act_sub;
334 94         1402     return ( $act->($parser) );
335             }
336              
337             __PACKAGE__->meta->make_immutable;
338              
339 30     30   252 no Moose;
  30         62  
  30         191  
340              
341              
342             1;
343              
344             __END__
345            
346             =pod
347            
348             =encoding UTF-8
349            
350             =head1 NAME
351            
352             Text::Parser::Rule - Makes it possible to write AWK-style parsing rules for Text::Parser
353            
354             =head1 VERSION
355            
356             version 0.927
357            
358             =head1 SYNOPSIS
359            
360             Users should not use this class directly to create and run rules. See L<Text::Parser::Manual::ExtendedAWKSyntax> for instructions on creating rules in a class. But the example below shows the way this class works for those that intend to improve the class.
361            
362             use Text::Parser::Rule;
363             use Text::Parser; # To demonstrate use with Text::Parser
364             use Data::Dumper 'Dumper'; # To print any records
365            
366             my $rule = Text::Parser::Rule->new( if => '$1 eq "NAME:"', do => '${2+}' );
367            
368             # Must have auto_split attribute set - this is automatically done by
369             # the add_rule method of Text::Parser
370             my $parser = Text::Parser->new(auto_split => 1);
371            
372             # Example of how internally the $parser would run the $rule
373             # This code below won't really run any rules because rules
374             # have to be applied when the $parser->read() method is called
375             # and not outside of that
376             $rule->run($parser) if $rule->test($parser);
377             print "Continuing to next rule..." if $rule->continue_to_next;
378            
379             =head1 CONSTRUCTOR
380            
381             =head2 new
382            
383             Takes optional attributes described in L<ATTRIBUTES|/ATTRIBUTES> section.
384            
385             my $rule = Text::Parser::Rule->new(
386             condition => '$1 eq "NAME:"', # Some condition string
387             action => 'return $2;', # Some action to do when condition is met
388             dont_record => 1, # Directive to not record
389             continue_to_next => 1, # Directive to next rule till another rule
390             # passes test condition
391             );
392            
393             =head1 ATTRIBUTES
394            
395             The attributes below may be used as options to C<new> constructor. Note that in some cases, the accessor method for the attribute is differently named. Use the attribute name in the constructor and accessor as a method.
396            
397             =head2 condition
398            
399             Read-write attribute. Set in the constructor with C<if> key. Must be string which after transformation must C<eval> successfully without compilation errors.
400            
401             my $rule = Text::Parser::Rule->new( if => 'm//' );
402             print $rule->action, "\n"; # m//
403             $rule->action('m/something/');
404             print $rule->action, "\n"; @ m/something/
405            
406             During a call to C<L<test|/test>> method, this C<condition> is C<eval>uated and the result is returned as a boolean for further decision-making.
407            
408             =head2 min_nf
409            
410             Read-only attribute. Gets adjusted automatically.
411            
412             print "Rule requires a minimum of ", $rule->min_nf, " fields on the line.\n";
413            
414             =head2 action
415            
416             Read-write attribute. Set in the constructor with C<do> key. Must be string which after transformation must C<eval> successfully without compilation errors.
417            
418             my $rule = Text::Parser->new( do => '' );
419             print $rule->action, "\n"; # :nothing:
420             $rule->action('return $1');
421             print $rule->action, "\n"; # return $1
422            
423             The C<L<action|/action>> is executed during a call to C<run> when C<condition> (and all preconditions) is true. The return value of the C<eval>uated C<action> is used or discarded based on the C<dont_record> attribute.
424            
425             =head2 dont_record
426            
427             Boolean indicating if return value of the C<action> (when transformed and C<eval>uated) should be stored in the parser as a record.
428            
429             print "Will not save records\n" if $rule->dont_record;
430            
431             The attribute is used in C<L<run|/run>> method. The results of the C<eval>uated C<action> are recorded in the object passed to C<run>. But when this attribute is set to true, then results are not recorded.
432            
433             =head2 continue_to_next
434            
435             Takes a boolean value. This can be set true only for rules with C<dont_record> attribute set to a true value. This attribute indicates that the rule will proceed to the next rule until some rule passes the C<L<test|/test>>. It is easiest to understand the use of this if you imagine a series of rules to test and execute in sequence:
436            
437             # This code is actually used in Text::Parser
438             # to run through the rules specified
439             foreach my $rule (@rules) {
440             next if not $rule->test($parser);
441             $rule->run($parser);
442             last if not $rule->continue_to_next;
443             }
444            
445             =head1 METHODS
446            
447             =head2 add_precondition
448            
449             Takes a list of rule strings that are similar to the C<condition> string. For example:
450            
451             $rule->add_precondition(
452             '$2 !~ /^ln/',
453             'looks_like_number($3)',
454             );
455            
456             During the call to C<L<test|/test>>, these preconditions and the C<condition> will all be combined in the C<and> operation. That means, all the preconditions must be satisfied, and then the C<condition> must be satisfied. If any of them C<eval>uates to a false boolean, C<test> will return false.
457            
458             =head2 test
459            
460             Takes one argument that must be a C<Text::Parser>. Returns a boolean value may be used to decide to call the C<run> method.
461            
462             If all preconditions and C<condition> C<eval>uate to a boolean true, then C<test> returns true.
463            
464             my $parser = Text::Parser->new(auto_split => 1);
465             $rule->test($parser);
466            
467             The method will always return a boolean false if the C<Text::Parser> object passed does not have the C<auto_split> attribute on.
468            
469             =head2 run
470            
471             Takes one argument that must be a C<Text::Parser>, and one optional argument which can be C<0> or C<1>. The default for this optional argument is C<1>. The C<0> value is used when calling a special kind of rule that doesn't need to check for valid current line (mainly useful for C<BEGIN> and C<END> rules). Has no return value.
472            
473             my $parser = Text::Parser->new(auto_split => 1);
474             $rule->run($parser);
475             $rule->run($parser, 'no_line');
476            
477             Runs the C<eval>uated C<action>. If C<dont_record> is false, the return value of the C<action> is recorded in C<$parser>. Otherwise, it is ignored.
478            
479             =head1 SEE ALSO
480            
481             =over 4
482            
483             =item *
484            
485             L<Text::Parser>
486            
487             =item *
488            
489             L<"The AWK Programming Language"|https://books.google.com/books?id=53ueQgAACAAJ&dq=The+AWK+Programming+Language&hl=en&sa=X&ei=LXxXVfq0GMOSsAWrpoC4Bg&ved=0CCYQ6AEwAA> by Alfred V. Aho, Brian W. Kernighan, and Peter J. Weinberger, Addison-Wesley, 1988. ISBN 0-201-07981-X
490            
491             =back
492            
493             =head1 BUGS
494            
495             Please report any bugs or feature requests on the bugtracker website
496             L<http://github.com/balajirama/Text-Parser/issues>
497            
498             When submitting a bug or request, please include a test-file or a
499             patch to an existing test-file that illustrates the bug or desired
500             feature.
501            
502             =head1 AUTHOR
503            
504             Balaji Ramasubramanian <balajiram@cpan.org>
505            
506             =head1 COPYRIGHT AND LICENSE
507            
508             This software is copyright (c) 2018-2019 by Balaji Ramasubramanian.
509            
510             This is free software; you can redistribute it and/or modify it under
511             the same terms as the Perl 5 programming language system itself.
512            
513             =cut
514