File Coverage

blib/lib/Locale/Maketext/Extract/Plugin/TT2.pm
Criterion Covered Total %
statement 127 127 100.0
branch 34 38 89.4
condition 31 36 86.1
subroutine 20 20 100.0
pod 2 2 100.0
total 214 223 95.9


line stmt bran cond sub pod time code
1             package Locale::Maketext::Extract::Plugin::TT2;
2             $Locale::Maketext::Extract::Plugin::TT2::VERSION = '1.00';
3 4     4   30 use strict;
  4         7  
  4         477  
4 4     4   25 use base qw(Locale::Maketext::Extract::Plugin::Base);
  4         8  
  4         439  
5 4     4   3222 use Template::Constants qw( :debug );
  4         18852  
  4         1216  
6 4     4   5536 use Template::Parser;
  4         355910  
  4         2427  
7              
8             # ABSTRACT: Template Toolkit format parser
9              
10              
11             # import strip_quotes
12             *strip_quotes
13             = \&Locale::Maketext::Extract::Plugin::TT2::Directive::strip_quotes;
14              
15             our %PARSER_OPTIONS;
16              
17             #===================================
18             sub file_types {
19             #===================================
20 18     18 1 118 return ( qw( tt tt2 html ), qr/\.tt2?\./ );
21             }
22              
23             my %Escapes = map { ( "\\$_" => eval("qq(\\$_)") ) } qw(t n r f b a e);
24              
25             #===================================
26             sub extract {
27             #===================================
28 53     53 1 107 my $self = shift;
29 53         92 my $data = shift;
30              
31 53         88 $Template::Directive::PRETTY = 1;
32 53         668 my $parser = Locale::Maketext::Extract::Plugin::TT2::Parser->new(
33             %PARSER_OPTIONS,
34             FACTORY => 'Locale::Maketext::Extract::Plugin::TT2::Directive',
35             FILE_INFO => 0,
36             );
37 53         9813 _init_overrides($parser);
38              
39 53         140 $parser->{extracted} = [];
40              
41 53         104 $Locale::Maketext::Extract::Plugin::TT2::Directive::PARSER
42             = $parser; # hack
43 53 50       878 $parser->parse($data)
44             || die $parser->error;
45              
46 53         10546 foreach my $entry ( @{ $parser->{extracted} } ) {
  53         172  
47 48         264 $entry->[2] =~ s/^\((.*)\)$/$1/s; # Remove () from vars
48 48         283 $_ =~ s/\\'/'/gs # Unescape \'
49 48         86 for @{$entry}[ 0, 2 ];
50 48         171 $entry->[2] =~ s/\\(?!")/\\\\/gs; # Escape all \ not followed by "
51             # Escape argument lists correctly
52 48         231 while ( my ( $char, $esc ) = each %Escapes ) {
53 336         3802 $entry->[2] =~ s/$esc/$char/g;
54             }
55 48         145 $entry->[1] =~ s/\D+.*$//;
56 48         259 $self->add_entry(@$entry);
57             }
58             }
59              
60             #===================================
61             sub _init_overrides {
62             #===================================
63 53     53   122 my $parser = shift;
64              
65             # Override the concatenation sub to return _ instead of .
66 53         111 my $states = $parser->{STATES};
67 53         87 foreach my $state ( @{$states} ) {
  53         108  
68 3339 100       17925 if ( my $CAT_no = $state->{ACTIONS}{CAT} ) {
69 53         310 my $CAT_rule_no
70             = $states->[ $states->[$CAT_no]{GOTOS}{expr} ]->{DEFAULT};
71              
72             # override the TT::Grammar sub which cats two args
73             $parser->{RULES}[ -$CAT_rule_no ][2] = sub {
74 3     3   825 my $first = ( $_[1] );
75 3         4 my $second = ( $_[3] );
76 3 100 66     10 if ( strip_quotes($first) && strip_quotes($second) ) {
77              
78             # both are literal
79 1         6 return "'${first}${second}'";
80             }
81             else {
82              
83             # at least one is an ident
84 2         9 return "$_[1] _ $_[3]";
85             }
86 53         418 };
87 53         731 last;
88             }
89             }
90             }
91              
92             #===================================
93             #===================================
94             package Locale::Maketext::Extract::Plugin::TT2::Parser;
95             #===================================
96             #===================================
97             $Locale::Maketext::Extract::Plugin::TT2::Parser::VERSION = '1.00';
98 4     4   52 use base 'Template::Parser';
  4         9  
  4         609  
99              
100             # disabled location() because it was adding unnecessary text
101             # to filter blocks
102             #===================================
103 24     24   1184 sub location {''}
104             #===================================
105              
106             # Custom TT parser for Locale::Maketext::Lexicon
107             #
108             # Written by Andy Wardley http://wardley.org/
109             #
110             # 18 September 2008
111             #
112              
113             #-----------------------------------------------------------------------
114             # custom directive generator to capture filters, variables and
115             # massage a few other elements to make life easy.
116             #-----------------------------------------------------------------------
117              
118             #===================================
119             #===================================
120             package Locale::Maketext::Extract::Plugin::TT2::Directive;
121             #===================================
122             #===================================
123             $Locale::Maketext::Extract::Plugin::TT2::Directive::VERSION = '1.00';
124 4     4   23 use base 'Template::Directive';
  4         8  
  4         12149  
125              
126             our $PARSER;
127              
128             #===================================
129             sub textblock {
130             #===================================
131 48     48   13049 my ( $class, $text ) = @_;
132 48         201 $text =~ s/([\\'])/\\$1/g;
133 48         210 return "'$text'";
134             }
135              
136             #===================================
137             sub ident {
138             #===================================
139 44     44   11924 my ( $class, $ident ) = @_;
140 44 50       144 return "NULL" unless @$ident;
141 44 100 100     267 if ( scalar @$ident <= 2 && !$ident->[1] ) {
142 18         39 my $var = $ident->[0];
143 18         118 $var =~ s/^'(.+)'$/$1/;
144 18         79 return $var;
145             }
146             else {
147 26         84 my @source = @$ident;
148 26         40 my @dotted;
149 26         40 my $first = 1;
150 26         32 my $first_literal;
151 26         81 while (@source) {
152 33         82 my ( $name, $args ) = splice( @source, 0, 2 );
153 33 100 100     120 if ($first) {
    100          
154 26         73 strip_quotes($name);
155 26 100 66     172 my $first_arg = $args && @$args ? $args->[0] : '';
156 26         57 $first_literal = strip_quotes($first_arg);
157 26         47 $first--;
158             }
159             elsif ( !strip_quotes($name) && $name =~ /\D/ ) {
160 1         3 $name = '$' . $name;
161             }
162 33         81 $name .= join_args($args);
163 33         118 push( @dotted, $name );
164             }
165              
166 26         40 my $got_i18n = 0;
167              
168             # Classic TT syntax [% l('...') %] or [% loc('....') %]
169 26 100 100     203 if ( $first_literal
    100 66        
      100        
      66        
170             && ( $ident->[0] eq "'l'" or $ident->[0] eq "'loc'" ) )
171             {
172 17         30 $got_i18n = 1;
173             }
174              
175             # Mojolicious TT syntax [% c.l('...') %]
176             elsif ( $ident->[0] eq "'c'"
177             && ( $ident->[2] eq "'l'" || $ident->[2] eq "'loc'" ) )
178             {
179 2         6 $got_i18n = 1;
180 2         6 splice( @$ident, 0, 2 );
181             }
182              
183 26 100       69 if ($got_i18n) {
184 19         28 my $string = shift @{ $ident->[1] };
  19         41  
185 19         39 strip_quotes($string);
186 19         49 $string =~ s/\\\\/\\/g;
187 19         37 my $args = join_args( $ident->[1] );
188 19         48 push @{ $PARSER->{extracted} },
  19         71  
189 19         34 [ $string, ${ $PARSER->{LINE} }, $args ];
190             }
191 26         151 return join( '.', @dotted );
192             }
193             }
194              
195             #===================================
196             sub text {
197             #===================================
198 13     13   4762 my ( $class, $text ) = @_;
199 13         38 $text =~ s/\\/\\\\/g;
200 13         58 return "'$text'";
201             }
202              
203             #===================================
204             sub quoted {
205             #===================================
206 13     13   1016 my ( $class, $items ) = @_;
207 13 50       43 return '' unless @$items;
208 13 100       68 return ( $items->[0] ) if scalar @$items == 1;
209 1         8 return '(' . join( ' _ ', @$items ) . ')';
210             }
211              
212             #===================================
213             sub args {
214             #===================================
215 54     54   9786 my ( $class, $args ) = @_;
216 54         95 my $hash = shift @$args;
217 54 100       167 push( @$args, '{ ' . join( ', ', @$hash ) . ' }' ) # named params
218             if @$hash;
219 54         234 return $args;
220             }
221              
222             #===================================
223             sub get {
224             #===================================
225 39     39   7286 my ( $class, $expr ) = @_;
226 39         147 return $expr;
227             }
228              
229             #===================================
230             sub filter {
231             #===================================
232 31     31   6715 my ( $class, $lnameargs, $block ) = @_;
233 31         67 my ( $name, $args, $alias ) = @$lnameargs;
234 31         65 $name = $name->[0];
235 31 50 100     151 return ''
      100        
      66        
236             unless $name eq "'l'"
237             or $name eq "'loc'"
238             or $name eq "'c.l'"
239             or $name eq "'c.loc'";
240              
241 31 100       84 if ( strip_quotes($block) ) {
242 29         81 $block =~ s/\\\\/\\/g;
243 29         104 $args = join_args( $class->args($args) );
244              
245             # NOTE: line number is at end of block, and can be a range
246 29         44 my ($end) = ( ${ $PARSER->{LINE} } =~ /^(\d+)/ );
  29         152  
247 29         44 my $start = $end;
248              
249             # rewind line count for newlines
250 29         73 $start -= $block =~ tr/\n//;
251 29 100       87 my $line = $start == $end ? $start : "$start-$end";
252 29         53 push @{ $PARSER->{extracted} }, [ $block, $line, $args ];
  29         112  
253              
254             }
255 31         127 return '';
256             }
257              
258             # strips outer single quotes from a string (modifies original string)
259             # returns true if stripped, or false
260             #===================================
261             sub strip_quotes {
262             #===================================
263 194     194   1088 return scalar $_[0] =~ s/^'(.*)'$/$1/s;
264             }
265              
266             #===================================
267             sub join_args {
268             #===================================
269 81     81   120 my $args = shift;
270 81 100 100     438 return '' unless $args && @$args;
271 62         143 my @new_args = (@$args);
272 62         126 for (@new_args) {
273 79         143 s/\\\\/\\/g;
274 79 100       130 if ( strip_quotes($_) ) {
275 57         110 s/"/\\"/g;
276 57         409 $_ = qq{"$_"};
277             }
278             }
279 62         265 return '(' . join( ', ', @new_args ) . ')';
280             }
281              
282              
283             1;
284              
285             __END__