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

Revision 23529, 6.0 kB (checked in by mattn, 5 years ago)

%s/MOJO/MENTA/g

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