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   422 use strict;
  58         125  
  58         1604  
4 58     58   283 use warnings;
  58         116  
  58         1248  
5 58     58   265 use Moo;
  58         109  
  58         292  
6 58     58   15971 use Types::Standard qw/Str ArrayRef Object CodeRef/;
  58         150  
  58         486  
7 58     58   44533 use Data::Dumper;
  58         129  
  58         3145  
8 58     58   22156 use Text::Amuse::Compile::Indexer::Specification;
  58         175  
  58         2016  
9 58     58   401 use Text::Amuse::Functions qw/muse_format_line/;
  58         119  
  58         67528  
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   3148 my $self = shift;
69 8         17 my @specs;
70 8         80 my $lang = $self->language_code;
71             my $escape = sub {
72 67     67   4006 return muse_format_line(ltx => $_[0], $lang);
73 8         43 };
74 8         17 foreach my $str (@{$self->index_specs}) {
  8         36  
75 11         97 my ($first, @lines) = grep { length($_) } split(/\n+/, $str);
  56         120  
76 11 50       76 if ($first =~ m/^INDEX ([a-z]+): (.+)/) {
77 11         45 my ($name, $label) = ($1, $2);
78 11         23 my @patterns;
79             # remove the comments and the white space
80 11         25 foreach my $str (@lines) {
81 45         14399 $str =~ s/\A\s*//g;
82 45         241 $str =~ s/\s*\z//g;
83 45 50       151 push @patterns, $escape->($str) if $str;
84             }
85 11         4474 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         171 return \@specs;
96             }
97              
98             sub indexed_tex_body {
99 7     7 1 19 my $self = shift;
100 7 50       16 if (@{$self->index_specs}) {
  7         45  
101 7         27 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 393 my $self = shift;
110 8         35 my $full_body = $self->latex_body;
111             # remove the indexes
112 8         921 $full_body =~ s/\\begin\{comment\}
113             \s*
114             INDEX\x{20}+[a-z]+:
115             .*?
116             \\end\{comment\}//gsx;
117              
118 8         859 my @paragraphs = split(/\n\n/, $full_body);
119              
120             # build a huge regexp with the matches
121 8         27 my %labels;
122             my @matches;
123 8         20 for (my $i = 0; $i < @{$self->specifications}; $i++) {
  19         343  
124 11         438 my $spec = $self->specifications->[$i];
125             MATCH:
126 11         76 foreach my $match (@{$spec->matches}) {
  11         164  
127 45         380 my $str = $match->{match};
128 45 50       99 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         159 matches => 0,
135             spec => $spec,
136             spec_index => $i,
137             };
138 45         65 my @pieces;
139 45 100       123 if ($match->{match} =~ m/\A\w/) {
140 40         62 push @pieces, "\\b";
141             }
142 45         80 push @pieces, quotemeta($match->{match});
143 45 100       124 if ($match->{match} =~ m/\w\z/) {
144 41         65 push @pieces, "\\b";
145             }
146 45         122 push @matches, join('', @pieces);
147             }
148             }
149 8         77 my $re_string = join('|', @matches);
150 8         239 my $re = qr{$re_string};
151             # print "Regex is $re\n";
152 8         23 my @out;
153              
154             my $add_index = sub {
155 138     138   267 my ($match) = @_;
156 138 50       310 die "Cannot find belonging specification for $match. Bug?" unless $labels{$match};
157 138         346 my $index_name = $labels{$match}{spec}->index_name;
158 138         212 my $label = $labels{$match}{label};
159 138         181 $labels{$match}{matches}++;
160 138         12536 return "\\index[$index_name]{$label}";
161 8         49 };
162              
163             LINE:
164 8         34 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       724 if ($p =~ m/^%/m) {
    100          
168 5         10 my @prepend;
169 5         98 while ($p =~ m/($re)/g) {
170 9         27 push @prepend, $add_index->($1);
171             }
172 5 100       37 if (@prepend) {
173 3         17 $p = join("\n", @prepend) . "\n" . $p;
174             }
175             }
176             elsif ($p =~ m/^\\(mark|part|chapter|section|subsection|subsubsection)/m) {
177 26         38 my @append;
178 26         314 while ($p =~ m/($re)/g) {
179 10         25 push @append, $add_index->($1);
180             }
181 26 100       63 if (@append) {
182 7         22 $p .= "\n" . join("\n", @append);
183             }
184              
185             }
186             else {
187 235         34892 $p =~ s/($re)/$add_index->($1) . $1/ge;
  119         207  
188             }
189 266         535 push @out, $p;
190             }
191             # collect the stats
192 8         36 my %stats;
193 8         68 foreach my $regex (sort keys %labels) {
194 45         688 my $stat = $labels{$regex};
195 45   100     161 $stats{$stat->{spec_index}} ||= 0;
196 45 100       90 if ($stat->{matches} > 0) {
197 41         75 $stats{$stat->{spec_index}} += $stat->{matches};
198             }
199             else {
200 4         26 $self->logger->("No matches found for $regex\n");
201             }
202             }
203 8         350 foreach my $k (sort keys %stats) {
204 11         270 $self->specifications->[$k]->total_found($stats{$k});
205             }
206 8         1892 return join("\n\n", @out);
207             }
208              
209             1;