File Coverage

blib/lib/Templ/Parser.pm
Criterion Covered Total %
statement 12 103 11.6
branch 0 40 0.0
condition 0 14 0.0
subroutine 4 17 23.5
pod 0 12 0.0
total 16 186 8.6


line stmt bran cond sub pod time code
1             package Templ::Parser;
2              
3 1     1   4 use strict;
  1         11  
  1         25  
4 1     1   3 use warnings;
  1         1  
  1         19  
5              
6 1     1   3 use Carp qw(cluck croak);
  1         1  
  1         37  
7 1     1   3 use Data::Dumper;
  1         2  
  1         956  
8              
9             my $PKG = __PACKAGE__;
10              
11             eval { require Perl::Tidy; require File::Temp };
12             my $can_tidy = $@ ? 0 : 1;
13              
14             eval { require v5.10; };
15             my $can_say = $@ ? 0 : 1;
16              
17             sub new {
18 0     0 0   my $class = shift;
19 0 0 0       if ( not defined $class || ref $class || $class !~ m/^(\w+\:\:)*\w+$/ ) {
      0        
20 0           croak "Can only be called as Templ::Parser::...->new";
21             }
22 0 0         if ($class eq $PKG) {
23 0           croak "$PKG cannot be instantiated directly, use a subclass";
24             }
25 0           my $self = bless {@_}, $class;
26 0           return $self;
27             }
28              
29             sub parse {
30 0     0 0   my $self = shift;
31 0           my $templ = shift;
32              
33             # The template is assumed to be starting as printing output, so
34             # wrap the whole template in a header/footer, escaping the contents
35 0           my $perl = '';
36 0 0         if ($self->prettify) { $perl .= $self->pretty_header; }
  0            
37 0           my $quoted = $templ->templ_code;
38 0           $quoted =~ s|\\|\\\\|gs;
39 0           $quoted =~ s|'|\\'|gs;
40 0           $perl .= $templ->header;
41 0           $perl .= $self->header;
42 0           $perl .= "'$quoted'";
43 0           $perl .= $self->footer;
44              
45             # Loop over all of the remaining <* ... *> tag types, performing the
46             # prescribed replacements... run in reverse to process latest / deepest
47             # subclass tags first
48             #
49             # $self->debug && print Data::Dumper->Dump([[$templ->tags]],['tags']);
50             # $self->debug && print Data::Dumper->Dump([$templ],['templ']);
51 0           foreach my $tag ( $templ->tags ) {
52 0           $perl = $tag->process( $perl, $self );
53             }
54              
55 0 0         if ( $self->prettify ) {
56             # Change any standalone single-quote print statements with
57             # literal newlines in them to a series of individual print
58             # or say statements for readability
59 0           $perl =~ s{
60             (?:
61             # $1 = Previous statement separator
62             ( (?: ^ | \; | \{ | \} ) [ \t]*? (?:\r?\n)? )
63             # $2 = Print statement indentation
64             ( \s*? )
65             # $3 = Double quote contents
66             print \s* '(.*?)(?
67             # $4 = Closing brace or semicolon
68             ( \s* (?: (?:\;|\}) (?:\r?\n)? | $ ) )
69             )
70             }
71 0           { $self->prettify_lines($1, $2, $3, $4) }egsx;
72             }
73              
74 0 0         if ( $self->tidy ) {
75 0 0         if ($can_tidy) {
76 0           require File::Temp;
77 0           require Perl::Tidy;
78              
79             # Using a temp file because the output is weird when we don't
80 0           ( undef, my $tmp ) = File::Temp::tempfile();
81 0           Perl::Tidy::perltidy(
82             'source' => \$perl,
83             'destination' => $tmp,
84             'argv' => [ split /\s+/, $self->tidy_options ],
85             );
86 0   0       open my $FH, '<', $tmp
87             || die "Unable to open file for reading $tmp: $!";
88 0           local $/ = undef;
89 0           $perl = <$FH>;
90 0           close $FH;
91             }
92             else {
93 0           warn "Unable to load Perl::Tidy and/or File::Temp\n";
94             }
95             }
96              
97 0 0 0       if ( $self->tidy || $self->prettify ) {
98              
99             # Remove blank append statements
100 0           my $append = $self->append;
101 0           $perl =~ s/(?:^|(?<=\n)[ \t]*)\Q$append\E'';[ \t]*(?:\r?\n|$)//;
102             }
103              
104 0 0         if ( $self->debug ) {
105 0           my @lines = split /\n/, $perl;
106 0           my $format = '%' . length( scalar(@lines) . '' ) . "s: %s\n";
107 0           print STDERR sprintf( $format, ( $_ + 1 ), $lines[$_] ) foreach (0 .. $#lines);
108             }
109              
110 0           return $perl;
111             }
112              
113             # Breaks a print statement with newlines in it into multiple statements
114             # Helps with formatting code to preserve indentation (used when prettify is
115             # enabled)
116             sub prettify_lines {
117 0     0 0   my $self = shift;
118 0           my $pre = shift; # Previous opening brace or semicolon
119 0           my $indent = shift; # Indentation spacing of the print statement
120 0           my $contents = shift; # Contents of the single quotes of the print
121 0           my $post = shift; # Closing brace or semicolon
122              
123 0           my $out = $pre;
124              
125             # Create a list of lines (and the ending partial line) in the print
126 0           my @chunks = split /(.*?\n)/, $contents;
127              
128             # print "CHUNK <<$_>>\n" foreach @chunks;
129 0           foreach ( 0 .. $#chunks ) {
130 0           my $is_last_chunk = ( $_ == $#chunks );
131 0           my $chunk = $chunks[$_];
132 0 0 0       next if ( ( $chunk eq '' ) && ( not $is_last_chunk ) );
133              
134 0           my $nl = '';
135 0 0         if ( $chunk =~ s/\r\n$// ) { $nl = '\r\n'; }
  0 0          
136 0           elsif ( $chunk =~ s/\n$// ) { $nl = '\n'; }
137              
138 0           my $statement;
139 0 0         if ($nl) {
140 0 0         if ($self->append_pretty) {
141 0           $statement = $self->append_pretty . "'$chunk'";
142             }
143             else {
144 0           $statement = $self->append . "'$chunk'" . '."$nl"';
145             }
146             }
147             else {
148 0           $statement = $self->append . "'$chunk'";
149             }
150 0 0         $statement .= $is_last_chunk ? $post : ";\n";
151              
152 0           $out .= $statement;
153             }
154              
155 0           return $out;
156             }
157              
158             ##############################################################################
159             # Some override-able functions for subclasses
160              
161 0     0 0   sub pretty_header { return ''; }
162 0     0 0   sub header { die "Subclass must override Templ::Parser->header"; }
163 0     0 0   sub append { die "Subclass must override Templ::Parser->append"; }
164 0     0 0   sub append_pretty { return ''; }
165 0     0 0   sub footer { die "Subclass must override Templ::Parser->footer"; }
166              
167             ##############################################################################
168             # Utility Functions
169              
170             # Returns the first defined value in a list, or a blank string if there are
171             # # no defined values
172             sub _default (@) {
173 0 0   0     foreach (@_) { defined($_) && return $_; }
  0            
174 0           return '';
175             }
176              
177             ##############################################################################
178             # Accessors...
179             sub debug {
180 0     0 0   my $self = shift;
181 0 0         if ( defined $_[0] ) { $self->{'debug'} = shift; }
  0            
182 0           return _default $self->{'debug'}, $Templ::Parser::debug, 0;
183             }
184              
185             sub tidy {
186 0     0 0   my $self = shift;
187 0 0         if ( defined $_[0] ) { $self->{'tidy'} = shift; }
  0            
188 0           return _default $self->{'tidy'}, $Templ::Parser::tidy, 0;
189             }
190              
191             sub tidy_options {
192 0     0 0   my $self = shift;
193 0 0         if ( defined $_[0] ) { $self->{'tidy_options'} = shift; }
  0            
194 0           return _default $self->{'tidy_options'}, $Templ::Parser::tidy_options,
195             '-pbp -nst -b -aws -dws -dsm -nbbc -kbl=0 -asc -npro -sbl';
196             }
197              
198             sub prettify {
199 0     0 0   my $self = shift;
200 0 0         if ( defined $_[0] ) { $self->{'prettify'} = shift; }
  0            
201 0 0         return _default $self->{'prettify'}, $Templ::Parser::prettify,
202             ( $self->tidy ? 1 : 0 );
203             }
204              
205             1;