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