File Coverage

blib/lib/Text/MicroMason/Base.pm
Criterion Covered Total %
statement 137 143 95.8
branch 48 72 66.6
condition 7 11 63.6
subroutine 25 27 92.5
pod 17 20 85.0
total 234 273 85.7


line stmt bran cond sub pod time code
1             package Text::MicroMason::Base;
2              
3 39     39   276 use strict;
  39         79  
  39         1663  
4             require Carp;
5              
6             ######################################################################
7              
8             ######################################################################
9              
10 39     39   16515 use Class::MixinFactory -hasafactory;
  39         130715  
  39         241  
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 8123 my $callee = shift;
22 163         271 my ( @traits, @attribs );
23 163         461 while ( scalar @_ ) {
24 302 100       1635 if ( $_[0] =~ /^\-(\w+)$/ ) {
25 264         711 push @traits, $1;
26 264         563 shift;
27             } else {
28 38         139 push @attribs, splice(@_, 0, 2);
29             }
30             }
31 163 100       352 if ( scalar @traits ) {
32 153 50       389 die("Adding moxins to an existing class not supported yet!")
33             unless ( $callee eq __PACKAGE__ );
34 153         653 $callee->class( @traits )->create( @attribs )
35             } else {
36 10         35 $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 11755 my $referent = shift;
46 203 100       513 if ( ! ref $referent ) {
47 154         475 bless { $referent->defaults(), @_ }, $referent;
48             } else {
49 49         119 bless { $referent->defaults(), %$referent, @_ }, ref $referent;
50             }
51             }
52              
53             sub defaults {
54             return ()
55 203     203 1 2206 }
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 97747 my ( $self, $src_type, $src_data, %options ) = @_;
66              
67 311         1029 ($self, $src_type, $src_data) = $self->prepare($src_type, $src_data,%options);
68            
69 311         883 my $code = $self->interpret( $src_type, $src_data );
70            
71 311 100       887 unless ( $self->eval_sub($code) ) {
72 17 50       198 ref($@) and die $@;
73 17         72 $self->croak_msg( "MicroMason compilation failed: $@\n" . _number_lines($code) . "\n" );
74             }
75              
76             }
77              
78             # Internal helper to number the lines in the compiled template when compilation croaks
79             sub _number_lines {
80 17     17   59 my $code = shift;
81              
82 17         56 my $n = 0;
83 17         99 return join("\n", map { sprintf("%4d %s", $n++, $_) } split(/\n/, $code)).
  204         674  
84             "\n** Please use Text::MicroMason->new\(-LineNumbers\) for better diagnostics!";
85             }
86              
87              
88             ######################################################################
89              
90             # $result = $mason->execute( code => $subref, @arguments );
91             # $result = $mason->execute( $src_type, $src_data, @arguments );
92             # $result = $mason->execute( $src_type, $src_data, \%options, @arguments );
93             sub execute {
94 262     262 1 290843 my $self = shift;
95 262 100       1191 my $sub = ( $_[0] eq 'code' ) ? do { shift; shift } : $self->compile( shift, shift, ref( $_[0] ) ? %{ shift() } : () );
  4 100       5  
  4         7  
  1         4  
96 257 100       8858 unless ($sub) {
97 5 50       15 ref($@) and die $@;
98 5         21 $self->croak_msg("MicroMason compilation failed: $@");
99             }
100 252         843 &$sub( @_ );
101             }
102              
103             ######################################################################
104              
105             ######################################################################
106              
107             # ($self, $src_type, $src_data) = $self->prepare($src_type, $src_data, %options)
108             sub prepare {
109 311     311 1 2765 my ( $self, $src_type, $src_data, %options ) = @_;
110 311 100       887 $self = $self->create( %options ) if ( scalar keys %options );
111 311         1046 return ( $self, $src_type, $src_data );
112             }
113              
114             ######################################################################
115              
116             # $perl_code = $mason->interpret( $src_type, $src_data );
117             sub interpret {
118 313     313 1 685 my ( $self, $src_type, $src_data ) = @_;
119 313         874 my $template = $self->read( $src_type, $src_data );
120 313         866 my @tokens = $self->lex( $template );
121 313         1000 my $code = $self->assemble( @tokens );
122              
123             # Source file and line number
124 313         1144 my $source_line = $self->source_file_line_label( $src_type, $src_data );
125            
126 313         1286 return $source_line . "\n" . $code;
127             }
128              
129             # $line_number_comment = $mason->source_file_line_label( $src_type, $src_data );
130             sub source_file_line_label {
131 313     313 0 663 my ( $self, $src_type, $src_data ) = @_;
132              
133 313 100       770 if ( $src_type eq 'file' ) {
134 47         182 return qq(# line 1 "$src_data");
135             }
136            
137 266         419 my @caller;
138             my $call_level;
139 266   100     374 do { @caller = caller( ++ $call_level ) }
  951         8639  
140             while ( $caller[0] =~ /^Text::MicroMason/ or $self->isa($caller[0]) );
141 266   33     726 my $package = ( $caller[1] || $0 );
142 266         1086 qq{# line 1 "text template (compiled at $package line $caller[2])"}
143             }
144              
145              
146             ######################################################################
147              
148             # $code_ref = $mason->eval_sub( $perl_code );
149             sub eval_sub {
150 292     292 1 606 my $m = shift;
151             package Text::MicroMason::Commands;
152             eval( shift )
153 292     1   88623 }
  1         8  
  1         2  
  1         63  
154              
155             ######################################################################
156              
157             ######################################################################
158              
159             # $template = $mason->read( $src_type, $src_data );
160             sub read {
161 313     313 1 924 my ( $self, $src_type, $src_data ) = @_;
162              
163 313         611 my $src_method = "read_$src_type";
164 313 50       1446 $self->can($src_method)
165             or $self->croak_msg("Unsupported source type '$src_type'");
166 313         796 $self->$src_method( $src_data );
167             }
168              
169             # $template = $mason->read_text( $template );
170             sub read_text {
171 263 50   263 1 972 ref($_[1]) ? $$_[1] : $_[1];
172             }
173              
174             # $contents = $mason->read_file( $filename );
175             sub read_file {
176 47     47 1 1225 my ( $self, $file ) = @_;
177 47         124 local *FILE;
178 47 50       1933 open FILE, "$file" or $self->croak_msg("MicroMason can't open $file: $!");
179 47         290 local $/ = undef;
180 47         1055 local $_ = ;
181 47 50       472 close FILE or $self->croak_msg("MicroMason can't close $file: $!");;
182 47         403 return $_;
183             }
184              
185             # $contents = $mason->read_handle( $filehandle );
186             sub read_handle {
187 3     3 1 10 my ( $self, $handle ) = @_;
188 3 50       18 my $fh = (ref $handle eq 'GLOB') ? $handle : $$handle;
189 3         32 local $/ = undef;
190             <$fh>
191 3         108 }
192              
193             ######################################################################
194              
195             # @token_pairs = $mason->lex( $template );
196             sub lex {
197 299     299 1 504 my $self = shift;
198 299         649 local $_ = "$_[0]";
199 299         440 my @tokens;
200 299 50       976 my $lexer = $self->can('lex_token')
201             or $self->croak_msg('Unable to lex_token(); must select a syntax mixin');
202             # warn "Lexing: " . pos($_) . " of " . length($_) . "\n";
203 299         1216 until ( /\G\z/gc ) {
204 1161 50 0     2440 my @parsed = &$lexer( $self ) or
205             /\G ( .{0,20} ) /gcxs
206             && die "MicroMason parsing halted at '$1'\n";
207 1161         4089 push @tokens, @parsed;
208             }
209 299         1228 return @tokens;
210             }
211              
212             # ( $type, $value ) = $mason->lex_token();
213             sub lex_token {
214 0     0 1 0 die "The lex_token() method is abstract and must be provided by a subclass";
215             }
216              
217             ######################################################################
218              
219             ######################################################################
220              
221             # Text elements used for subroutine assembly
222             sub assembler_rules {
223             template => [ qw( $sub_start $init_errs $init_output
224             $init_args @perl $return_output $sub_end ) ],
225              
226             # Subroutine scafolding
227             sub_start => 'sub { ',
228             sub_end => '}',
229             init_errs =>
230             'local $SIG{__DIE__} = sub { ref($_[0]) and die $_[0]; die "MicroMason execution failed: ", @_ };',
231            
232             # Argument processing elements
233             init_args => 'my %ARGS = @_ if ($#_ % 2);',
234            
235             # Output generation
236 308 50   308   492 init_output => sub { my $m = shift; my $sub = $m->{output_sub} ? '$m->{output_sub}' : 'sub {push @OUT, @_}'; 'my @OUT; my $_out = ' . $sub . ';' },
  308         697  
  308         1138  
237 308 50   308   488 add_output => sub { my $m = shift; $m->{output_sub} ? '&$_out' : 'push @OUT,' },
  308         1049  
238 313     313 1 15273 return_output => 'join("", @OUT)',
239              
240             # Mapping between token types
241             text_token => 'perl OUT( QUOTED );',
242             expr_token => "perl OUT( \"\".do{\nTOKEN\n} );",
243             # the "". here forces string context, and should hopefully make
244             # 'uninitialized' warnings appear closer to their source, rather
245             # than at the big join "", @OUT; at the end
246             file_token => "perl OUT( \$m->execute( file => do {\nTOKEN\n} ) );",
247             # Note that we need newline after TOKEN here in case it ends with a comment.
248             }
249              
250             sub assembler_vars {
251 313     313 0 457 my $self = shift;
252 313         839 my %assembler = $self->assembler_rules();
253            
254 313         902 my @assembly = @{ delete $assembler{ template } };
  313         1174  
255            
256 1047         4204 my %token_map = map { ( /^(.*?)_token$/ )[0] => delete $assembler{$_} }
257 313         1167 grep { /_token$/ } keys %assembler;
  3266         6869  
258              
259 313 100       1131 my %fragments = map { $_ => map { ref($_) ? &{$_}( $self ) : $_ } $assembler{$_} } keys %assembler;
  2219         3170  
  2219         4805  
  616         1202  
260              
261 313         2316 return( \@assembly, \%fragments, \%token_map );
262             }
263              
264             # $perl_code = $mason->assemble( @tokens );
265             sub assemble {
266 313     313 1 2470 my $self = shift;
267 313         883 my @tokens = @_;
268            
269 313         858 my ( $order, $fragments, $token_map ) = $self->assembler_vars();
270            
271 313         683 my %token_streams = map { $_ => [] } map { ( /^\W?\@(\w+)$/ ) } @$order;
  1497         3088  
  3403         6559  
272              
273 313         1055 while ( scalar @tokens ) {
274 1228         2567 my ( $type, $token ) = splice( @tokens, 0, 2 );
275            
276 1228 100 100     4106 unless ( $token_streams{$type} or $token_map->{$type} ) {
277 40         75 my $method = "assemble_$type";
278 40 50       156 my $sub = $self->can( $method )
279             or $self->croak_msg( "Unexpected token type '$type': '$token'" );
280 40         100 ($type, $token) = &$sub( $self, $token );
281             }
282            
283 1228 100       11937 if ( my $typedef = $token_map->{ $type } ) {
284             # Perform token map substitution in a single pass so that uses of
285             # OUT in the token text are not improperly converted to output calls.
286             # -- Simon, 2009-11-14
287             my %substitution_map = (
288             'OUT' => $fragments->{add_output},
289 959         3599 'TOKEN' => $token,
290             'QUOTED' => "qq(\Q$token\E)",
291             );
292 959         7149 $typedef =~ s/\b(OUT|TOKEN|QUOTED)\b/$substitution_map{$1}/g;
293            
294 959         3576 ( $type, $token ) = split ' ', $typedef, 2;
295             }
296            
297 1228 50       2844 my $ary = $token_streams{$type}
298             or $self->croak_msg( "Unexpected token type '$type': '$token'" );
299            
300 1228         3312 push @$ary, $token
301             }
302            
303             join( "\n", map {
304 313 50       663 /^(\W+)(\w+)$/ or $self->croak_msg("Can't assemble $_");
  3403         8880  
305 3403 100       7879 if ( $1 eq '$' ) {
    100          
    100          
    50          
306 1906         4245 $fragments->{ $2 }
307             } elsif ( $1 eq '@' ) {
308 905         1133 @{ $token_streams{ $2 } }
  905         2166  
309             } elsif ( $1 eq '!@' ) {
310 296         449 reverse @{ $token_streams{ $2 } }
  296         612  
311             } elsif ( $1 eq '-@' ) {
312             ()
313 296         2665 } else {
314 0         0 $self->croak_msg("Can't assemble $_");
315             }
316             } @$order );
317             }
318              
319             ######################################################################
320              
321             ######################################################################
322              
323             sub croak_msg {
324 22     22 1 53 local $Carp::CarpLevel = 2;
325 22 50       4399 shift and Carp::croak( ( @_ == 1 ) ? $_[0] : join(' ', map _printable(), @_) )
    50          
326             }
327              
328             my %Escape = (
329             ( map { chr($_), unpack('H2', chr($_)) } (0..255) ),
330             "\\"=>'\\', "\r"=>'r', "\n"=>'n', "\t"=>'t', "\""=>'"'
331             );
332              
333             # $special_characters_escaped = _printable( $source_string );
334             sub _printable {
335 0 0   0   0 local $_ = scalar(@_) ? (shift) : $_;
336 0 0       0 return "(undef)" unless defined;
337 0         0 s/([\r\n\t\"\\\x00-\x1f\x7F-\xFF])/\\$Escape{$1}/sgo;
338 0 0       0 /[^\w\d\-\:\.\']/ ? "q($_)" : $_;
339             }
340              
341             ######################################################################
342              
343              
344             sub cache_key {
345 52     52 0 182 my $self = shift;
346 52         85 my ($src_type, $src_data, %options) = @_;
347              
348 52         114 return $src_data;
349             }
350              
351              
352             1;
353              
354             __END__