File Coverage

blib/lib/Text/Parser/RuleSpec.pm
Criterion Covered Total %
statement 216 216 100.0
branch 82 82 100.0
condition 27 27 100.0
subroutine 60 60 100.0
pod 8 8 100.0
total 393 393 100.0


line stmt bran cond sub pod time code
1             ## Please see file perltidy.ERR
2 42     42   1260101 use strict;
  42         142  
  42         1591  
3 42     42   258 use warnings;
  42         104  
  42         2299  
4              
5             package Text::Parser::RuleSpec 1.000;
6              
7             # ABSTRACT: Syntax sugar for rule specification while subclassing Text::Parser or derivatives
8              
9              
10 42     42   2624 use Moose;
  42         1918546  
  42         373  
11 42     42   307668 use Moose::Exporter;
  42         145  
  42         647  
12 42     42   25020 use MooseX::ClassAttribute;
  42         3510378  
  42         195  
13 42     42   11048875 use Text::Parser::Error;
  42         137  
  42         463  
14 42     42   32357 use Text::Parser::Rule;
  42         144  
  42         2018  
15 42     42   33024 use List::MoreUtils qw(before_incl after_incl);
  42         598116  
  42         319  
16              
17             Moose::Exporter->setup_import_methods(
18                 with_meta => [
19                     'applies_rule', 'unwraps_lines_using',
20                     'disables_superclass_rules', 'applies_cloned_rule',
21                 ],
22                 as_is => ['_check_custom_unwrap_args'],
23                 also => 'Moose'
24             );
25              
26             class_has _all_rules => (
27                 is => 'rw',
28                 isa => 'HashRef[Text::Parser::Rule]',
29                 lazy => 1,
30                 default => sub { {} },
31                 traits => ['Hash'],
32                 handles => {
33                     _add_new_rule => 'set',
34                     is_known_rule => 'exists',
35                     class_rule_object => 'get',
36                 },
37             );
38              
39             class_has _class_rule_order => (
40                 is => 'rw',
41                 isa => 'HashRef[ArrayRef[Str]]',
42                 lazy => 1,
43                 default => sub { {} },
44                 traits => ['Hash'],
45                 handles => {
46                     _class_has_rules => 'exists',
47                     __cls_rule_order => 'get',
48                     _set_class_rule_order => 'set',
49                 }
50             );
51              
52             class_has _class_rules_in_order => (
53                 is => 'rw',
54                 isa => 'HashRef[ArrayRef[Text::Parser::Rule]]',
55                 lazy => 1,
56                 default => sub { {} },
57                 traits => ['Hash'],
58                 handles => {
59                     _class_rules => 'get',
60                     _set_rules_of_class => 'set',
61                 },
62             );
63              
64              
65             sub class_has_rules {
66 724     724 1 1585     my ( $this_cls, $cls ) = ( shift, shift );
67 724 100       1540     return 0 if not defined $cls;
68 722 100       26327     return 0 if not $this_cls->_class_has_rules($cls);
69 133         336     return $this_cls->class_rule_order($cls);
70             }
71              
72              
73             sub class_rule_order {
74 235     235 1 2191     my ( $class, $cls ) = @_;
75 235 100       560     return () if not defined $cls;
76 234 100       8860     $class->_class_has_rules($cls) ? @{ $class->__cls_rule_order($cls) } : ();
  218         8295  
77             }
78              
79              
80             sub class_rules {
81 267     267 1 596     my ( $class, $cls ) = @_;
82 267 100       552     return () if not $class->class_has_rules($cls);
83 66         101     @{ $class->_class_rules($cls) };
  66         2426  
84             }
85              
86              
87              
88             sub populate_class_rules {
89 30     30 1 184     my ( $class, $cls ) = @_;
90 30 100 100     1226     return if not defined $cls or not $class->_class_has_rules($cls);
91 28         100     my @ord = $class->class_rule_order($cls);
92                 $class->_set_rules_of_class(
93 28         114         $cls => [ map { $class->class_rule_object($_) } @ord ] );
  65         2306  
94             }
95              
96              
97             sub applies_rule {
98 39     39 1 211410     my ( $meta, $name ) = ( shift, shift );
99 39         171     _first_things_on_applies_rule( $meta, $name, @_ );
100 25         3705     _register_rule( _full_rule_name( $meta, $name ), @_ );
101 23         104     _set_correct_rule_order( $meta, $name, @_ );
102             }
103              
104             sub _first_things_on_applies_rule {
105 39     39   105     my ( $meta, $name ) = ( shift, shift );
106 39         140     _excepts_apply_rule( $meta, $name, @_ );
107 25         95     _set_default_of_attributes( $meta, auto_split => 1 );
108             }
109              
110             sub _full_rule_name {
111 52     52   117     my ( $meta, $name ) = ( shift, shift );
112 52         299     return $meta->name . '/' . $name;
113             }
114              
115             sub _excepts_apply_rule {
116 39     39   86     my ( $meta, $name ) = ( shift, shift );
117 39         116     _rulespec_cant_be_in_main( $meta, $name, 'applies_rule' );
118 37         117     _rule_must_have_name( $meta, $name );
119 34         124     _check_args_hash_stuff( $meta, "applies_rule $name", @_ );
120             }
121              
122             sub _rulespec_cant_be_in_main {
123 54     54   137     my ( $meta, $name, $funcname ) = ( shift, shift, shift );
124 54 100       201     my $follow = defined $name ? ": $name" : '.';
125 54 100       322     parser_exception("$funcname cannot be called in main$follow")
126                     if $meta->name eq 'main';
127             }
128              
129             my %rule_options = (
130                 if => 1,
131                 do => 1,
132                 dont_record => 1,
133                 continue_to_next => 1,
134                 before => 1,
135                 after => 1,
136             );
137              
138             sub _rule_must_have_name {
139 37     37   79     my ( $meta, $name ) = ( shift, shift );
140                 parser_exception("applies_rule requires rule name argument")
141                     if not defined $name
142                     or ( '' ne ref($name) )
143 37 100 100     317         or ( exists $rule_options{$name} );
      100        
144             }
145              
146             sub _check_args_hash_stuff {
147 37     37   83     my ( $meta, $funccall ) = ( shift, shift );
148 37         108     my (%opt) = _check_arg_is_hash( $funccall, @_ );
149 34         124     _if_empty_prepopulate_rules_from_superclass($meta);
150 34 100       136     _check_location_args( $meta, $funccall, %opt )
151                     if _has_location_opts(%opt);
152             }
153              
154             sub _has_location_opts {
155 59     59   184     my (%opt) = @_;
156 59 100       398     exists $opt{before} or exists $opt{after};
157             }
158              
159             sub _check_arg_is_hash {
160 37     37   550     my $funccall = shift;
161 37 100 100     217     parser_exception(
162                     "$funccall must be followed by a hash. See documentation.")
163                     if not @_
164                     or ( scalar(@_) % 2 );
165 34         165     return @_;
166             }
167              
168             sub _check_location_args {
169 9     9   31     my ( $meta, $name, %opt ) = ( shift, shift, @_ );
170                 parser_exception(
171                     "\'$name\' call can have \'before\' or \'after\'; not both.")
172 9 100 100     49         if exists $opt{before} and exists $opt{after};
173 8 100       22     my $loc = exists $opt{before} ? 'before' : 'after';
174 8         34     my ( $cls, $rule ) = split /\//, $opt{$loc}, 2;
175 8 100       31     parser_exception(
176                     "Clause $loc must follow format <classname>/<rulename>: \'$name\'")
177                     if not defined $rule;
178                 parser_exception("Unknown rule $opt{$loc} in clause $loc: \'$name\'")
179 7 100       272         if not Text::Parser::RuleSpec->is_known_rule( $opt{$loc} );
180 5         21     my (@r) = Text::Parser::RuleSpec->class_rule_order( $meta->name );
181 5         12     my $is_super_rule = grep { $_ eq $opt{$loc} } @r;
  14         35  
182 5 100 100     44     parser_exception(
183                     "Use \'$loc\' clause only with superclass rules ; not this class: \'$name\'"
184                 ) if $cls eq $meta->name or not $is_super_rule;
185             }
186              
187             sub _register_rule {
188 25     25   63     my $key = shift;
189 25 100       1051     parser_exception("name rules uniquely: $key")
190                     if Text::Parser::RuleSpec->is_known_rule($key);
191 24         87     my %opts = _get_rule_opts_only(@_);
192 24         766     my $rule = Text::Parser::Rule->new(%opts);
193 23         865     Text::Parser::RuleSpec->_add_new_rule( $key => $rule );
194             }
195              
196             sub _get_rule_opts_only {
197 26     26   86     my (%opt) = @_;
198 26 100       85     delete $opt{before} if exists $opt{before};
199 26 100       69     delete $opt{after} if exists $opt{after};
200 26         103     return (%opt);
201             }
202              
203             sub _set_default_of_attributes {
204 30     30   100     my ( $meta, %val ) = @_;
205 30         148     while ( my ( $k, $v ) = ( each %val ) ) {
206 30 100       206         _inherit_set_default_mk_ro( $meta, $k, $v )
207                         if not defined $meta->get_attribute($k);
208                 }
209             }
210              
211             sub _inherit_set_default_mk_ro {
212 16     16   213     my ( $meta, $attr, $def ) = ( shift, shift, shift );
213 16         85     my $old = $meta->find_attribute_by_name($attr);
214 16         1131     my $new = $old->clone_and_inherit_options( default => $def, is => 'ro' );
215 16         34200     $meta->add_attribute($new);
216             }
217              
218             sub _set_correct_rule_order {
219 25     25   62     my ( $meta, $rule_name ) = ( shift, shift );
220 25         64     my $rname = _full_rule_name( $meta, $rule_name );
221 25 100       72     return _push_to_class_rules( $meta->name, $rname )
222                     if not _has_location_opts(@_);
223 2         21     _insert_rule_in_order( $meta->name, $rname, @_ );
224             }
225              
226             my %INSERT_RULE_FUNC = (
227                 before => \&_ins_before_rule,
228                 after => \&_ins_after_rule,
229             );
230              
231             sub _insert_rule_in_order {
232 2     2   8     my ( $cls, $rname, %opt ) = ( shift, shift, @_ );
233 2 100       7     my $loc = exists $opt{before} ? 'before' : 'after';
234 2         12     $INSERT_RULE_FUNC{$loc}->( $cls, $opt{$loc}, $rname );
235 2         8     Text::Parser::RuleSpec->populate_class_rules($cls);
236             }
237              
238             sub _ins_before_rule {
239 1     1   5     my ( $cls, $before, $rname ) = ( shift, shift, shift );
240 1         4     my (@ord) = Text::Parser::RuleSpec->class_rule_order($cls);
241 1     1   35     my (@ord1) = before_incl { $_ eq $before } @ord;
  1         23  
242 1     1   13     my (@ord2) = after_incl { $_ eq $before } @ord;
  1         4  
243 1         3     pop @ord1;
244 1         48     Text::Parser::RuleSpec->_set_class_rule_order(
245                     $cls => [ @ord1, $rname, @ord2 ] );
246             }
247              
248             sub _ins_after_rule {
249 1     1   6     my ( $cls, $after, $rname ) = ( shift, shift, shift );
250 1         4     my (@ord) = Text::Parser::RuleSpec->class_rule_order($cls);
251 1     2   14     my (@ord1) = before_incl { $_ eq $after } @ord;
  2         6  
252 1     2   9     my (@ord2) = after_incl { $_ eq $after } @ord;
  2         6  
253 1         3     shift @ord2;
254 1         42     Text::Parser::RuleSpec->_set_class_rule_order(
255                     $cls => [ @ord1, $rname, @ord2 ] );
256             }
257              
258             sub _if_empty_prepopulate_rules_from_superclass {
259 36     36   91     my ( $meta, $cls ) = ( shift, 'Text::Parser::RuleSpec' );
260 36         320     my @ro = map { $cls->class_rule_order($_) } ( $meta->superclasses );
  38         2241  
261 36 100       1405     $cls->_set_class_rule_order( $meta->name => \@ro )
262                     if not $cls->_class_has_rules( $meta->name );
263             }
264              
265             sub _push_to_class_rules {
266 23     23   73     my ( $class, $cls, $rulename ) = ( 'Text::Parser::RuleSpec', @_ );
267 23         80     my @ord = $class->class_rule_order($cls);
268 23         56     push @ord, $rulename;
269 23         891     $class->_set_class_rule_order( $cls => \@ord );
270 23         107     $class->populate_class_rules($cls);
271             }
272              
273              
274             sub applies_cloned_rule {
275 6     6 1 33690     my ( $meta, $orule ) = ( shift, shift );
276 6         21     _first_things_on_applies_cloned_rule( $meta, $orule, @_ );
277 2         318     my $nrule = _gen_new_rule_name_from( $meta, $orule );
278 2         10     _register_cloned_rule( _full_rule_name( $meta, $nrule ),
279                     _qualified_rulename( $orule, $meta ), @_ );
280 2         9     _set_correct_rule_order( $meta, $nrule, @_ );
281             }
282              
283             sub _first_things_on_applies_cloned_rule {
284 6     6   13     my ( $meta, $name ) = ( shift, shift );
285 6         17     _excepts_apply_cloned_rule( $meta, $name, @_ );
286 2         10     _set_default_of_attributes( $meta, auto_split => 1 );
287             }
288              
289             sub _excepts_apply_cloned_rule {
290 6     6   14     my ( $meta, $name ) = ( shift, shift );
291 6         21     _rulespec_cant_be_in_main( $meta, $name, 'applies_cloned_rule' );
292 6         18     _must_have_named_super( $meta, $name );
293 3         17     _check_args_hash_stuff( $meta, "applies_cloned_rule $name", @_ );
294 3 100       13     parser_exception("$name is not an existing rule ; can\'t clone it")
295                     if not _is_existing_rule( $name, $meta );
296             }
297              
298             my %clone_options = ( %rule_options, add_precondition => 1, );
299              
300             sub _must_have_named_super {
301 6     6   13     my ( $meta, $name ) = ( shift, shift );
302                 parser_exception("applies_cloned_rule requires original rule name")
303                     if not defined $name
304                     or ( '' ne ref($name) )
305 6 100 100     55         or ( exists $clone_options{$name} );
      100        
306             }
307              
308             sub _is_existing_rule {
309 3     3   9     my ( $rname, $meta ) = ( shift, shift );
310 3 100       109     return 1 if Text::Parser::RuleSpec->is_known_rule($rname);
311 2 100       20     return 0 if $rname =~ /\//;
312 1         41     return Text::Parser::RuleSpec->is_known_rule(
313                     $meta->name . '/' . $rname );
314             }
315              
316             sub _qualified_rulename {
317 2     2   5     my ( $r, $meta ) = ( shift, shift );
318 2 100       71     return $meta->name . '/' . $r
319                     if not Text::Parser::RuleSpec->is_known_rule($r);
320 1         6     return $r;
321             }
322              
323             sub _gen_new_rule_name_from {
324 2     2   5     my ( $meta, $oname ) = ( shift, shift );
325 2         9     my ( $cls, $rname ) = split( /\//, $oname, 2 );
326 2 100       6     $rname = $cls if not defined $rname;
327 2         9     my $nname = $meta->name . '/' . $rname;
328 2 100       76     return $rname if not Text::Parser::RuleSpec->is_known_rule($nname);
329 1         3     my $incr = 2;
330 1         38     $incr++ while Text::Parser::RuleSpec->is_known_rule("$nname\@$incr");
331 1         4     return "$rname\@$incr";
332             }
333              
334             sub _register_cloned_rule {
335 2     2   4     my ( $key, $orule ) = ( shift, shift );
336 2         10     my %opts = _get_rule_opts_only(@_);
337 2         73     my $o = Text::Parser::RuleSpec->class_rule_object($orule);
338 2         14     my $rule = $o->clone(%opts);
339 2         80     Text::Parser::RuleSpec->_add_new_rule( $key => $rule );
340             }
341              
342              
343             sub disables_superclass_rules {
344 7     7 1 36317     my $meta = shift;
345 7         28     _rulespec_cant_be_in_main( $meta, undef, 'disables_superclass_rules' );
346 6         30     _check_disable_rules_args( $meta->name, @_ );
347 2         7     _find_and_remove_superclass_rules( $meta, @_ );
348             }
349              
350             sub _check_disable_rules_args {
351 6     6   12     my $cls = shift;
352 6 100       25     parser_exception(
353                     "No arguments specified in call to disable_superclass_rules")
354                     if not @_;
355 5         13     foreach my $a (@_) {
356 7         18         _test_rule_type_and_val( $cls, $a );
357                 }
358             }
359              
360             my %disable_arg_types = ( '' => 1, 'Regexp' => 1, 'CODE' => 1 );
361              
362             sub _test_rule_type_and_val {
363 7     7   14     my $type_a = ref( $_[1] );
364                 parser_exception(
365                     "Rules must be selected by regular expressions or a code")
366 7 100       25         if not exists $disable_arg_types{$type_a};
367 6 100       18     _test_rule_string_val(@_) if $type_a eq '';
368             }
369              
370             sub _test_rule_string_val {
371 4     4   12     my ( $cls, $a ) = ( shift, shift );
372 4 100       22     parser_exception(
373                     "disable_superclass_rule called with $a ; must be in format <superclass>/<rulename>"
374                 ) if $a !~ /\//;
375 3         11     my @c = split /\//, $a, 2;
376 3 100       15     parser_exception("Cannot disable rules of same class") if $c[0] eq $cls;
377             }
378              
379             sub _find_and_remove_superclass_rules {
380 2     2   4     my $meta = shift;
381 2         6     _if_empty_prepopulate_rules_from_superclass($meta);
382 2         11     my @ord = _filtered_rules( $meta->name, @_ );
383 2         90     Text::Parser::RuleSpec->_set_class_rule_order( $meta->name => \@ord );
384 2         11     Text::Parser::RuleSpec->populate_class_rules( $meta->name );
385             }
386              
387             sub _filtered_rules {
388 2     2   5     my $cls = shift;
389 2         3     local $_;
390 2 100       8     map { _is_to_be_filtered( $_, @_ ) ? () : $_ }
  5         22  
391                     ( Text::Parser::RuleSpec->class_rule_order($cls) );
392             }
393              
394             my %test_for_filter_type = (
395                 '' => sub { $_[0] eq $_[1]; },
396                 'Regexp' => sub { $_[0] =~ $_[1]; },
397                 'CODE' => sub { $_[1]->( $_[0] ); },
398             );
399              
400             sub _is_to_be_filtered {
401 5     5   11     my $r = shift;
402 5         7     foreach my $p (@_) {
403 9         17         my $t = ref $p;
404 9 100       34         return 1 if $test_for_filter_type{$t}->( $r, $p );
405                 }
406 1         9     return 0;
407             }
408              
409              
410             sub unwraps_lines_using {
411 2     2 1 6977     my $meta = shift;
412 2         12     _rulespec_cant_be_in_main( $meta, undef, 'unwraps_lines_using' );
413 1         4     my ( $is_wr, $un_wr ) = _check_custom_unwrap_args(@_);
414 1         76     _set_lws_and_routines( $meta, $is_wr, $un_wr );
415             }
416              
417             sub _check_custom_unwrap_args {
418 8 100   8   29     parser_exception( "Needs exactly 4 arguments ; " . scalar(@_) . " given" )
419                     if @_ != 4;
420 7         24     _test_fields_unwrap_rtn(@_);
421 3         11     my (%opt) = @_;
422 3         12     return ( $opt{is_wrapped}, $opt{unwrap_routine} );
423             }
424              
425             sub _test_fields_unwrap_rtn {
426 7     7   33     my (%opt) = (@_);
427                 parser_exception(
428                     "unwraps_lines_using must have keys: is_wrapped, unwrap_routine")
429 7 100 100     48         if not( exists $opt{is_wrapped} and exists $opt{unwrap_routine} );
430 4         21     _is_arg_a_code( $_, %opt ) for (qw(is_wrapped unwrap_routine));
431             }
432              
433             sub _is_arg_a_code {
434 7     7   23     my ( $arg, %opt ) = (@_);
435                 parser_exception(
436                     "$arg in call to unwraps_lines_using must be code reference")
437 7 100       36         if 'CODE' ne ref( $opt{$arg} );
438             }
439              
440             sub _set_lws_and_routines {
441 1     1   3     my ( $meta, $is_wr, $unwr ) = @_;
442 1         5     _set_default_of_attributes( $meta, line_wrap_style => 'custom' );
443 1     1   307     _set_default_of_attributes( $meta, _is_wrapped => sub { $is_wr; } );
  1         28  
444 1     1   253     _set_default_of_attributes( $meta, _unwrap_routine => sub { $unwr; } );
  1         24  
445             }
446              
447              
448             __PACKAGE__->meta->make_immutable;
449              
450 42     42   212386 no Moose;
  42         158  
  42         445  
451 42     42   13409 no MooseX::ClassAttribute;
  42         164  
  42         416  
452              
453             1;
454              
455             __END__
456            
457             =pod
458            
459             =encoding UTF-8
460            
461             =head1 NAME
462            
463             Text::Parser::RuleSpec - Syntax sugar for rule specification while subclassing Text::Parser or derivatives
464            
465             =head1 VERSION
466            
467             version 1.000
468            
469             =head1 SYNOPSIS
470            
471             package MyFavorite::Parser;
472            
473             use Text::Parser::RuleSpec;
474             extends 'Text::Parser';
475            
476             has '+multiline_type' => (default => 'join_next');
477            
478             unwraps_lines_using (
479             is_wrapped => sub {
480             my $self = shift;
481             $_ = shift;
482             chomp;
483             m/\s+[~]\s*$/;
484             },
485             unwrap_routine => sub {
486             my ($self, $last, $current) = @_;
487             chomp $last;
488             $last =~ s/\s+[~]\s*$//g;
489             "$last $current";
490             },
491             );
492            
493             applies_rule get_emails => (
494             if => '$1 eq "EMAIL:"',
495             do => '$2;'
496             );
497            
498             package main;
499            
500             my $parser = MyFavorite::Parser->new();
501             $parser->read('/path/to/email_lists.txt');
502             my (@emails) = $parser->get_records();
503             print "Here are all the emails from the file: @emails\n";
504            
505             =head1 DESCRIPTION
506            
507             =head2 Primary usage
508            
509             This class enables users to create their own parser classes for a known text file format, and facilitates code-sharing across multiple variants of the same basic text format. The basic steps are as follows:
510            
511             package MyFavorite::Parser;
512             use Text::Parser::RuleSpec;
513             extends 'Text::Parser';
514            
515             That's it! This is the bare-minimum required to make your own text parser. But it is not particularly useful at this point without any rules of its own.
516            
517             applies_rule comment_char => (
518             if => '$1 =~ /^#/;',
519             dont_record => 1,
520             );
521            
522             This above rule ignores all comment lines and is added to C<MyFavorite::Parser> class. So now when you create an instance of C<MyFavorite::Parser>, it would automatically run this rule when you call C<L<read|Text::Parser/read>>.
523            
524             We can preset any attributes for this parser class using the familiar L<Moose> functions. Here is an example:
525            
526             has '+line_wrap_style' => (
527             default => 'trailing_backslash',
528             is => 'ro',
529             );
530            
531             has '+auto_trim' => (
532             default => 'b',
533             is => 'ro',
534             );
535            
536             =head2 Using attributes for storage
537            
538             Sometimes, you may want to store the parsed information in attributes, instead of records. So for example:
539            
540             has current_section => (
541             is => 'rw',
542             isa => 'Str|Undef',
543             default => undef,
544             lazy => 1,
545             );
546            
547             has _num_lines_by_section => (
548             is => 'rw',
549             isa => 'HashRef[Int]',
550             default => sub { {}; },
551             lazy => 1,
552             handles => {
553             num_lines => 'get',
554             _set_num_lines => 'set',
555             }
556             );
557            
558             applies_rule inc_section_num_lines => (
559             if => '$1 ne "SECTION"',
560             do => 'my $sec = $this->current_section;
561             my $n = $this->num_lines($sec);
562             $this->_set_num_lines($sec => $n+1);',
563             dont_record => 1,
564             );
565            
566             applies_rule get_section_name => (
567             if => '$1 eq "SECTION"',
568             do => '$this->current_section($2); $this->_set_num_lines($2 => 0);',
569             dont_record => 1,
570             );
571            
572             In the above example, you can see how the section name we get from one rule is used in a different rule.
573            
574             =head2 Inheriting rules in subclasses
575            
576             We can further subclass a class that C<extends> L<Text::Parser>. Inheriting the rules of the superclass is automatic:
577            
578             package MyParser1;
579             use Text::Parser::RuleSpec;
580            
581             extends 'Text::Parser';
582            
583             applies_rule rule1 => (
584             do => '# something',
585             );
586            
587             package MyParser2;
588             use Text::Parser::RuleSpec;
589            
590             extends 'MyParser1';
591            
592             applies_rule rule1 => (
593             do => '# something else',
594             );
595            
596             Now, C<MyParser2> contains two rules: C<MyParser1/rule1> and C<MyParser2/rule1>. Note that both the rules in both classes are called C<rule1> and both will be executed. By default, rules of superclasses will be run before rules in the subclass. The subclass can change this order by explicitly stating that its own C<rule1> is run C<before> the C<rule1> of C<MyParser1>:
597            
598             package MyParser2;
599             use Text::Parser::RuleSpec;
600            
601             extends 'MyParser1';
602            
603             applies_rule rule1 => (
604             do => '# something else',
605             before => 'MyParser1/rule1',
606             );
607            
608             A subclass may choose to disable any superclass rules:
609            
610             package MyParser3;
611             use Text::Parser::RuleSpec;
612            
613             extends 'MyParser2';
614            
615             disables_superclass_rules qr/^MyParser1/; # disables all rules from MyParser1 class
616            
617             Or to clone a rule from either the same class, a superclass, or even from some other random class.
618            
619             package ClonerParser;
620             use Text::Parser::RuleSpec;
621            
622             use Some::Parser; # contains rules: "heading", "section"
623             extends 'MyParser2';
624            
625             applies_rule my_own_rule => (
626             if => '# check something',
627             do => '# collect some data',
628             after => 'MyParser2/rule1',
629             );
630            
631             applies_cloned_rule 'MyParser2/rule1' => (
632             add_precondition => '# Additional condition',
633             do => '# Optionally change the action',
634             # prepend_action => '# Or just prepend something',
635             # append_action => '# Or append something',
636             after => 'MyParser1/rule1',
637             );
638            
639             Imagine this situation: Programmer A writes a text parser for a text format syntax SYNT1, and programmer B notices that the text format he wishes to parse (SYNT2) is similar, except for a few differences. Instead of having to re-write the code from scratch, he can reuse the code from programmer A and modify it exactly as needed. This is especially useful when syntaxes many different text formats are very similar.
640            
641             =head1 METHODS
642            
643             There is no constructor for this module. You cannot create an instance of C<Text::Parser::RuleSpec>. Therefore, all methods here can be called on the C<Text::Parser::RuleSpec> directly.
644            
645             =head2 class_has_rules
646            
647             Takes parser class name and returns a boolean representing if that class has any rules or not. Returns boolean true if the class has any rules, and a boolean false otherwise.
648            
649             print "There are no class rules for MyFavorite::Parser.\n"
650             if not Text::Parser::RuleSpec->class_has_rules('MyFavorite::Parser');
651            
652             =head2 class_rule_order
653            
654             Takes a single string argument and returns the ordered list of rule names for the class.
655            
656             my (@order) = Text::Parser::RuleSpec->class_rule_order('MyFavorite::Parser');
657            
658             =head2 class_rule_object
659            
660             This takes a single string argument with the fully qualified rule name, and returns the actual rule object identified by that name.
661            
662             my $rule = Text::Parser::RuleSpec->class_rule_object('MyFavorite::Parser/rule1');
663            
664             =head2 class_rules
665            
666             Takes a single string argument and returns the actual rule objects of the given class name. This is a shortcut to first running C<class_rule_order> and then running C<class_rule_object> on each one of them.
667            
668             my (@rules) = Text::Parser::RuleSpec->class_rules('MyFavorite::Parser');
669            
670             =head2 is_known_rule
671            
672             Takes a string argument expected to be fully-qualified name of a rule. Returns a boolean that indicates if such a rule was ever compiled. The fully-qualified name of a rule is of the form C<Some::Class/rule_name>. Any suffixes like C<@2> or C<@3> should be included to check the existence of any cloned rules.
673            
674             print "Some::Parser::Class/some_rule is a rule\n"
675             if Text::Parser::RuleSpec->is_known_rule('Some::Parser::Class/some_rule');
676            
677             =head2 populate_class_rules
678            
679             Takes a parser class name as string argument. It populates the class rules according to the latest order of rules.
680            
681             Text::Parser::RuleSpec->populate_class_rules('MyFavorite::Parser');
682            
683             =head1 FUNCTIONS
684            
685             The following methods are exported into the namespace of your class by default, and may only be called outside the C<main> namespace.
686            
687             =head2 applies_rule
688            
689             Takes one mandatory string argument - a rule name - followed by the options to create a rule. These are the same as the arguments to the C<L<add_rule|Text::Parser/"add_rule">> method of L<Text::Parser> class. Returns nothing. Exceptions will be thrown if any of the required arguments are not provided.
690            
691             applies_rule print_emails => (
692             if => '$1 eq "EMAIL:"',
693             do => 'print $2;',
694             dont_record => 1,
695             continue_to_next => 1,
696             );
697            
698             The above call to create a rule C<print_emails> in your class C<MyFavorite::Parser>, will save the rule as C<MyFavorite::Parser/print_emails>. So if you want to clone it in sub-classes or want to insert a rule before or after that in a sub-class, then this is the way to reference the rule.
699            
700             Optionally, one may provide one of C<before> or C<after> clauses to specify when this rule is to be executed.
701            
702             applies_rule check_line_syntax => (
703             if => '$1 ne "SECTION"',
704             do => '$this->check_syntax($this->current_section, $_);',
705             before => 'Parent::Parser/add_line_to_data_struct',
706             );
707            
708             The above rule will apply C<>
709            
710             Exceptions will be thrown if the C<before> or C<after> rule does not have a class name in it, or if it is the same as the current class, or if the rule is not among the inherited rules so far. Only one of C<before> or C<after> clauses may be provided.
711            
712             =head2 applies_cloned_rule
713            
714             Clones an existing rule to make a replica, but you can add options to change any parameters of the rule.
715            
716             applies_cloned_rule 'Some::SuperClass::Parser/some_rule' => (
717             add_precondition => '1; # add some tests returning boolean',
718             before => 'MayBe::Another::Superclass::Parser/some_other_rule',
719             ## Or even 'Some::SuperClass::Parser/another_rule'
720             do => '## Change the do clause of original rule',
721             );
722            
723             The first argument must be a string containing the rule name to be cloned. You may clone a superclass rule, or even a rule from another class that you have only C<use>d in your code, but are not actually inheriting (using C<extends>). You may even clone a rule from the present class if the rule has been defined already. If the rule name specified contains a class name, then the exact rule is cloned, modified according to other clauses, and inserted into the rule order. But if the rule name specified does not have a classname, then the function looks for a rule with that name in the current class, and clones that one.
724            
725             You may use one of the C<before> or C<after> clauses just like in C<applies_rule>. You may use any of the other rule creation options like C<if>, C<do>, C<continue_to_next>, or C<dont_record>. And you may optionally also use the C<add_precondition> clause. In many cases, you may not need any of the rule-creation options at all and may use only C<add_precondition> or any one of C<before> or C<after> clauses. If you do use any of the rule-creating options like C<do> or C<if>, then it will change those fields of the cloned copy of the original rule.
726            
727             Note that when you clone a rule, you do not change the original rule itself. You actually make a second copy and modify that. So you retain the original rule along with the clone.
728            
729             The new cloned rule created is automatically renamed by C<applies_cloned_rule>. If a rule C<Some::Other::Class/my_rule_1> is cloned into your parser class C<MyFavorite::Parser>, then the clone is named C<MyFavorite::Parser/my_rule_1>. This way, the original rule is left unaffected. If such a name already exists, then the clone adds C<@2> suffix to the name, viz., C<MyFavorite::Parser/my_rule_1@2>. If that also exists, it will be called C<MyFavorite::Parser/my_rule_1@3>. And so on it goes on incrementing.
730            
731             =head2 disables_superclass_rules
732            
733             Takes a list of rule names, or regular expression patterns, or subroutine references to identify rules that are to be disabled. You cannot disable rules of the same class.
734            
735             A string argument is expected to contain the full rule-name (including class name) in the format C<My::Parser::Class/my_rule>. The C</> (slash) separating the class name and rule name is mandatory.
736            
737             A regexp argument is tested against the full rule-name.
738            
739             If a subroutine reference is provided, the subroutine is called for each rule in the class, and the rule is disabled if the subroutine returns a true value.
740            
741             disables_superclass_rules qw(Parent::Parser::Class/parent_rule Another::Class/another_rule);
742             disables_superclass_rules qr/Parent::Parser::Class\/comm.*/;
743             disables_superclass_rules sub {
744             my $rulename = shift;
745             $rulename =~ /[@]/;
746             };
747            
748             =head2 unwraps_lines_using
749            
750             This function may be used if one wants to specify a custom line-unwrapping routine. Takes a hash argument with mandatory keys as follows:
751            
752             unwraps_lines_using(
753             is_wrapped => sub { # Should return a boolean for each $line
754             1;
755             },
756             unwrap_routine => sub { # Should return a string for each $last and $line
757             my ($self, $last, $line) = @_;
758             $last.$line;
759             },
760             );
761            
762             For the pair of routines to not cause unexpected C<undef> results, they should return defined values always. To effectively unwrap lines, the C<is_wrapped> routine should return a boolean C<1> when it encounters the continuation character, and C<unwrap_routine> should return a string that appropriately joins the last and current line together.
763            
764             =head1 SEE ALSO
765            
766             =over 4
767            
768             =item *
769            
770             L<Text::Parser::Manual::ExtendedAWKSyntax> - Read this manual to learn how to do cool things with this class
771            
772             =item *
773            
774             L<Text::Parser::Error> - there is a change in how exceptions are thrown by this class. Read this page for more information.
775            
776             =back
777            
778             =head1 BUGS
779            
780             Please report any bugs or feature requests on the bugtracker website
781             L<http://github.com/balajirama/Text-Parser/issues>
782            
783             When submitting a bug or request, please include a test-file or a
784             patch to an existing test-file that illustrates the bug or desired
785             feature.
786            
787             =head1 AUTHOR
788            
789             Balaji Ramasubramanian <balajiram@cpan.org>
790            
791             =head1 COPYRIGHT AND LICENSE
792            
793             This software is copyright (c) 2018-2019 by Balaji Ramasubramanian.
794            
795             This is free software; you can redistribute it and/or modify it under
796             the same terms as the Perl 5 programming language system itself.
797            
798             =cut
799