File Coverage

blib/lib/Text/Amuse/Compile/Indexer.pm
Criterion Covered Total %
statement 98 102 96.0
branch 19 24 79.1
condition 2 2 100.0
subroutine 12 12 100.0
pod 2 2 100.0
total 133 142 93.6


line stmt bran cond sub pod time code
1             package Text::Amuse::Compile::Indexer;
2              
3 58     58   413 use strict;
  58         121  
  58         1618  
4 58     58   305 use warnings;
  58         122  
  58         1272  
5 58     58   278 use Moo;
  58         119  
  58         321  
6 58     58   16432 use Types::Standard qw/Str ArrayRef Object CodeRef/;
  58         142  
  58         493  
7 58     58   46295 use Data::Dumper;
  58         139  
  58         3242  
8 58     58   22683 use Text::Amuse::Compile::Indexer::Specification;
  58         171  
  58         2130  
9 58     58   416 use Text::Amuse::Functions qw/muse_format_line/;
  58         125  
  58         69643  
10              
11              
12             =encoding utf8
13              
14             =head1 NAME
15              
16             Text::Amuse::Compile::Indexer - Class for LaTeX indexes
17              
18             =head1 SYNOPSIS
19              
20             Everything here is pretty much private and used by L
21              
22             =head1 ACCESSORS AND METHODS
23              
24             =over 4
25              
26             =item latex_body
27              
28             The body provided to the constructor
29              
30             =item index_specs
31              
32             The raw indexes
33              
34             =item specifications
35              
36             Lazy built, the L
37             objects
38              
39             =item language_code
40              
41             The language ISO code. To be passed to C
42              
43             =item indexed_tex_body
44              
45             Method meant to be called from the L
46             object.
47              
48             =item interpolate_indexes
49              
50             Main method to get the job done.
51              
52             =item logger
53              
54             Required coderef for logging.
55              
56             =back
57              
58             =cut
59              
60              
61             has latex_body => (is => 'ro', required => 1, isa => Str);
62             has index_specs => (is => 'ro', required => 1, isa => ArrayRef[Str]);
63             has specifications => (is => 'lazy', isa => ArrayRef[Object]);
64             has language_code => (is => 'ro');
65             has logger => (is => 'ro', isa => CodeRef, required => 1);
66              
67             sub _build_specifications {
68 8     8   2887 my $self = shift;
69 8         16 my @specs;
70 8         72 my $lang = $self->language_code;
71             my $escape = sub {
72 67     67   4043 return muse_format_line(ltx => $_[0], $lang);
73 8         45 };
74 8         19 foreach my $str (@{$self->index_specs}) {
  8         34  
75 11         81 my ($first, @lines) = grep { length($_) } split(/\n+/, $str);
  56         127  
76 11 50       90 if ($first =~ m/^INDEX ([a-z]+): (.+)/) {
77 11         48 my ($name, $label) = ($1, $2);
78 11         21 my @patterns;
79             # remove the comments and the white space
80 11         28 foreach my $str (@lines) {
81 45         14169 $str =~ s/\A\s*//g;
82 45         245 $str =~ s/\s*\z//g;
83 45 50       152 push @patterns, $escape->($str) if $str;
84             }
85 11         4391 push @specs, Text::Amuse::Compile::Indexer::Specification->new(
86             index_name => $escape->($name),
87             index_label => $escape->($label),
88             patterns => \@patterns,
89             );
90             }
91             else {
92 0         0 die "Invalid index specification $first, expecting INDEX :
93             }
94             }
95 8         172 return \@specs;
96             }
97              
98             sub indexed_tex_body {
99 7     7 1 15 my $self = shift;
100 7 50       14 if (@{$self->index_specs}) {
  7         42  
101 7         32 return $self->interpolate_indexes;
102             }
103             else {
104 0         0 return $self->latex_body;
105             }
106             }
107              
108             sub interpolate_indexes {
109 8     8 1 437 my $self = shift;
110 8         83 my $full_body = $self->latex_body;
111             # remove the indexes
112 8         933 $full_body =~ s/\\begin\{comment\}
113             \s*
114             INDEX\x{20}+[a-z]+:
115             .*?
116             \\end\{comment\}//gsx;
117              
118 8         824 my @paragraphs = split(/\n\n/, $full_body);
119              
120             # build a huge regexp with the matches
121 8         25 my %labels;
122             my @matches;
123 8         23 for (my $i = 0; $i < @{$self->specifications}; $i++) {
  19         339  
124 11         477 my $spec = $self->specifications->[$i];
125             MATCH:
126 11         86 foreach my $match (@{$spec->matches}) {
  11         164  
127 45         393 my $str = $match->{match};
128 45 50       96 if (my $exists = $labels{$str}) {
129 0         0 $self->logger->("$str already has a label $exists->{label} " . $exists->{spec}->index_name . "\n");
130 0         0 next MATCH;
131             }
132             $labels{$str} = {
133             label => $match->{label},
134 45         155 matches => 0,
135             spec => $spec,
136             spec_index => $i,
137             };
138 45         62 my @pieces;
139 45 100       114 if ($match->{match} =~ m/\A\w/) {
140 40         63 push @pieces, "\\b";
141             }
142 45         109 push @pieces, quotemeta($match->{match});
143 45 100       129 if ($match->{match} =~ m/\w\z/) {
144 41         58 push @pieces, "\\b";
145             }
146 45         123 push @matches, join('', @pieces);
147             }
148             }
149 8         86 my $re_string = join('|', @matches);
150 8         223 my $re = qr{$re_string};
151             # print "Regex is $re\n";
152 8         19 my @out;
153              
154             my $add_index = sub {
155 138     138   256 my ($match) = @_;
156 138 50       295 die "Cannot find belonging specification for $match. Bug?" unless $labels{$match};
157 138         277 my $index_name = $labels{$match}{spec}->index_name;
158 138         212 my $label = $labels{$match}{label};
159 138         183 $labels{$match}{matches}++;
160 138         12206 return "\\index[$index_name]{$label}";
161 8         51 };
162              
163             LINE:
164 8         22 foreach my $p (@paragraphs) {
165             # we index the inline comments as well, so we can index
166             # what we want, where we want.
167 266 100       679 if ($p =~ m/^%/m) {
    100          
168 5         9 my @prepend;
169 5         95 while ($p =~ m/($re)/g) {
170 9         54 push @prepend, $add_index->($1);
171             }
172 5 100       37 if (@prepend) {
173 3         12 $p = join("\n", @prepend) . "\n" . $p;
174             }
175             }
176             elsif ($p =~ m/^\\(mark|part|chapter|section|subsection|subsubsection)/m) {
177 26         37 my @append;
178 26         304 while ($p =~ m/($re)/g) {
179 10         22 push @append, $add_index->($1);
180             }
181 26 100       58 if (@append) {
182 7         26 $p .= "\n" . join("\n", @append);
183             }
184              
185             }
186             else {
187 235         34662 $p =~ s/($re)/$add_index->($1) . $1/ge;
  119         211  
188             }
189 266         531 push @out, $p;
190             }
191             # collect the stats
192 8         18 my %stats;
193 8         73 foreach my $regex (sort keys %labels) {
194 45         604 my $stat = $labels{$regex};
195 45   100     151 $stats{$stat->{spec_index}} ||= 0;
196 45 100       80 if ($stat->{matches} > 0) {
197 41         76 $stats{$stat->{spec_index}} += $stat->{matches};
198             }
199             else {
200 4         29 $self->logger->("No matches found for $regex\n");
201             }
202             }
203 8         362 foreach my $k (sort keys %stats) {
204 11         254 $self->specifications->[$k]->total_found($stats{$k});
205             }
206 8         1717 return join("\n\n", @out);
207             }
208              
209             1;