root/lang/perl/MENTA/trunk/lib/MENTA/Template.pm @ 24791

Revision 24791, 7.0 kB (checked in by tokuhirom, 4 years ago)

merge from NanoA.see http://d.hatena.ne.jp/kazuhooku/20081121/1227267264

Line 
1# a NanoA version
2# based on Mojo::Template. Copyright (C) 2010, Sebastian Riedel.
3# some modified by tokuhirom
4
5package MENTA::Template;
6use strict;
7use warnings;
8use constant DEBUG => $ENV{MENTA_TEMPLATE_DEBUG} || 0;
9
10use Carp 'croak';
11
12sub new {
13    my $class = shift;
14    return bless {
15        code => '',
16        comment_mark => '#',
17        expression_mark => '=',
18        raw_expression_mark => '=r',
19        line_start => '?',
20        template => '',
21        tree => [],
22        tag_start => '<?',
23        tag_end => '?>',
24    }, $class;
25}
26
27sub code { shift->{code} }
28
29sub build {
30    my $self = shift;
31
32    # Compile
33    my @lines;
34    for my $line (@{$self->{tree}}) {
35
36        # New line
37        push @lines, '';
38        for (my $j = 0; $j < @{$line}; $j += 2) {
39            my $type  = $line->[$j];
40            my $value = $line->[$j + 1];
41
42            # Need to fix line ending?
43            my $newline = chomp $value;
44
45            # Text
46            if ($type eq 'text') {
47
48                # Quote and fix line ending
49                $value = quotemeta($value);
50                $value .= '\n' if $newline;
51
52                $lines[-1] .= "\$_MENTA .= \"" . $value . "\";";
53            }
54
55            # Code
56            if ($type eq 'code') {
57                $lines[-1] .= "$value;";
58            }
59
60            # Expression
61            if ($type eq 'expr') {
62                $lines[-1] .= "\$_MENTA_T = scalar $value; \$_MENTA .= ref \$_MENTA_T eq 'MENTA::Template::RawString' ? \$\$_MENTA_T : escape_html(\$_MENTA_T);";
63            }
64
65            # Raw Expression
66            if ($type eq 'raw_expr') {
67               
68                $lines[-1] .= "\$_MENTA_T = $value; \$_MENTA .= ref \$_MENTA_T eq q(MENTA::Template::RawString) ? \$\$_MENTA_T : \$_MENTA_T;";
69            }
70        }
71    }
72
73    # Wrap
74    $lines[0] ||= '';
75    $lines[0]   = q/sub { my $_MENTA = ''; my $_MENTA_T = '';/ . $lines[0];
76    $lines[-1] .= q/return $_MENTA; }/;
77
78    $self->{code} = join "\n", @lines;
79    return $self;
80}
81
82# I am so smart! I am so smart! S-M-R-T! I mean S-M-A-R-T...
83sub parse {
84    my ($self, $tmpl) = @_;
85    $self->{template} = $tmpl;
86
87    # Clean start
88    delete $self->{tree};
89
90    # Tags
91    my $line_start    = quotemeta $self->{line_start};
92    my $tag_start     = quotemeta $self->{tag_start};
93    my $tag_end       = quotemeta $self->{tag_end};
94    my $cmnt_mark     = quotemeta $self->{comment_mark};
95    my $expr_mark     = quotemeta $self->{expression_mark};
96    my $raw_expr_mark = quotemeta $self->{raw_expression_mark};
97
98    # Tokenize
99    my $state = 'text';
100    my $multiline_expression = 0;
101    for my $line (split /\n/, $tmpl) {
102
103        # Perl line without return value
104        if ($line =~ /^$line_start\s+(.+)$/) {
105            push @{$self->{tree}}, ['code', $1];
106            $multiline_expression = 0;
107            next;
108        }
109
110        # Perl line with return value
111        if ($line =~ /^$line_start$expr_mark\s+(.+)$/) {
112            push @{$self->{tree}}, ['expr', $1];
113            $multiline_expression = 0;
114            next;
115        }
116
117        # Perl line with raw return value
118        if ($line =~ /^$line_start$raw_expr_mark\s+(.+)$/) {
119            push @{$self->{tree}}, ['raw_expr', $1];
120            $multiline_expression = 0;
121            next;
122        }
123
124        # Comment line, dummy token needed for line count
125        if ($line =~ /^$line_start$cmnt_mark\s+(.+)$/) {
126            push @{$self->{tree}}, [];
127            $multiline_expression = 0;
128            next;
129        }
130
131        # Escaped line ending?
132        if ($line =~ /(\\+)$/) {
133            my $length = length $1;
134
135            # Newline escaped
136            if ($length == 1) {
137                $line =~ s/\\$//;
138            }
139
140            # Backslash escaped
141            if ($length >= 2) {
142                $line =~ s/\\\\$/\\/;
143                $line .= "\n";
144            }
145        }
146
147        # Normal line ending
148        else { $line .= "\n" }
149
150        # Mixed line
151        my @token;
152        for my $token (split /
153            (
154                $tag_start$raw_expr_mark # Raw Expression
155            |
156                $tag_start$expr_mark     # Expression
157            |
158                $tag_start$cmnt_mark     # Comment
159            |
160                $tag_start               # Code
161            |
162                $tag_end                 # End
163            )
164        /x, $line) {
165
166            # Garbage
167            next unless $token;
168
169            # End
170            if ($token =~ /^$tag_end$/) {
171                $state = 'text';
172                $multiline_expression = 0;
173            }
174
175            # Code
176            elsif ($token =~ /^$tag_start$/) { $state = 'code' }
177
178            # Comment
179            elsif ($token =~ /^$tag_start$cmnt_mark$/) { $state = 'cmnt' }
180
181            # Raw Expression
182            elsif ($token =~ /^$tag_start$raw_expr_mark$/) {
183                $state = 'raw_expr';
184            }
185
186            # Expression
187            elsif ($token =~ /^$tag_start$expr_mark$/) {
188                $state = 'expr';
189            }
190
191            # Value
192            else {
193
194                # Comments are ignored
195                next if $state eq 'cmnt';
196
197                # Multiline expressions are a bit complicated,
198                # only the first line can be compiled as 'expr'
199                $state = 'code' if $multiline_expression;
200                $multiline_expression = 1 if $state eq 'expr';
201
202                # Store value
203                push @token, $state, $token;
204            }
205        }
206        push @{$self->{tree}}, \@token;
207    }
208
209    return $self;
210}
211
212sub _context {
213    my ($self, $text, $line) = @_;
214
215    $line     -= 1;
216    my $nline  = $line + 1;
217    my $pline  = $line - 1;
218    my $nnline = $line + 2;
219    my $ppline = $line - 2;
220    my @lines  = split /\n/, $text;
221
222    # Context
223    my $context = (($line + 1) . ': ' . $lines[$line] . "\n");
224
225    # -1
226    $context = (($pline + 1) . ': ' . $lines[$pline] . "\n" . $context)
227      if $lines[$pline];
228
229    # -2
230    $context = (($ppline + 1) . ': ' . $lines[$ppline] . "\n" . $context)
231      if $lines[$ppline];
232
233    # +1
234    $context = ($context . ($nline + 1) . ': ' . $lines[$nline] . "\n")
235      if $lines[$nline];
236
237    # +2
238    $context = ($context . ($nnline + 1) . ': ' . $lines[$nnline] . "\n")
239      if $lines[$nnline];
240
241    return $context;
242}
243
244# Debug goodness
245sub _error {
246    my ($self, $error) = @_;
247
248    # No trace in production mode
249    return undef unless DEBUG;
250
251    # Line
252    if ($error =~ /at\s+\(eval\s+\d+\)\s+line\s+(\d+)/) {
253        my $line  = $1;
254        my $delim = '-' x 76;
255
256        my $report = "\nTemplate error around line $line.\n";
257        my $template = $self->_context($self->{template}, $line);
258        $report .= "$delim\n$template$delim\n";
259
260        # Advanced debugging
261        if (DEBUG >= 2) {
262            my $code = $self->_context($self->code, $line);
263            $report .= "$code$delim\n";
264        }
265
266        $report .= "$error\n";
267        return $report;
268    }
269
270    # No line found
271    return "Template error: $error";
272}
273
274# create raw string (that does not need to be escaped)
275sub raw_string {
276    my $s = shift;
277    bless \$s, 'MENTA::Template::RawString';
278}
279
2801;
Note: See TracBrowser for help on using the browser.