]> mj.ucw.cz Git - temple.git/blob - UCW/Temple.pm
bafe91d6bf65593be184178c806414b1ea02bbf1
[temple.git] / UCW / Temple.pm
1 #!/usr/bin/perl
2 # A really simple template engine
3 # (c) 2004--2008 Martin Mares <mj@ucw.cz>
4
5 package UCW::Temple;
6
7 use strict;
8 use warnings;
9 no strict 'vars';
10
11 require Exporter;
12 our $VERSION = 1.0;
13 our @ISA = qw(Exporter);
14 our @EXPORT = qw(out);
15 our @EXPORT_OK = qw();
16
17 use IO::File;
18
19 our @cond;
20
21 sub out {
22         $T::out_func->(@_);
23 }
24
25 sub get_nested($$$) {
26         my ($r,$left,$right) = @_;
27         pos $$r = pos($$r)-1;
28         my $z = "";
29         my $nest = 0;
30         do {
31                 if ($$r =~ /\G([^$left$right]+)/cgs) {
32                 } elsif ($$r =~ /\G([$left])/cgs) {
33                         $nest++;
34                 } elsif ($$r =~ /\G([$right])/cgs) {
35                         $nest--;
36                 } else {
37                         die "File ended when looking for matching $right";
38                 }
39                 $z .= $1;
40         } while ($nest);
41         return $z;
42 }
43
44 sub eval_if_ok($) {
45         if ($cond[0] > 0) {
46                 my $x = shift;
47                 my $res = eval "package T; $x";
48                 return $res unless $@;
49                 die "Error evaluating $x: $@";
50         } else {
51                 return "";
52         }
53 }
54
55 sub eval_func($$) {
56         my ($f, $a) = @_;
57         if ($f =~ /^(if|fi|else|elif)$/) {
58                 if ($f eq "if") {
59                         $a ne "()" or die "\@if requires an argument";
60                         if ($cond[0] > 0) {
61                                 unshift @cond, (eval_if_ok($a) ? 1 : -1);
62                         } else {
63                                 unshift @cond, 0;
64                         }
65                 } elsif ($f eq "fi") {
66                         $a eq "()" or die "\@fi takes no arguments";
67                         $#cond or die "\@fi without \@if";
68                         shift @cond;
69                 } elsif ($f eq "else") {
70                         $a eq "()" or die "\@else takes no arguments";
71                         $#cond or die "\@else without \@if";
72                         $cond[0] = -$cond[0];
73                 } elsif ($f eq "elif") {
74                         $a ne "()" or die "\@elif requires an argument";
75                         $#cond or die "\@elif without \@if";
76                         if ($cond[0] > 0) {
77                                 $cond[0] = 0;
78                         } elsif ($cond[0] < 0) {
79                                 $cond[0] = 1;
80                                 $cond[0] = (eval_if_ok($a) ? 1 : -1);
81                         }
82                 }
83                 # print "Cond stack: @cond\n";
84         } else {
85                 my $res = eval_if_ok("$f($a)");
86                 out $res if defined $res;
87         }
88 }
89
90 sub parse_string($) {
91         my ($t) = @_;
92         pos $t = 0;
93         for(;;) {
94                 if ($t =~ /\G([^\@]+)/cgs) {
95                         out $1 if $cond[0] > 0;
96                 } elsif ($t =~ /\G\@\s*\n/cgs) {
97                         # @ at end of line is ignored and eats the end of line
98                 } elsif ($t =~ /\G\@#[^\n]*\n/cgs) {
99                         # a comment, which is ignored
100                 } elsif ($t =~ /\G\@\@/cgs) {
101                         out "\@";
102                 } elsif ($t =~ /\G\@{/cgs) {
103                         my $x = get_nested(\$t, "{", "}");
104                         out eval_if_ok($x);
105                 } elsif ($t =~ /\G\@\[/cgs) {
106                         my $x = get_nested(\$t, '\[', '\]');
107                         $x =~ s/^\[//;
108                         $x =~ s/\]$//;
109                         eval_if_ok($x);
110                 } elsif ($t =~ /\G\@(\w+)\(/cgs) {
111                         my $func = $1;
112                         my $args = get_nested(\$t, '(', ')');
113                         eval_func($func, $args);
114                 } elsif ($t =~ /\G\@(\w+)([^\n]*)\n/cgs) {
115                         eval_func($1, "($2)");
116                 } elsif ($t =~ /\G\@(\$\w+)/cgs) {
117                         out eval_if_ok($1);
118                 } elsif ($t =~ /\G(\@[^\n]*)/cgs) {
119                         die "Unknown control sequence $1";
120                 } elsif ($t =~ /\G$/cgs) {
121                         last;
122                 } elsif ($t =~ /\G([^\n]*)/cgs) {
123                         die "Internal parser error at $1 (pos " . pos($t) . ")";
124                 } else { die; }
125         }
126 }
127
128 sub parse_file($) {
129         my ($name) = @_;
130         my $fh = new IO::File $name;
131         die "Unable to open $name: $!" unless defined $fh;
132         my $text;
133         { local $/; undef $/; $text = <$fh>; }
134         undef $fh;
135         parse_string($text);
136 }
137
138 sub start()
139 {
140         @cond = (1);
141 }
142
143 sub finish()
144 {
145         $#cond and die "Unterminated \@if (depth $#cond)";
146 }
147
148 sub process_file($) {
149         start();
150         parse_file($_[0]);
151         finish();
152 }
153
154 sub process_string($) {
155         start();
156         parse_string($_[0]);
157         finish();
158 }
159
160 ### Perl commands embedded in the templates are evaluated in this package ###
161
162 package T;
163
164 import UCW::Temple;
165
166 our $temp;
167 our $out_func = sub { print @_; };
168
169 sub include {
170         my $fn = shift @_;
171         while (@_) {
172                 my $v = shift @_;
173                 $temp = shift @_;
174                 UCW::Temple::eval_if_ok("\$$v = \$temp");
175         }
176         UCW::Temple::parse_file($fn);
177         return;
178 }
179
180 sub load {
181         my $f = shift @_;
182         (!defined($f) || @_) and die "\@load requires only one argument";
183         require $f;
184         return;
185 }
186
187 1;