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   4912 use strict;
  7         22  
  7         276  
4 7     7   41 use warnings;
  7         18  
  7         374  
5 7     7   42 use Carp qw(confess);
  7         34  
  7         451  
6 7     7   3293 use Clone qw(clone);
  7         3642  
  7         454  
7 7     7   736 use Data::Dumper ();
  7         9740  
  7         158  
8 7     7   33 use Moo;
  7         15  
  7         51  
9 7     7   8333 use MooX::StrictConstructor;
  7         97147  
  7         52  
10 7     7   231797 use MooX::Types::MooseLike::Base qw(ArrayRef CodeRef RegexpRef ScalarRef);
  7         77  
  7         616  
11 7     7   51 use namespace::autoclean;
  7         34  
  7         88  
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   62 my $self = shift;
43            
44 28         811 my $regex = $self->start_rule;
45 28         724 my $content_ref = $self->content_ref;
46 28 50       195 defined ${$content_ref}
  28         135  
47             or return confess 'content_ref is a reference to undef';
48 28         72 my @stack;
49 28         51 while ( ${$content_ref} =~ m{ \G .*? ( $regex ) }xmsgc ) {
  475         15263  
50             push @stack, {
51 447         845 start_pos => pos( ${$content_ref} ) - length $1,
  447         1722  
52             };
53             }
54 28         899 $self->stack(\@stack);
55            
56             # debug if requested
57 28 50       2336 $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   105 my $self = shift;
74            
75 28         625 my $content_ref = $self->content_ref;
76 28         181 for my $stack_item ( @{ $self->stack } ) {
  28         543  
77 447         9532 my $rules = clone( $self->rules );
78 447         195639 my $pos = $stack_item->{start_pos};
79 447         1108 my $level = 0;
80 447         1272 my @level_matched = ( 1 );
81 447         843 my $has_matched = 0;
82 447 50       16051 $self->debug_code
83             and $self->debug_code->('rules start', "$level: Starting at pos $pos.");
84 447         4017 my (@parent_rules, @parent_pos, %level_and_of, @stack_result);
85             RULE: {
86 447         913 my $rule = shift @{$rules};
  32218         55836  
  32218         74380  
87 32218 100       84929 if (! $rule) {
88 2362 50       48272 $self->debug_code
89             and $self->debug_code->('rules last', "$level: No more rules found.");
90 2362 100       16925 if (@parent_rules) {
91 1915         4279 $rules = pop @parent_rules;
92 1915         4419 () = pop @parent_pos;
93 1915 50       38020 $self->debug_code
94             and $self->debug_code->('rules parent', "$level: Going back to parent.");
95             # delete the parent and match
96 1915 100       13401 if ( ! $has_matched ) {
97             LEVEL: ## no critic (DeepNests)
98 14         63 for my $parent_level ( reverse 0 .. ( $level - 1 ) ) {
99 14 100       72 if ( exists $level_and_of{$parent_level} ) { ## no critic (DeepNests)
100 1         4 $level_and_of{$parent_level} = 0;
101 1         3 last LEVEL;
102             }
103             }
104             }
105 1915         3602 --$level;
106 1915         5140 redo RULE;
107             }
108 447         2831 last RULE;
109             }
110             # goto child
111 29856 100       83742 if ( ref $rule eq 'ARRAY' ) {
112 7749         16502 push @parent_rules, $rules;
113 7749         17204 push @parent_pos, $pos;
114 7749         167855 $rules = clone($rule);
115 7749 50       184500 $self->debug_code
116             and $self->debug_code->('rules child', "$level: Going to child.");
117 7749         55961 $level_matched[ ++$level ] = 1;
118 7749         25454 redo RULE;
119             }
120             # alternative
121 22107 100       74276 if ( lc $rule eq 'or' ) {
122 6243 100       15181 if ($has_matched) {
123 783         1641 $rules = pop @parent_rules;
124 783         11012 () = pop @parent_pos;
125 783         1589 $has_matched = 0;
126 783 50       17549 $self->debug_code
127             and $self->debug_code->('rules ignore', "$level: Matched before 'or' so ignore alternatives. Going back to parent.");
128 783         5180 --$level;
129 783         2099 redo RULE;
130             }
131             $self->debug_code
132 5460 50       120739 and $self->debug_code->('rules try', "$level: Not matched so try next alternative.");
133 5460         37942 $level_matched[$level] = 1;
134 5460         13812 redo RULE;
135             }
136             # to expect the next match
137 15864 100       40761 if ( lc $rule eq 'and' ) {
138 2085 100       6022 if ( ! exists $level_and_of{$level} ) {
139 403         1216 $level_and_of{$level} = 1;
140             }
141 2085 50       5728 if ( $level_matched[$level] ) {
142 2085 50       46230 $self->debug_code
143             and $self->debug_code->('rules next', "$level: And next rule.");
144 2085         17488 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 13779 100       35802 if ( lc $rule eq 'begin' ) {
154 5339         10083 @stack_result = ();
155 5339 50       117175 $self->debug_code
156             and $self->debug_code->('rules begin', "$level: Begin.");
157 5339         39404 redo RULE;
158             }
159             # done
160 8440 100       22891 if ( lc $rule eq 'end' ) {
161             my $is_and
162             = ! exists $level_and_of{$level}
163             || exists $level_and_of{$level}
164 446   66     2871 && $level_and_of{$level};
165 446 50       1209 if ($is_and) {
166 446         827 push @{ $stack_item->{match} }, @stack_result;
  446         2480  
167 446 50       9950 $self->debug_code
168             and $self->debug_code->('rules end', "$level: End, so store data.");
169             }
170 446         3648 redo RULE;
171             }
172             # ref $rule is 'Regexp' or $rule is code
173 7994         15231 pos ${$content_ref} = $pos;
  7994         37891  
174 7994 50       185139 $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 7994 100       63291 : ${$content_ref} =~ m{ \G ( $rule ) }xms;
  7888         541119  
181 7994   66     60266 $level_matched[$level] &&= $has_matched;
182 7994 100       24914 if ( exists $level_and_of{$level} ) {
183 1241   100     5173 $level_and_of{$level} &&= $has_matched;
184             }
185 7994 100       19407 if ($has_matched) {
186 2532         5995 push @stack_result, @result;
187 2532         7892 $pos += length $full_match;
188             $self->debug_code
189 2532 50       66885 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 2532         23489 redo RULE;
197             }
198 5462         12006 $rules = pop @parent_rules;
199 5462         23269 $pos = pop @parent_pos;
200             $self->debug_code
201 5462 50       146816 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 5462         40336 --$level;
209 5462         18190 redo RULE;
210             }
211             }
212            
213 28         106 return $self;
214             }
215            
216             sub _cleanup_and_calculate_reference {
217 28     28   58 my $self = shift;
218            
219 28         590 my $stack = $self->stack;
220 28         833 my $content_ref = $self->content_ref;
221 28         103 @{$stack} = map {
222             exists $_->{match}
223 447 100       1030 ? do {
224             # calculate reference
225 446         755 my $pre_match = substr ${$content_ref}, 0, $_->{start_pos};
  446         2402  
226 446         2129 my $newline_count = $pre_match =~ tr{\n}{\n};
227 446         1346 $_->{line_number} = $newline_count + 1;
228 446         1209 $_;
229             }
230             # cleanup
231             : ();
232 28         198 } @{$stack};
  28         82  
233            
234             # debug if requested
235 28 50       715 $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 89 my ($self, $arg_ref) = @_;
252            
253 28         139 $self->_parse_pos;
254 28         359 $self->_parse_rules;
255 28         160 $self->_cleanup_and_calculate_reference;
256            
257 28         307 return $self;
258             }
259            
260             __PACKAGE__->meta->make_immutable;
261            
262             1;
263            
264             __END__