File Coverage

blib/lib/Text/Parser/Rule.pm
Criterion Covered Total %
statement 113 113 100.0
branch 50 50 100.0
condition 15 15 100.0
subroutine 28 28 100.0
pod 2 3 66.6
total 208 209 99.5


line stmt bran cond sub pod time code
1 30     30   474718 use strict;
  30         236  
  30         1107  
2 30     30   159 use warnings;
  30         65  
  30         1314  
3              
4             package Text::Parser::Rule 0.926;
5              
6             # ABSTRACT: Makes it possible to write AWK-style parsing rules for Text::Parser
7              
8 30     30   638 use Moose;
  30         414207  
  30         251  
9 30     30   200178 use Text::Parser::Errors;
  30         73  
  30         3830  
10 30     30   223 use Scalar::Util 'blessed', 'looks_like_number';
  30         60  
  30         1582  
11 30     30   1331 use String::Util ':all';
  30         6413  
  30         66118  
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   70     my $self = shift;
24 32         85     $self->_set_highest_nf;
25 32         772     $self->_cond_sub_str( _gen_sub_str( $self->condition ) );
26 32         793     $self->_cond_sub(
27                     _set_cond_sub( $self->condition, $self->_cond_sub_str ) );
28             }
29              
30             sub _get_min_req_fields {
31 74     74   132     my $str = shift;
32                 my @indx
33 74         1492         = $str =~ /\$([0-9]+)|\$[{]([-][0-9]+)[}]|[$@][{]([-]?[0-9]+)[+][}]/g;
34 74         208     my @inds = sort { $b <=> $a } ( grep { defined $_ } @indx );
  59         195  
  294         541  
35 74 100       201     return 0 if not @inds;
36 46 100       191     ( $inds[0] >= -$inds[-1] ) ? $inds[0] : -$inds[-1];
37             }
38              
39             my $SUB_BEGIN = 'sub {
40             my $this = shift;
41             my $__ = $this->_ExAWK_symbol_table;
42             local $_ = $this->this_line;
43             ';
44              
45             my $SUB_END = '
46             }';
47              
48             sub _gen_sub_str {
49 74     74   121     my $str = shift;
50 74         175     my $anon = $SUB_BEGIN . _replace_awk_vars($str) . $SUB_END;
51 74         1841     return $anon;
52             }
53              
54             sub _replace_awk_vars {
55 74     74   136     local $_ = shift;
56 74         182     _replace_positional_indicators();
57 74         180     _replace_range_shortcut();
58 74 100       191     _replace_exawk_vars() if m/[~][a-z_][a-z0-9_]+/i;
59 74         249     return $_;
60             }
61              
62             sub _replace_positional_indicators {
63 74     74   160     s/\$0/\$this->this_line/g;
64 74         118     s/\$[{]([-][0-9]+)[}]/\$this->field($1)/g;
65 74         331     s/\$([0-9]+)/\$this->field($1-1)/g;
66             }
67              
68             sub _replace_range_shortcut {
69 74     74   138     s/\$[{]([-][0-9]+)[+][}]/\$this->join_range($1)/g;
70 74         129     s/\$[{]([0-9]+)[+][}]/\$this->join_range($1-1)/g;
71 74         125     s/\\\@[{]([-][0-9]+)[+][}]/[\$this->field_range($1)]/g;
72 74         105     s/\\\@[{]([0-9]+)[+][}]/[\$this->field_range($1-1)]/g;
73 74         108     s/\@[{]([-][0-9]+)[+][}]/\$this->field_range($1)/g;
74 74         108     s/\@[{]([0-9]+)[+][}]/\$this->field_range($1-1)/g;
75             }
76              
77             sub _replace_exawk_vars {
78 7     7   34     my (@varnames) = _uniq( $_ =~ /[~]([a-z_][a-z0-9_]+)/ig );
79 7         20     foreach my $var (@varnames) {
80 10         22         my $v = '~' . $var;
81 10         132         s/$v/\$__->{$var}/g;
82                 }
83             }
84              
85             sub _uniq {
86 7     7   16     my (%elem) = map { $_ => 1 } @_;
  10         35  
87 7         31     return ( keys %elem );
88             }
89              
90             has _cond_sub_str => (
91                 is => 'rw',
92                 isa => 'Str',
93                 init_arg => undef,
94             );
95              
96             sub _set_cond_sub {
97 74     74   153     my ( $rstr, $sub_str ) = @_;
98 74         7627     my $sub = eval $sub_str;
99 74 100       245     _throw_bad_cond( $rstr, $sub_str, $@ ) if not defined $sub;
100 73         2036     return $sub;
101             }
102              
103             sub _throw_bad_cond {
104 1     1   3     my ( $code, $sub_str, $msg ) = @_;
105 1         6     die bad_rule_syntax(
106                     code => $code,
107                     msg => $msg,
108                     subroutine => $sub_str,
109                 );
110             }
111              
112             has _cond_sub => (
113                 is => 'rw',
114                 isa => 'CodeRef',
115                 init_arg => undef,
116             );
117              
118              
119             has min_nf => (
120                 is => 'ro',
121                 isa => 'Num',
122                 traits => ['Number'],
123                 init_arg => undef,
124                 default => 0,
125                 lazy => 1,
126                 handles => { _set_min_nf => 'set', }
127             );
128              
129             sub _set_highest_nf {
130 74     74   315     my $self = shift;
131 74         154     my $nf = _get_min_req_fields( $self->_gen_joined_str );
132 74         2415     $self->_set_min_nf($nf);
133             }
134              
135             sub _gen_joined_str {
136 74     74   108     my $self = shift;
137 74         112     my (@strs) = ();
138 74 100       2180     push @strs, $self->condition if $self->_has_condition;
139 74 100       2086     push @strs, $self->action if $self->_has_action;
140 74 100       2424     push @strs, $self->_join_preconds('; ') if not $self->_no_preconds;
141 74         351     my $str = join '; ', @strs;
142             }
143              
144              
145             has action => (
146                 is => 'rw',
147                 isa => 'Str',
148                 init_arg => 'do',
149                 predicate => '_has_action',
150                 trigger => \&_set_action,
151             );
152              
153             sub _set_action {
154 36     36   74     my $self = shift;
155 36         119     $self->_set_highest_nf;
156 36         884     $self->_act_sub_str( _gen_sub_str( $self->action ) );
157 36         851     $self->_act_sub( _set_cond_sub( $self->action, $self->_act_sub_str ) );
158             }
159              
160             has _act_sub => (
161                 is => 'rw',
162                 isa => 'CodeRef',
163                 init_arg => undef,
164             );
165              
166             has _act_sub_str => (
167                 is => 'rw',
168                 isa => 'Str',
169                 init_arg => undef,
170             );
171              
172              
173             has dont_record => (
174                 is => 'rw',
175                 isa => 'Bool',
176                 default => 0,
177                 lazy => 1,
178                 trigger => \&_check_continue_to_next,
179             );
180              
181             sub _check_continue_to_next {
182 15     15   37     my $self = shift;
183 15 100       422     return if not $self->continue_to_next;
184 5 100       127     die illegal_rule_cont if not $self->dont_record;
185             }
186              
187              
188             has continue_to_next => (
189                 is => 'rw',
190                 isa => 'Bool',
191                 default => 0,
192                 lazy => 1,
193                 trigger => \&_check_continue_to_next,
194             );
195              
196              
197             sub BUILD {
198 33     33 0 69     my $self = shift;
199 33 100 100     896     die illegal_rule_no_if_no_act
200                     if not $self->_has_condition and not $self->_has_action;
201 32 100       835     $self->action('return $0;') if not $self->_has_action;
202 32 100       838     $self->condition(1) if not $self->_has_condition;
203             }
204              
205              
206             has _preconditions => (
207                 is => 'ro',
208                 isa => 'ArrayRef[Str]',
209                 init_arg => undef,
210                 default => sub { [] },
211                 lazy => 1,
212                 traits => ['Array'],
213                 handles => {
214                     _preconds => 'elements',
215                     add_precondition => 'push',
216                     _join_preconds => 'join',
217                     _get_precond => 'get',
218                     _no_preconds => 'is_empty',
219                 },
220             );
221              
222             after add_precondition => sub {
223                 my $self = shift;
224                 $self->_set_highest_nf;
225                 my $str = $self->_get_precond(-1);
226                 my $substr = _gen_sub_str($str);
227                 $self->_add_precond_substr($substr);
228                 $self->_add_precond_sub( _set_cond_sub( $str, $substr ) );
229             };
230              
231             has _precondition_substrs => (
232                 is => 'ro',
233                 isa => 'ArrayRef[Str]',
234                 init_arg => undef,
235                 default => sub { [] },
236                 lazy => 1,
237                 traits => ['Array'],
238                 handles => {
239                     _precond_substrs => 'elements',
240                     _add_precond_substr => 'push',
241                 }
242             );
243              
244             has _precond_subroutines => (
245                 is => 'ro',
246                 isa => 'ArrayRef[CodeRef]',
247                 init_arg => undef,
248                 default => sub { [] },
249                 lazy => 1,
250                 traits => ['Array'],
251                 handles => {
252                     _precond_subs => 'elements',
253                     _add_precond_sub => 'push',
254                 }
255             );
256              
257              
258             sub test {
259 311     311 1 3036     my $self = shift;
260 311 100       580     return 0 if not _check_parser_arg(@_);
261 308         513     my $parser = shift;
262 308 100 100     815     return 0 if not $parser->auto_split or $parser->NF < $self->min_nf;
263 245 100       634     return 0 if not $self->_test_preconditions($parser);
264 239         516     return $self->_test_cond_sub($parser);
265             }
266              
267             sub _check_parser_arg {
268 421 100   421   804     return 0 if not @_;
269 418         583     my $parser = shift;
270 418 100       1349     return 0 if not defined blessed($parser);
271 417         1500     $parser->isa('Text::Parser');
272             }
273              
274             sub _test_preconditions {
275 245     245   436     my ( $self, $parser ) = @_;
276 245         8360     foreach my $cond ( $self->_precond_subs ) {
277 7         129         my $val = $cond->($parser);
278 7 100 100     45         return 0 if not defined $val or not $val;
279                 }
280 239         511     return 1;
281             }
282              
283             sub _test_cond_sub {
284 239     239   444     my ( $self, $parser ) = @_;
285 239         5738     my $cond = $self->_cond_sub;
286 239 100       6202     return 0 if not defined $parser->this_line;
287 238         4009     my $val = $cond->($parser);
288 238 100       1220     defined $val and $val;
289             }
290              
291              
292             sub run {
293 110     110 1 254     my $self = shift;
294 110 100       226     die rule_run_improperly if not _check_parser_arg(@_);
295 109 100 100     2762     return if nocontent( $self->action ) or not $_[0]->auto_split;
296 96 100       317     push @_, 1 if @_ == 1;
297 96         263     my (@res) = $self->_call_act_sub(@_);
298 96 100       2426     return if $self->dont_record;
299 80         2517     $_[0]->push_records(@res);
300             }
301              
302             sub _call_act_sub {
303 96     96   183     my ( $self, $parser, $test_line ) = @_;
304 96 100 100     2596     return if $test_line and not defined $parser->this_line;
305 94         2239     my $act = $self->_act_sub;
306 94         1622     return ( $act->($parser) );
307             }
308              
309             __PACKAGE__->meta->make_immutable;
310              
311 30     30   256 no Moose;
  30         69  
  30         206  
312              
313              
314             1;
315              
316             __END__
317            
318             =pod
319            
320             =encoding UTF-8
321            
322             =head1 NAME
323            
324             Text::Parser::Rule - Makes it possible to write AWK-style parsing rules for Text::Parser
325            
326             =head1 VERSION
327            
328             version 0.926
329            
330             =head1 SYNOPSIS
331            
332             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.
333            
334             use Text::Parser::Rule;
335             use Text::Parser; # To demonstrate use with Text::Parser
336             use Data::Dumper 'Dumper'; # To print any records
337            
338             my $rule = Text::Parser::Rule->new( if => '$1 eq "NAME:"', do => '${2+}' );
339            
340             # Must have auto_split attribute set - this is automatically done by
341             # the add_rule method of Text::Parser
342             my $parser = Text::Parser->new(auto_split => 1);
343            
344             # Example of how internally the $parser would run the $rule
345             # This code below won't really run any rules because rules
346             # have to be applied when the $parser->read() method is called
347             # and not outside of that
348             $rule->run($parser) if $rule->test($parser);
349             print "Continuing to next rule..." if $rule->continue_to_next;
350            
351             =head1 CONSTRUCTOR
352            
353             =head2 new
354            
355             Takes optional attributes described in L<ATTRIBUTES|/ATTRIBUTES> section.
356            
357             my $rule = Text::Parser::Rule->new(
358             condition => '$1 eq "NAME:"', # Some condition string
359             action => 'return $2;', # Some action to do when condition is met
360             dont_record => 1, # Directive to not record
361             continue_to_next => 1, # Directive to next rule till another rule
362             # passes test condition
363             );
364            
365             =head1 ATTRIBUTES
366            
367             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.
368            
369             =head2 condition
370            
371             Read-write attribute. Set in the constructor with C<if> key. Must be string which after transformation must C<eval> successfully without compilation errors.
372            
373             my $rule = Text::Parser::Rule->new( if => 'm//' );
374             print $rule->action, "\n"; # m//
375             $rule->action('m/something/');
376             print $rule->action, "\n"; @ m/something/
377            
378             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.
379            
380             =head2 min_nf
381            
382             Read-only attribute. Gets adjusted automatically.
383            
384             print "Rule requires a minimum of ", $rule->min_nf, " fields on the line.\n";
385            
386             =head2 action
387            
388             Read-write attribute. Set in the constructor with C<do> key. Must be string which after transformation must C<eval> successfully without compilation errors.
389            
390             my $rule = Text::Parser->new( do => '' );
391             print $rule->action, "\n"; # :nothing:
392             $rule->action('return $1');
393             print $rule->action, "\n"; # return $1
394            
395             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.
396            
397             =head2 dont_record
398            
399             Boolean indicating if return value of the C<action> (when transformed and C<eval>uated) should be stored in the parser as a record.
400            
401             print "Will not save records\n" if $rule->dont_record;
402            
403             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.
404            
405             =head2 continue_to_next
406            
407             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:
408            
409             # This code is actually used in Text::Parser
410             # to run through the rules specified
411             foreach my $rule (@rules) {
412             next if not $rule->test($parser);
413             $rule->run($parser);
414             last if not $rule->continue_to_next;
415             }
416            
417             =head1 METHODS
418            
419             =head2 add_precondition
420            
421             Takes a list of rule strings that are similar to the C<condition> string. For example:
422            
423             $rule->add_precondition(
424             '$2 !~ /^ln/',
425             'looks_like_number($3)',
426             );
427            
428             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.
429            
430             =head2 test
431            
432             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.
433            
434             If all preconditions and C<condition> C<eval>uate to a boolean true, then C<test> returns true.
435            
436             my $parser = Text::Parser->new(auto_split => 1);
437             $rule->test($parser);
438            
439             The method will always return a boolean false if the C<Text::Parser> object passed does not have the C<auto_split> attribute on.
440            
441             =head2 run
442            
443             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.
444            
445             my $parser = Text::Parser->new(auto_split => 1);
446             $rule->run($parser);
447             $rule->run($parser, 'no_line');
448            
449             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.
450            
451             =head1 SEE ALSO
452            
453             =over 4
454            
455             =item *
456            
457             L<Text::Parser>
458            
459             =item *
460            
461             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
462            
463             =back
464            
465             =head1 BUGS
466            
467             Please report any bugs or feature requests on the bugtracker website
468             L<http://github.com/balajirama/Text-Parser/issues>
469            
470             When submitting a bug or request, please include a test-file or a
471             patch to an existing test-file that illustrates the bug or desired
472             feature.
473            
474             =head1 AUTHOR
475            
476             Balaji Ramasubramanian <balajiram@cpan.org>
477            
478             =head1 COPYRIGHT AND LICENSE
479            
480             This software is copyright (c) 2018-2019 by Balaji Ramasubramanian.
481            
482             This is free software; you can redistribute it and/or modify it under
483             the same terms as the Perl 5 programming language system itself.
484            
485             =cut
486