File Coverage

blib/lib/Text/MicroMason/Base.pm
Criterion Covered Total %
statement 132 138 95.6
branch 46 68 67.6
condition 7 11 63.6
subroutine 25 27 92.5
pod 17 20 85.0
total 227 264 85.9


line stmt bran cond sub pod time code
1             package Text::MicroMason::Base;
2              
3 39     39   230 use strict;
  39         68  
  39         1309  
4             require Carp;
5              
6             ######################################################################
7              
8             ######################################################################
9              
10 39     39   14445 use Class::MixinFactory -hasafactory;
  39         105736  
  39         214  
11             for my $factory ( (__PACKAGE__)->mixin_factory ) {
12             $factory->base_class( "Text::MicroMason::Base" );
13             $factory->mixin_prefix( "Text::MicroMason" );
14             }
15              
16             ######################################################################
17              
18             ######################################################################
19              
20             sub new {
21 163     163 1 7503 my $callee = shift;
22 163         251 my ( @traits, @attribs );
23 163         724 while ( scalar @_ ) {
24 302 100       1112 if ( $_[0] =~ /^\-(\w+)$/ ) {
25 264         655 push @traits, $1;
26 264         512 shift;
27             } else {
28 38         126 push @attribs, splice(@_, 0, 2);
29             }
30             }
31 163 100       348 if ( scalar @traits ) {
32 153 50       373 die("Adding moxins to an existing class not supported yet!")
33             unless ( $callee eq __PACKAGE__ );
34 153         611 $callee->class( @traits )->create( @attribs )
35             } else {
36 10         31 $callee->create( @attribs )
37             }
38             }
39              
40             ######################################################################
41              
42             # $mason = $class->create( %options );
43             # $clone = $object->create( %options );
44             sub create {
45 203     203 1 11283 my $referent = shift;
46 203 100       465 if ( ! ref $referent ) {
47 154         428 bless { $referent->defaults(), @_ }, $referent;
48             } else {
49 49         98 bless { $referent->defaults(), %$referent, @_ }, ref $referent;
50             }
51             }
52              
53             sub defaults {
54             return ()
55 203     203 1 1986 }
56              
57             ######################################################################
58              
59             ######################################################################
60              
61             # $code_ref = $mason->compile( text => $template, %options );
62             # $code_ref = $mason->compile( file => $filename, %options );
63             # $code_ref = $mason->compile( handle => $filehandle, %options );
64             sub compile {
65 311     311 1 100338 my ( $self, $src_type, $src_data, %options ) = @_;
66              
67 311         888 ($self, $src_type, $src_data) = $self->prepare($src_type, $src_data,%options);
68            
69 311         782 my $code = $self->interpret( $src_type, $src_data );
70            
71 311 100       724 $self->eval_sub( $code )
72             or $self->croak_msg( "MicroMason compilation failed: $@\n". _number_lines($code)."\n" );
73              
74             }
75              
76             # Internal helper to number the lines in the compiled template when compilation croaks
77             sub _number_lines {
78 17     17   180 my $code = shift;
79              
80 17         25 my $n = 0;
81 17         86 return join("\n", map { sprintf("%4d %s", $n++, $_) } split(/\n/, $code)).
  204         619  
82             "\n** Please use Text::MicroMason->new\(-LineNumbers\) for better diagnostics!";
83             }
84              
85              
86             ######################################################################
87              
88             # $result = $mason->execute( code => $subref, @arguments );
89             # $result = $mason->execute( $src_type, $src_data, @arguments );
90             # $result = $mason->execute( $src_type, $src_data, \%options, @arguments );
91             sub execute {
92 262     262 1 238625 my $self = shift;
93 4         5 my $sub = ( $_[0] eq 'code' ) ? do { shift; shift } :
  4         9  
94 262 100       1003 $self->compile( shift, shift, ref($_[0]) ? %{ shift() } : () )
  1 100       3  
    100          
95             or $self->croak_msg("MicroMason compilation failed: $@");
96 252         7267 &$sub( @_ );
97             }
98              
99             ######################################################################
100              
101             ######################################################################
102              
103             # ($self, $src_type, $src_data) = $self->prepare($src_type, $src_data, %options)
104             sub prepare {
105 311     311 1 2371 my ( $self, $src_type, $src_data, %options ) = @_;
106 311 100       761 $self = $self->create( %options ) if ( scalar keys %options );
107 311         922 return ( $self, $src_type, $src_data );
108             }
109              
110             ######################################################################
111              
112             # $perl_code = $mason->interpret( $src_type, $src_data );
113             sub interpret {
114 313     313 1 557 my ( $self, $src_type, $src_data ) = @_;
115 313         677 my $template = $self->read( $src_type, $src_data );
116 313         783 my @tokens = $self->lex( $template );
117 313         911 my $code = $self->assemble( @tokens );
118              
119             # Source file and line number
120 313         988 my $source_line = $self->source_file_line_label( $src_type, $src_data );
121            
122 313         1051 return $source_line . "\n" . $code;
123             }
124              
125             # $line_number_comment = $mason->source_file_line_label( $src_type, $src_data );
126             sub source_file_line_label {
127 313     313 0 567 my ( $self, $src_type, $src_data ) = @_;
128              
129 313 100       660 if ( $src_type eq 'file' ) {
130 47         155 return qq(# line 1 "$src_data");
131             }
132            
133 266         353 my @caller;
134             my $call_level;
135 266   100     303 do { @caller = caller( ++ $call_level ) }
  951         7335  
136             while ( $caller[0] =~ /^Text::MicroMason/ or $self->isa($caller[0]) );
137 266   33     598 my $package = ( $caller[1] || $0 );
138 266         928 qq{# line 1 "text template (compiled at $package line $caller[2])"}
139             }
140              
141              
142             ######################################################################
143              
144             # $code_ref = $mason->eval_sub( $perl_code );
145             sub eval_sub {
146 292     292 1 450 my $m = shift;
147             package Text::MicroMason::Commands;
148             eval( shift )
149 292     1   69404 }
  1         8  
  1         3  
  1         56  
150              
151             ######################################################################
152              
153             ######################################################################
154              
155             # $template = $mason->read( $src_type, $src_data );
156             sub read {
157 313     313 1 764 my ( $self, $src_type, $src_data ) = @_;
158              
159 313         556 my $src_method = "read_$src_type";
160 313 50       1240 $self->can($src_method)
161             or $self->croak_msg("Unsupported source type '$src_type'");
162 313         711 $self->$src_method( $src_data );
163             }
164              
165             # $template = $mason->read_text( $template );
166             sub read_text {
167 263 50   263 1 822 ref($_[1]) ? $$_[1] : $_[1];
168             }
169              
170             # $contents = $mason->read_file( $filename );
171             sub read_file {
172 47     47 1 1093 my ( $self, $file ) = @_;
173 47         103 local *FILE;
174 47 50       1429 open FILE, "$file" or $self->croak_msg("MicroMason can't open $file: $!");
175 47         244 local $/ = undef;
176 47         920 local $_ = ;
177 47 50       397 close FILE or $self->croak_msg("MicroMason can't close $file: $!");;
178 47         323 return $_;
179             }
180              
181             # $contents = $mason->read_handle( $filehandle );
182             sub read_handle {
183 3     3 1 8 my ( $self, $handle ) = @_;
184 3 50       11 my $fh = (ref $handle eq 'GLOB') ? $handle : $$handle;
185 3         10 local $/ = undef;
186             <$fh>
187 3         80 }
188              
189             ######################################################################
190              
191             # @token_pairs = $mason->lex( $template );
192             sub lex {
193 299     299 1 426 my $self = shift;
194 299         540 local $_ = "$_[0]";
195 299         381 my @tokens;
196 299 50       831 my $lexer = $self->can('lex_token')
197             or $self->croak_msg('Unable to lex_token(); must select a syntax mixin');
198             # warn "Lexing: " . pos($_) . " of " . length($_) . "\n";
199 299         1059 until ( /\G\z/gc ) {
200 1161 50 0     2031 my @parsed = &$lexer( $self ) or
201             /\G ( .{0,20} ) /gcxs
202             && die "MicroMason parsing halted at '$1'\n";
203 1161         3372 push @tokens, @parsed;
204             }
205 299         1058 return @tokens;
206             }
207              
208             # ( $type, $value ) = $mason->lex_token();
209             sub lex_token {
210 0     0 1 0 die "The lex_token() method is abstract and must be provided by a subclass";
211             }
212              
213             ######################################################################
214              
215             ######################################################################
216              
217             # Text elements used for subroutine assembly
218             sub assembler_rules {
219             template => [ qw( $sub_start $init_errs $init_output
220             $init_args @perl $return_output $sub_end ) ],
221              
222             # Subroutine scafolding
223             sub_start => 'sub { ',
224             sub_end => '}',
225             init_errs =>
226             'local $SIG{__DIE__} = sub { die "MicroMason execution failed: ", @_ };',
227            
228             # Argument processing elements
229             init_args => 'my %ARGS = @_ if ($#_ % 2);',
230            
231             # Output generation
232 308 50   308   424 init_output => sub { my $m = shift; my $sub = $m->{output_sub} ? '$m->{output_sub}' : 'sub {push @OUT, @_}'; 'my @OUT; my $_out = ' . $sub . ';' },
  308         625  
  308         928  
233 308 50   308   411 add_output => sub { my $m = shift; $m->{output_sub} ? '&$_out' : 'push @OUT,' },
  308         863  
234 313     313 1 12679 return_output => 'join("", @OUT)',
235              
236             # Mapping between token types
237             text_token => 'perl OUT( QUOTED );',
238             expr_token => "perl OUT( \"\".do{\nTOKEN\n} );",
239             # the "". here forces string context, and should hopefully make
240             # 'uninitialized' warnings appear closer to their source, rather
241             # than at the big join "", @OUT; at the end
242             file_token => "perl OUT( \$m->execute( file => do {\nTOKEN\n} ) );",
243             # Note that we need newline after TOKEN here in case it ends with a comment.
244             }
245              
246             sub assembler_vars {
247 313     313 0 393 my $self = shift;
248 313         757 my %assembler = $self->assembler_rules();
249            
250 313         735 my @assembly = @{ delete $assembler{ template } };
  313         957  
251            
252 1047         3534 my %token_map = map { ( /^(.*?)_token$/ )[0] => delete $assembler{$_} }
253 313         971 grep { /_token$/ } keys %assembler;
  3266         5788  
254              
255 313 100       950 my %fragments = map { $_ => map { ref($_) ? &{$_}( $self ) : $_ } $assembler{$_} } keys %assembler;
  2219         2604  
  2219         3956  
  616         931  
256              
257 313         2299 return( \@assembly, \%fragments, \%token_map );
258             }
259              
260             # $perl_code = $mason->assemble( @tokens );
261             sub assemble {
262 313     313 1 2153 my $self = shift;
263 313         756 my @tokens = @_;
264            
265 313         775 my ( $order, $fragments, $token_map ) = $self->assembler_vars();
266            
267 313         613 my %token_streams = map { $_ => [] } map { ( /^\W?\@(\w+)$/ ) } @$order;
  1497         2747  
  3403         5474  
268              
269 313         889 while ( scalar @tokens ) {
270 1228         2162 my ( $type, $token ) = splice( @tokens, 0, 2 );
271            
272 1228 100 100     3419 unless ( $token_streams{$type} or $token_map->{$type} ) {
273 40         55 my $method = "assemble_$type";
274 40 50       132 my $sub = $self->can( $method )
275             or $self->croak_msg( "Unexpected token type '$type': '$token'" );
276 40         88 ($type, $token) = &$sub( $self, $token );
277             }
278            
279 1228 100       2238 if ( my $typedef = $token_map->{ $type } ) {
280             # Perform token map substitution in a single pass so that uses of
281             # OUT in the token text are not improperly converted to output calls.
282             # -- Simon, 2009-11-14
283             my %substitution_map = (
284             'OUT' => $fragments->{add_output},
285 959         2964 'TOKEN' => $token,
286             'QUOTED' => "qq(\Q$token\E)",
287             );
288 959         6007 $typedef =~ s/\b(OUT|TOKEN|QUOTED)\b/$substitution_map{$1}/g;
289            
290 959         2947 ( $type, $token ) = split ' ', $typedef, 2;
291             }
292            
293 1228 50       2269 my $ary = $token_streams{$type}
294             or $self->croak_msg( "Unexpected token type '$type': '$token'" );
295            
296 1228         2921 push @$ary, $token
297             }
298            
299             join( "\n", map {
300 313 50       567 /^(\W+)(\w+)$/ or $self->croak_msg("Can't assemble $_");
  3403         7458  
301 3403 100       6371 if ( $1 eq '$' ) {
    100          
    100          
    50          
302 1906         3351 $fragments->{ $2 }
303             } elsif ( $1 eq '@' ) {
304 905         941 @{ $token_streams{ $2 } }
  905         1774  
305             } elsif ( $1 eq '!@' ) {
306 296         333 reverse @{ $token_streams{ $2 } }
  296         526  
307             } elsif ( $1 eq '-@' ) {
308             ()
309 296         2196 } else {
310 0         0 $self->croak_msg("Can't assemble $_");
311             }
312             } @$order );
313             }
314              
315             ######################################################################
316              
317             ######################################################################
318              
319             sub croak_msg {
320 22     22 1 61 local $Carp::CarpLevel = 2;
321 22 50       4074 shift and Carp::croak( ( @_ == 1 ) ? $_[0] : join(' ', map _printable(), @_) )
    50          
322             }
323              
324             my %Escape = (
325             ( map { chr($_), unpack('H2', chr($_)) } (0..255) ),
326             "\\"=>'\\', "\r"=>'r', "\n"=>'n', "\t"=>'t', "\""=>'"'
327             );
328              
329             # $special_characters_escaped = _printable( $source_string );
330             sub _printable {
331 0 0   0   0 local $_ = scalar(@_) ? (shift) : $_;
332 0 0       0 return "(undef)" unless defined;
333 0         0 s/([\r\n\t\"\\\x00-\x1f\x7F-\xFF])/\\$Escape{$1}/sgo;
334 0 0       0 /[^\w\d\-\:\.\']/ ? "q($_)" : $_;
335             }
336              
337             ######################################################################
338              
339              
340             sub cache_key {
341 52     52 0 133 my $self = shift;
342 52         71 my ($src_type, $src_data, %options) = @_;
343              
344 52         84 return $src_data;
345             }
346              
347              
348             1;
349              
350             __END__