File Coverage

blib/lib/Locale/TextDomain/OO/Extract/Base/RegexBasedExtractor.pm
Criterion Covered Total %
statement 139 156 89.1
branch 47 70 67.1
condition 7 9 77.7
subroutine 13 13 100.0
pod 1 1 100.0
total 207 249 83.1


line stmt bran cond sub pod time code
1             package Locale::TextDomain::OO::Extract::Base::RegexBasedExtractor; ## no critic (TidyCode)
2            
3 7     7   2843 use strict;
  7         16  
  7         188  
4 7     7   39 use warnings;
  7         15  
  7         197  
5 7     7   36 use Carp qw(confess);
  7         17  
  7         316  
6 7     7   1587 use Clone qw(clone);
  7         12801  
  7         323  
7 7     7   402 use Data::Dumper ();
  7         4896  
  7         119  
8 7     7   33 use Moo;
  7         17  
  7         41  
9 7     7   4232 use MooX::StrictConstructor;
  7         74232  
  7         36  
10 7     7   119477 use MooX::Types::MooseLike::Base qw(ArrayRef CodeRef RegexpRef ScalarRef);
  7         19  
  7         431  
11 7     7   40 use namespace::autoclean;
  7         16  
  7         57  
12            
13             our $VERSION = '2.007';
14            
15             has content_ref => (
16             is => 'rw',
17             isa => ScalarRef,
18             );
19            
20             has start_rule => (
21             is => 'rw',
22             isa => RegexpRef,
23             );
24            
25             has rules => (
26             is => 'rw',
27             isa => ArrayRef,
28             );
29            
30             has debug_code => (
31             is => 'rw',
32             isa => CodeRef,
33             clearer => 'clear_debug_code',
34             );
35            
36             has stack => (
37             is => 'rw',
38             isa => ArrayRef,
39             );
40            
41             sub _parse_pos {
42 28     28   55 my $self = shift;
43            
44 28         491 my $regex = $self->start_rule;
45 28         509 my $content_ref = $self->content_ref;
46 28 50       146 defined ${$content_ref}
  28         145  
47             or return confess 'content_ref is a reference to undef';
48 28         59 my @stack;
49 28         55 while ( ${$content_ref} =~ m{ \G .*? ( $regex ) }xmsgc ) {
  473         7644  
50             push @stack, {
51 445         768 start_pos => pos( ${$content_ref} ) - length $1,
  445         1486  
52             };
53             }
54 28         578 $self->stack(\@stack);
55            
56             # debug if requested
57 28 50       1716 $self->debug_code
58             or return $self;
59 0         0 my $dump = Data::Dumper ## no critic (LongChainsOfMethodCalls)
60             ->new([ $self->stack ], [ qw(stack) ])
61             ->Indent(1)
62             ->Quotekeys(0)
63             ->Sortkeys(1)
64             ->Useqq(1)
65             ->Dump;
66 0         0 chomp $dump;
67 0         0 $self->debug_code->('stack start', $dump);
68            
69 0         0 return $self;
70             }
71            
72             sub _parse_rules { ## no critic (ExcessComplexity)
73 28     28   55 my $self = shift;
74            
75 28         402 my $content_ref = $self->content_ref;
76 28         145 for my $stack_item ( @{ $self->stack } ) {
  28         389  
77 445         6875 my $rules = clone( $self->rules );
78 445         148821 my $pos = $stack_item->{start_pos};
79 445         882 my $level = 0;
80 445         1008 my @level_matched = ( 1 );
81 445         727 my $has_matched = 0;
82 445 50       9322 $self->debug_code
83             and $self->debug_code->('rules start', "$level: Starting at pos $pos.");
84 445         3035 my (@parent_rules, @parent_pos, %level_and_of, @stack_result);
85             RULE: {
86 445         687 my $rule = shift @{$rules};
  32164         46840  
  32164         59057  
87 32164 100       62106 if (! $rule) {
88 2358 50       32563 $self->debug_code
89             and $self->debug_code->('rules last', "$level: No more rules found.");
90 2358 100       13236 if (@parent_rules) {
91 1913         3244 $rules = pop @parent_rules;
92 1913         3403 () = pop @parent_pos;
93 1913 50       25642 $self->debug_code
94             and $self->debug_code->('rules parent', "$level: Going back to parent.");
95             # delete the parent and match
96 1913 100       10386 if ( ! $has_matched ) {
97             LEVEL: ## no critic (DeepNests)
98 14         55 for my $parent_level ( reverse 0 .. ( $level - 1 ) ) {
99 14 100       50 if ( exists $level_and_of{$parent_level} ) { ## no critic (DeepNests)
100 1         2 $level_and_of{$parent_level} = 0;
101 1         3 last LEVEL;
102             }
103             }
104             }
105 1913         2883 --$level;
106 1913         3552 redo RULE;
107             }
108 445         1833 last RULE;
109             }
110             # goto child
111 29806 100       61965 if ( ref $rule eq 'ARRAY' ) {
112 7737         13271 push @parent_rules, $rules;
113 7737         13293 push @parent_pos, $pos;
114 7737         119205 $rules = clone($rule);
115 7737 50       120757 $self->debug_code
116             and $self->debug_code->('rules child', "$level: Going to child.");
117 7737         43358 $level_matched[ ++$level ] = 1;
118 7737         18645 redo RULE;
119             }
120             # alternative
121 22069 100       45087 if ( lc $rule eq 'or' ) {
122 6231 100       11361 if ($has_matched) {
123 781         1339 $rules = pop @parent_rules;
124 781         8727 () = pop @parent_pos;
125 781         1251 $has_matched = 0;
126 781 50       11669 $self->debug_code
127             and $self->debug_code->('rules ignore', "$level: Matched before 'or' so ignore alternatives. Going back to parent.");
128 781         4231 --$level;
129 781         1391 redo RULE;
130             }
131             $self->debug_code
132 5450 50       77531 and $self->debug_code->('rules try', "$level: Not matched so try next alternative.");
133 5450         28653 $level_matched[$level] = 1;
134 5450         9490 redo RULE;
135             }
136             # to expect the next match
137 15838 100       30184 if ( lc $rule eq 'and' ) {
138 2085 100       4372 if ( ! exists $level_and_of{$level} ) {
139 403         865 $level_and_of{$level} = 1;
140             }
141 2085 50       4108 if ( $level_matched[$level] ) {
142 2085 50       29933 $self->debug_code
143             and $self->debug_code->('rules next', "$level: And next rule.");
144 2085         11146 redo RULE;
145             }
146 0         0 $rules = pop @parent_rules;
147 0         0 () = pop @parent_pos;
148 0 0       0 $self->debug_code
149             and $self->debug_code->('rules ignore following', "$level: Ignore following. Going back to parent.");
150 0         0 --$level;
151 0         0 redo RULE;
152             }
153 13753 100       26871 if ( lc $rule eq 'begin' ) {
154 5327         8376 @stack_result = ();
155 5327 50       76565 $self->debug_code
156             and $self->debug_code->('rules begin', "$level: Begin.");
157 5327         28943 redo RULE;
158             }
159             # done
160 8426 100       16346 if ( lc $rule eq 'end' ) {
161             my $is_and
162             = ! exists $level_and_of{$level}
163             || exists $level_and_of{$level}
164 444   66     1898 && $level_and_of{$level};
165 444 50       950 if ($is_and) {
166 444         658 push @{ $stack_item->{match} }, @stack_result;
  444         1757  
167 444 50       7094 $self->debug_code
168             and $self->debug_code->('rules end', "$level: End, so store data.");
169             }
170 444         2552 redo RULE;
171             }
172             # ref $rule is 'Regexp' or $rule is code
173 7982         11401 pos ${$content_ref} = $pos;
  7982         89271  
174 7982 50       121424 $self->debug_code
175             and $self->debug_code->('rules current pos', "$level: Set the current pos to $pos.");
176             $has_matched
177             = my ($full_match, @result)
178             = ref $rule eq 'CODE'
179             ? $rule->($content_ref)
180 7982 100       47480 : ${$content_ref} =~ m{ \G ( $rule ) }xms;
  7888         298667  
181 7982   66     41235 $level_matched[$level] &&= $has_matched;
182 7982 100       16821 if ( exists $level_and_of{$level} ) {
183 1241   100     3266 $level_and_of{$level} &&= $has_matched;
184             }
185 7982 100       14844 if ($has_matched) {
186 2530         4822 push @stack_result, @result;
187 2530         6015 $pos += length $full_match;
188             $self->debug_code
189 2530 50       41545 and do {
190 0 0       0 my $rule_qr = ref $rule eq 'CODE' ? $rule->() : $rule;
191 0         0 $self->debug_code->(
192             'rules match',
193             "$level: Rule\n$rule_qr\nhas matched\n$full_match\nThe current pos is $pos.",
194             );
195             };
196 2530         16641 redo RULE;
197             }
198 5452         9654 $rules = pop @parent_rules;
199 5452         15254 $pos = pop @parent_pos;
200             $self->debug_code
201 5452 50       91517 and do {
202 0 0       0 my $rule_qr = ref $rule eq 'CODE' ? $rule->() : $rule;
203 0         0 $self->debug_code->(
204             'rules no match',
205             "$level: Rule\n$rule_qr\nhas not matched. Going back to parent.",
206             );
207             };
208 5452         31078 --$level;
209 5452         12205 redo RULE;
210             }
211             }
212            
213 28         91 return $self;
214             }
215            
216             sub _cleanup_and_calculate_reference {
217 28     28   62 my $self = shift;
218            
219 28         463 my $stack = $self->stack;
220 28         571 my $content_ref = $self->content_ref;
221 28         81 @{$stack} = map {
222             exists $_->{match}
223 445 100       815 ? do {
224             # calculate reference
225 444         574 my $pre_match = substr ${$content_ref}, 0, $_->{start_pos};
  444         5042  
226 444         1836 my $newline_count = $pre_match =~ tr{\n}{\n};
227 444         1035 $_->{line_number} = $newline_count + 1;
228 444         994 $_;
229             }
230             # cleanup
231             : ();
232 28         170 } @{$stack};
  28         74  
233            
234             # debug if requested
235 28 50       487 $self->debug_code
236             or return $self;
237 0         0 my $dump = Data::Dumper ## no critic (LongChainsOfMethodCalls)
238             ->new([ $self->stack ], [ qw(stack) ])
239             ->Indent(1)
240             ->Quotekeys(0)
241             ->Sortkeys(1)
242             ->Useqq(1)
243             ->Dump;
244 0         0 chomp $dump;
245 0         0 $self->debug_code->('stack clean', $dump);
246            
247 0         0 return $self;
248             }
249            
250             sub extract {
251 28     28 1 83 my ($self, $arg_ref) = @_;
252            
253 28         123 $self->_parse_pos;
254 28         264 $self->_parse_rules;
255 28         167 $self->_cleanup_and_calculate_reference;
256            
257 28         215 return $self;
258             }
259            
260             __PACKAGE__->meta->make_immutable;
261            
262             1;
263            
264             __END__