File Coverage

blib/lib/Text/Template/Simple/Tokenizer.pm
Criterion Covered Total %
statement 154 191 80.6
branch 70 90 77.7
condition 20 31 64.5
subroutine 27 34 79.4
pod 4 4 100.0
total 275 350 78.5


line stmt bran cond sub pod time code
1             package Text::Template::Simple::Tokenizer;
2 60     60   217 use strict;
  60         60  
  60         1321  
3 60     60   186 use warnings;
  60         55  
  60         1928  
4              
5             our $VERSION = '0.90';
6              
7 60     60   191 use constant CMD_CHAR => 0;
  60         59  
  60         2669  
8 60     60   223 use constant CMD_ID => 1;
  60         67  
  60         2184  
9 60     60   208 use constant CMD_CALLBACK => 2;
  60         71  
  60         2284  
10              
11 60     60   206 use constant ID_DS => 0;
  60         65  
  60         2068  
12 60     60   198 use constant ID_DE => 1;
  60         75  
  60         2080  
13 60     60   189 use constant ID_PRE_CHOMP => 2;
  60         53  
  60         2111  
14 60     60   188 use constant ID_POST_CHOMP => 3;
  60         61  
  60         2005  
15              
16 60     60   181 use constant SUBSTR_OFFSET_FIRST => 0;
  60         68  
  60         1935  
17 60     60   195 use constant SUBSTR_OFFSET_SECOND => 1;
  60         60  
  60         1991  
18 60     60   180 use constant SUBSTR_LENGTH => 1;
  60         56  
  60         2090  
19              
20 60     60   216 use Text::Template::Simple::Util qw( LOG DEBUG fatal );
  60         64  
  60         2674  
21 60     60   213 use Text::Template::Simple::Constants qw( :all );
  60         71  
  60         83304  
22              
23             my @COMMANDS = ( # default command list
24             # command id
25             [ DIR_CAPTURE , T_CAPTURE ],
26             [ DIR_DYNAMIC , T_DYNAMIC, ],
27             [ DIR_STATIC , T_STATIC, ],
28             [ DIR_NOTADELIM, T_NOTADELIM ],
29             [ DIR_COMMENT , T_COMMENT ],
30             [ DIR_COMMAND , T_COMMAND ],
31             );
32              
33             sub new {
34 502     502 1 462 my $class = shift;
35 502         522 my $self = [];
36 502         591 bless $self, $class;
37 502   33     1171 $self->[ID_DS] = shift || fatal('tts.tokenizer.new.ds');
38 502   33     814 $self->[ID_DE] = shift || fatal('tts.tokenizer.new.de');
39 502   100     1411 $self->[ID_PRE_CHOMP] = shift || CHOMP_NONE;
40 502   100     1235 $self->[ID_POST_CHOMP] = shift || CHOMP_NONE;
41 502         774 return $self;
42             }
43              
44             sub tokenize {
45             # compile the template into a tree and optimize
46 502     502 1 559 my($self, $tmp, $map_keys) = @_;
47              
48 502 100       874 return $self->_empty_token( $tmp ) if ! $tmp;
49              
50 498         432 my($ds, $de) = @{ $self }[ ID_DS, ID_DE ];
  498         767  
51 498         617 my($qds, $qde) = map { quotemeta $_ } $ds, $de;
  996         1875  
52              
53 498         520 my(@tokens, $inside);
54              
55 498         3654 OUT_TOKEN: foreach my $i ( split /($qds)/xms, $tmp ) {
56              
57 1710 100       2531 if ( $i eq $ds ) {
58 606         1311 push @tokens, [ $i, T_DELIMSTART, [], undef ];
59 606         548 $inside = 1;
60 606         829 next OUT_TOKEN;
61             }
62              
63 1104         3559 IN_TOKEN: foreach my $j ( split /($qde)/xms, $i ) {
64 1592 100       2438 if ( $j eq $de ) {
65 604         634 my $last_token = $tokens[LAST_TOKEN];
66 604 100       976 if ( T_NOTADELIM == $last_token->[TOKEN_ID] ) {
67 20         53 $last_token->[TOKEN_STR] = $self->tilde(
68             $last_token->[TOKEN_STR] . $de
69             );
70             }
71             else {
72 584         1143 push @tokens, [ $j, T_DELIMEND, [], undef ];
73             }
74 604         564 $inside = 0;
75 604         1184 next IN_TOKEN;
76             }
77 988         1896 push @tokens, $self->_token_code( $j, $inside, $map_keys, \@tokens );
78             }
79             }
80              
81 498 50       1976 $self->_debug_tokens( \@tokens ) if $self->can('DEBUG_TOKENS');
82              
83 498         1500 return \@tokens;
84             }
85              
86             sub tilde {
87 402     402 1 638 my(undef, @args) = @_;
88 402         865 return Text::Template::Simple::Util::escape( q{~} => @args );
89             }
90              
91             sub quote {
92 0     0 1 0 my(undef, @args) = @_;
93 0         0 return Text::Template::Simple::Util::escape( q{"} => @args );
94             }
95              
96             sub _empty_token {
97 4     4   6 my $self = shift;
98 4         5 my $tmp = shift;
99 4 50       7 fatal('tts.tokenizer.tokenize.tmp') if ! defined $tmp;
100             # empty string or zero
101             return [
102 4         23 [ $self->[ID_DS], T_DELIMSTART, [], undef ],
103             [ $tmp , T_RAW , [], undef ],
104             [ $self->[ID_DE], T_DELIMEND , [], undef ],
105             ]
106             }
107              
108             sub _get_command_chars {
109 988     988   854 my($self, $str) = @_;
110             return
111 988 100       4408 $str ne EMPTY_STRING # left
    100          
    100          
112             ? substr $str, SUBSTR_OFFSET_FIRST , SUBSTR_LENGTH : EMPTY_STRING,
113             $str ne EMPTY_STRING # extra
114             ? substr $str, SUBSTR_OFFSET_SECOND, SUBSTR_LENGTH : EMPTY_STRING,
115             $str ne EMPTY_STRING # right
116             ? substr $str, length($str) - 1 , SUBSTR_LENGTH : EMPTY_STRING,
117             ;
118             }
119              
120             sub _user_commands {
121 606     606   1009 my $self = shift;
122 606 50       2506 return +() if ! $self->can('commands');
123 0         0 return $self->commands;
124             }
125              
126             sub _token_for_command {
127 524     524   776 my($self, $tree, $map_keys, $str, $last_cmd, $second_cmd, $cmd, $inside) = @_;
128 524         981 my($copen, $cclose, $ctoken) = $self->_chomp_token( $second_cmd, $last_cmd );
129 524         734 my $len = length $str;
130 524 50       747 my $cb = $map_keys ? 'quote' : $cmd->[CMD_CALLBACK];
131 524 100       619 my $soff = $copen ? 2 : 1;
132 524 100       715 my $slen = $len - ($cclose ? $soff+1 : 1);
133 524         907 my $buf = substr $str, $soff, $slen;
134              
135 524 100       1040 if ( T_NOTADELIM == $cmd->[CMD_ID] ) {
136 22         40 $buf = $self->[ID_DS] . $buf;
137 22         34 $tree->[LAST_TOKEN][TOKEN_ID] = T_DISCARD;
138             }
139              
140 524         507 my $needs_chomp = defined $ctoken;
141 524 50       1534 $self->_chomp_prev($tree, $ctoken) if $needs_chomp;
142              
143 524 50       754 my $id = $map_keys ? T_RAW : $cmd->[CMD_ID];
144 524 50       704 my $val = $cb ? $self->$cb( $buf ) : $buf;
145              
146             return [
147 524 50       2871 $val,
148             $id,
149             [ (CHOMP_NONE) x 2 ],
150             $needs_chomp ? $ctoken : undef # trigger
151             ];
152             }
153              
154             sub _token_for_code {
155 82     82   136 my($self, $tree, $map_keys, $str, $last_cmd, $first_cmd) = @_;
156 82         184 my($copen, $cclose, $ctoken) = $self->_chomp_token( $first_cmd, $last_cmd );
157 82         409 my $len = length $str;
158 82 100       150 my $soff = $copen ? 1 : 0;
159 82 100       155 my $slen = $len - ( $cclose ? $soff+1 : 0 );
160              
161 82         92 my $needs_chomp = defined $ctoken;
162 82 50       237 $self->_chomp_prev($tree, $ctoken) if $needs_chomp;
163              
164             return [
165 82 100       546 substr($str, $soff, $slen),
    50          
166             $map_keys ? T_MAPKEY : T_CODE,
167             [ (CHOMP_NONE) x 2 ],
168             $needs_chomp ? $ctoken : undef # trigger
169             ];
170             }
171              
172             sub _token_code {
173 988     988   1197 my($self, $str, $inside, $map_keys, $tree) = @_;
174 988         1352 my($first_cmd, $second_cmd, $last_cmd) = $self->_get_command_chars( $str );
175              
176 988 100       1654 if ( $inside ) {
177 606         962 my @common = ($tree, $map_keys, $str, $last_cmd);
178 606         1045 foreach my $cmd ( @COMMANDS, $self->_user_commands ) {
179 1404 100       2379 next if $first_cmd ne $cmd->[CMD_CHAR];
180 524         885 return $self->_token_for_command( @common, $second_cmd, $cmd, $inside );
181             }
182 82         206 return $self->_token_for_code( @common, $first_cmd );
183             }
184              
185 382         410 my $prev = $tree->[PREVIOUS_TOKEN];
186              
187             return [
188 382 100       562 $self->tilde( $str ),
189             T_RAW,
190             [ $prev ? $prev->[TOKEN_TRIGGER] : undef, CHOMP_NONE ],
191             undef # trigger
192             ];
193             }
194              
195             sub _chomp_token {
196 606     606   602 my($self, $open_tok, $close_tok) = @_;
197 606         736 my($pre, $post) = ( $self->[ID_PRE_CHOMP], $self->[ID_POST_CHOMP] );
198 606         457 my $c = CHOMP_NONE;
199              
200             my $copen = $open_tok eq DIR_CHOMP_NONE ? RESET_FIELD
201 8         9 : $open_tok eq DIR_COLLAPSE ? do { $c |= COLLAPSE_LEFT; 1 }
  8         9  
202 4         6 : $pre & COLLAPSE_ALL ? do { $c |= COLLAPSE_LEFT; 1 }
  4         4  
203 4         5 : $pre & CHOMP_ALL ? do { $c |= CHOMP_LEFT; 1 }
  4         7  
204 606 100       2250 : $open_tok eq DIR_CHOMP ? do { $c |= CHOMP_LEFT; 1 }
  10 100       9  
  10 100       12  
    100          
    50          
205             : 0
206             ;
207              
208             my $cclose = $close_tok eq DIR_CHOMP_NONE ? RESET_FIELD
209 6         5 : $close_tok eq DIR_COLLAPSE ? do { $c |= COLLAPSE_RIGHT; 1 }
  6         7  
210 4         5 : $post & COLLAPSE_ALL ? do { $c |= COLLAPSE_RIGHT; 1 }
  4         4  
211 4         5 : $post & CHOMP_ALL ? do { $c |= CHOMP_RIGHT; 1 }
  4         3  
212 606 100       1981 : $close_tok eq DIR_CHOMP ? do { $c |= CHOMP_RIGHT; 1 }
  32 100       31  
  32 100       38  
    100          
    50          
213             : 0
214             ;
215              
216 606   100     1264 my $cboth = $copen > 0 && $cclose > 0;
217              
218 606 100 100     1447 $c |= COLLAPSE_ALL if ( ( $c & COLLAPSE_LEFT ) && ( $c & COLLAPSE_RIGHT ) );
219 606 100 100     1276 $c |= CHOMP_ALL if ( ( $c & CHOMP_LEFT ) && ( $c & CHOMP_RIGHT ) );
220              
221 606   100     2104 return $copen, $cclose, $c || CHOMP_NONE;
222             }
223              
224             sub _chomp_prev {
225 606     606   543 my($self, $tree, $ctoken) = @_;
226 606   100     1099 my $prev = $tree->[PREVIOUS_TOKEN] || return; # no previous if this is first
227 302 100       498 return if T_RAW != $prev->[TOKEN_ID]; # only RAWs can be chomped
228              
229 300         270 my $tc_prev = $prev->[TOKEN_CHOMP][TOKEN_CHOMP_PREV];
230 300         256 my $tc_next = $prev->[TOKEN_CHOMP][TOKEN_CHOMP_NEXT];
231              
232 300 100       840 $prev->[TOKEN_CHOMP] = [
    50          
233             $tc_next ? $tc_next : CHOMP_NONE,
234             $tc_prev ? $tc_prev | $ctoken : $ctoken
235             ];
236 300         532 return;
237             }
238              
239             sub _get_symbols {
240             # fetch the related constants
241 0     0   0 my $self = shift;
242 0   0     0 my $regex = shift || fatal('tts.tokenizer._get_symbols.regex');
243 60     60   307 no strict qw( refs );
  60         78  
  60         26281  
244 0         0 return grep { $_ =~ $regex } keys %{ ref($self) . q{::} };
  0         0  
  0         0  
245             }
246              
247             sub _visualize_chomp {
248 0     0   0 my $self = shift;
249 0         0 my $param = shift;
250 0 0       0 return 'undef' if ! defined $param;
251              
252 0         0 my @test = map { $_->[0] }
253 0         0 grep { $param & $_->[1] }
254 0         0 map { [ $_, $self->$_() ] }
  0         0  
255             $self->_get_symbols( qr{ \A (?: CHOMP|COLLAPSE ) }xms );
256              
257 0 0       0 return @test ? join( q{,}, @test ) : 'undef';
258             }
259              
260             sub _visualize_tid {
261 0     0   0 my $self = shift;
262 0         0 my $id = shift;
263             my @ids = (
264             undef,
265 0         0 sort { $self->$a() <=> $self->$b() }
266 0         0 grep { $_ ne 'T_MAXID' }
  0         0  
267             $self->_get_symbols( qr{ \A (?: T_ ) }xms )
268             );
269              
270 0   0     0 my $rv = $ids[ $id ] || ( defined $id ? $id : 'undef' );
271 0         0 return $rv;
272             }
273              
274             sub _debug_tokens {
275 0     0   0 my $self = shift;
276 0         0 my $tokens = shift;
277 0         0 my $buf = $self->_debug_tokens_head;
278              
279 0         0 foreach my $t ( @{ $tokens } ) {
  0         0  
280             $buf .= $self->_debug_tokens_row(
281             $self->_visualize_tid( $t->[TOKEN_ID] ),
282             Text::Template::Simple::Util::visualize_whitespace(
283             $t->[TOKEN_STR]
284             ),
285 0 0       0 map { $_ eq 'undef' ? EMPTY_STRING : $_ }
286 0         0 map { $self->_visualize_chomp( $_ ) }
  0         0  
287             $t->[TOKEN_CHOMP][TOKEN_CHOMP_NEXT],
288             $t->[TOKEN_CHOMP][TOKEN_CHOMP_PREV],
289             $t->[TOKEN_TRIGGER]
290             );
291             }
292 0         0 Text::Template::Simple::Util::LOG( DEBUG => $buf );
293 0         0 return;
294             }
295              
296             sub _debug_tokens_head {
297 0     0   0 my $self = shift;
298 0         0 return <<'HEAD';
299              
300             ---------------------------
301             TOKEN DUMP
302             ---------------------------
303             HEAD
304             }
305              
306             sub _debug_tokens_row {
307 0     0   0 my($self, @params) = @_;
308 0         0 return sprintf <<'DUMP', @params;
309             ID : %s
310             STRING : %s
311             CHOMP_NEXT: %s
312             CHOMP_PREV: %s
313             TRIGGER : %s
314             ---------------------------
315             DUMP
316             }
317              
318             sub DESTROY {
319 502   50 502   916 my $self = shift || return;
320 502 50       934 LOG( DESTROY => ref $self ) if DEBUG;
321 502         1479 return;
322             }
323              
324             1;
325              
326             __END__
327              
328             =head1 NAME
329              
330             Text::Template::Simple::Tokenizer - C<Tokenizer>
331              
332             =head1 SYNOPSIS
333              
334             use strict;
335             use warnings;
336             use Text::Template::Simple::Constants qw( :token );
337             use Text::Template::Simple::Tokenizer;
338             my $t = Text::Template::Simple::Tokenizer->new( $start_delim, $end_delim );
339             foreach my $token ( @{ $t->tokenize( $raw_data ) } ) {
340             printf "Token type: %s\n", $token->[TOKEN_ID];
341             printf "Token data: %s\n", $token->[TOKEN_STR];
342             }
343              
344             =head1 DESCRIPTION
345              
346             This document describes version C<0.90> of C<Text::Template::Simple::Tokenizer>
347             released on C<5 July 2016>.
348              
349             Splits the input into tokens with the defined delimiter pair.
350              
351             =head1 METHODS
352              
353             =head2 new
354              
355             The object constructor. Accepts two parameters in this order:
356             C<start_delimiter> and C<end_delimiter>.
357              
358             =head2 C<tokenize>
359              
360             Splits the input into tokens with the supplied delimiter pair. Accepts a single
361             parameter: the raw template string.
362              
363             =head2 ESCAPE METHODS
364              
365             =head2 tilde
366              
367             Escapes the tilde character.
368              
369             =head3 quote
370              
371             Escapes double quotes.
372              
373             =head1 AUTHOR
374              
375             Burak Gursoy <burak@cpan.org>.
376              
377             =head1 COPYRIGHT
378              
379             Copyright 2004 - 2016 Burak Gursoy. All rights reserved.
380              
381             =head1 LICENSE
382              
383             This library is free software; you can redistribute it and/or modify
384             it under the same terms as Perl itself, either Perl version 5.24.0 or,
385             at your option, any later version of Perl 5 you may have available.
386             =cut