File Coverage

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


line stmt bran cond sub pod time code
1             package Text::MicroMason::Base;
2              
3 39     39   125 use strict;
  39         37  
  39         1172  
4             require Carp;
5              
6             ######################################################################
7              
8             ######################################################################
9              
10 39     39   14782 use Class::MixinFactory -hasafactory;
  39         100470  
  39         244  
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 145     145 1 4651 my $callee = shift;
22 145         126 my ( @traits, @attribs );
23 145         312 while ( scalar @_ ) {
24 247 100       760 if ( $_[0] =~ /^\-(\w+)$/ ) {
25 217         515 push @traits, $1;
26 217         356 shift;
27             } else {
28 30         72 push @attribs, splice(@_, 0, 2);
29             }
30             }
31 145 100       221 if ( scalar @traits ) {
32 135 50       283 die("Adding moxins to an existing class not supported yet!")
33             unless ( $callee eq __PACKAGE__ );
34 135         424 $callee->class( @traits )->create( @attribs )
35             } else {
36 10         23 $callee->create( @attribs )
37             }
38             }
39              
40             ######################################################################
41              
42             # $mason = $class->create( %options );
43             # $clone = $object->create( %options );
44             sub create {
45 185     185 1 6210 my $referent = shift;
46 185 100       324 if ( ! ref $referent ) {
47 136         284 bless { $referent->defaults(), @_ }, $referent;
48             } else {
49 49         71 bless { $referent->defaults(), %$referent, @_ }, ref $referent;
50             }
51             }
52              
53             sub defaults {
54             return ()
55 185     185 1 1409 }
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 292     292 1 41885 my ( $self, $src_type, $src_data, %options ) = @_;
66              
67 292         652 ($self, $src_type, $src_data) = $self->prepare($src_type, $src_data,%options);
68            
69 292         549 my $code = $self->interpret( $src_type, $src_data );
70            
71 292 100       521 $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 14     14   979 my $code = shift;
79              
80 14         15 my $n = 0;
81 14         54 return join("\n", map { sprintf("%4d %s", $n++, $_) } split(/\n/, $code)).
  174         358  
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 249     249 1 150331 my $self = shift;
93 4         4 my $sub = ( $_[0] eq 'code' ) ? do { shift; shift } :
  4         10  
94 249 100       857 $self->compile( shift, shift, ref($_[0]) ? %{ shift() } : () )
  1 100       15  
    100          
95             or $self->croak_msg("MicroMason compilation failed: $@");
96 242         3883 &$sub( @_ );
97             }
98              
99             ######################################################################
100              
101             ######################################################################
102              
103             # ($self, $src_type, $src_data) = $self->prepare($src_type, $src_data, %options)
104             sub prepare {
105 292     292 1 1672 my ( $self, $src_type, $src_data, %options ) = @_;
106 292 100       620 $self = $self->create( %options ) if ( scalar keys %options );
107 292         701 return ( $self, $src_type, $src_data );
108             }
109              
110             ######################################################################
111              
112             # $perl_code = $mason->interpret( $src_type, $src_data );
113             sub interpret {
114 294     294 1 301 my ( $self, $src_type, $src_data ) = @_;
115 294         475 my $template = $self->read( $src_type, $src_data );
116 294         573 my @tokens = $self->lex( $template );
117 294         690 my $code = $self->assemble( @tokens );
118              
119             # Source file and line number
120 294         777 my $source_line = $self->source_file_line_label( $src_type, $src_data );
121            
122 294         738 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 294     294 0 316 my ( $self, $src_type, $src_data ) = @_;
128              
129 294 100       491 if ( $src_type eq 'file' ) {
130 47         93 return qq(# line 1 "$src_data");
131             }
132            
133 247         171 my @caller;
134             my $call_level;
135 247   100     175 do { @caller = caller( ++ $call_level ) }
  828         6902  
136             while ( $caller[0] =~ /^Text::MicroMason/ or $self->isa($caller[0]) );
137 247   33     427 my $package = ( $caller[1] || $0 );
138 247         720 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 291     291 1 267 my $m = shift;
147             package Text::MicroMason::Commands;
148             eval( shift )
149 291     1   63889 }
  1         4  
  1         1  
  1         43  
150              
151             ######################################################################
152              
153             ######################################################################
154              
155             # $template = $mason->read( $src_type, $src_data );
156             sub read {
157 294     294 1 546 my ( $self, $src_type, $src_data ) = @_;
158              
159 294         372 my $src_method = "read_$src_type";
160 294 50       1040 $self->can($src_method)
161             or $self->croak_msg("Unsupported source type '$src_type'");
162 294         507 $self->$src_method( $src_data );
163             }
164              
165             # $template = $mason->read_text( $template );
166             sub read_text {
167 244 50   244 1 650 ref($_[1]) ? $$_[1] : $_[1];
168             }
169              
170             # $contents = $mason->read_file( $filename );
171             sub read_file {
172 47     47 1 748 my ( $self, $file ) = @_;
173 47         81 local *FILE;
174 47 50       1165 open FILE, "$file" or $self->croak_msg("MicroMason can't open $file: $!");
175 47         147 local $/ = undef;
176 47         573 local $_ = ;
177 47 50       304 close FILE or $self->croak_msg("MicroMason can't close $file: $!");;
178 47         253 return $_;
179             }
180              
181             # $contents = $mason->read_handle( $filehandle );
182             sub read_handle {
183 3     3 1 4 my ( $self, $handle ) = @_;
184 3 50       10 my $fh = (ref $handle eq 'GLOB') ? $handle : $$handle;
185 3         8 local $/ = undef;
186             <$fh>
187 3         53 }
188              
189             ######################################################################
190              
191             # @token_pairs = $mason->lex( $template );
192             sub lex {
193 280     280 1 257 my $self = shift;
194 280         391 local $_ = "$_[0]";
195 280         217 my @tokens;
196 280 50       725 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 280         753 until ( /\G\z/gc ) {
200 1118 50 0     1694 my @parsed = &$lexer( $self ) or
201             /\G ( .{0,20} ) /gcxs
202             && die "MicroMason parsing halted at '$1'\n";
203 1118         2773 push @tokens, @parsed;
204             }
205 280         882 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 289 50   289   230 init_output => sub { my $m = shift; my $sub = $m->{output_sub} ? '$m->{output_sub}' : 'sub {push @OUT, @_}'; 'my @OUT; my $_out = ' . $sub . ';' },
  289         425  
  289         773  
233 289 50   289   222 add_output => sub { my $m = shift; $m->{output_sub} ? '&$_out' : 'push @OUT,' },
  289         746  
234 294     294 1 9100 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 294     294 0 224 my $self = shift;
248 294         558 my %assembler = $self->assembler_rules();
249            
250 294         543 my @assembly = @{ delete $assembler{ template } };
  294         719  
251            
252 990         2725 my %token_map = map { ( /^(.*?)_token$/ )[0] => delete $assembler{$_} }
253 294         762 grep { /_token$/ } keys %assembler;
  3076         3555  
254              
255 294 100       684 my %fragments = map { $_ => map { ref($_) ? &{$_}( $self ) : $_ } $assembler{$_} } keys %assembler;
  2086         1567  
  2086         2978  
  578         764  
256              
257 294         1474 return( \@assembly, \%fragments, \%token_map );
258             }
259              
260             # $perl_code = $mason->assemble( @tokens );
261             sub assemble {
262 294     294 1 1506 my $self = shift;
263 294         497 my @tokens = @_;
264            
265 294         476 my ( $order, $fragments, $token_map ) = $self->assembler_vars();
266            
267 294         382 my %token_streams = map { $_ => [] } map { ( /^\W?\@(\w+)$/ ) } @$order;
  1402         1782  
  3194         3572  
268              
269 294         798 while ( scalar @tokens ) {
270 1185         1359 my ( $type, $token ) = splice( @tokens, 0, 2 );
271            
272 1185 100 66     2566 unless ( $token_streams{$type} or $token_map->{$type} ) {
273 39         49 my $method = "assemble_$type";
274 39 50       122 my $sub = $self->can( $method )
275             or $self->croak_msg( "Unexpected token type '$type': '$token'" );
276 39         70 ($type, $token) = &$sub( $self, $token );
277             }
278            
279 1185 100       1734 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 917         2302 'TOKEN' => $token,
286             'QUOTED' => "qq(\Q$token\E)",
287             );
288 917         4789 $typedef =~ s/\b(OUT|TOKEN|QUOTED)\b/$substitution_map{$1}/g;
289            
290 917         1965 ( $type, $token ) = split ' ', $typedef, 2;
291             }
292            
293 1185 50       1828 my $ary = $token_streams{$type}
294             or $self->croak_msg( "Unexpected token type '$type': '$token'" );
295            
296 1185         2296 push @$ary, $token
297             }
298            
299             join( "\n", map {
300 294 50       360 /^(\W+)(\w+)$/ or $self->croak_msg("Can't assemble $_");
  3194         5439  
301 3194 100       4653 if ( $1 eq '$' ) {
    100          
    100          
    50          
302 1792         2169 $fragments->{ $2 }
303             } elsif ( $1 eq '@' ) {
304 848         529 @{ $token_streams{ $2 } }
  848         1390  
305             } elsif ( $1 eq '!@' ) {
306 277         181 reverse @{ $token_streams{ $2 } }
  277         426  
307             } elsif ( $1 eq '-@' ) {
308             ()
309 277         1716 } else {
310 0         0 $self->croak_msg("Can't assemble $_");
311             }
312             } @$order );
313             }
314              
315             ######################################################################
316              
317             ######################################################################
318              
319             sub croak_msg {
320 16     16 1 17 local $Carp::CarpLevel = 2;
321 16 50       2764 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 83 my $self = shift;
342 52         49 my ($src_type, $src_data, %options) = @_;
343              
344 52         80 return $src_data;
345             }
346              
347              
348             1;
349              
350             __END__