1 # A really simple template engine
2 # (c) 2004--2012 Martin Mares <mj@ucw.cz>
12 our @ISA = qw(Exporter);
13 our @EXPORT = qw(out);
14 our @EXPORT_OK = qw();
32 my $current_file_name;
37 my ($msg, $offset) = @_;
38 if (defined $current_file_name) {
40 # This is rather tricky. We want to report the exact place, where the error
41 # occurred, but the cost of keeping track of the current line number is too high.
42 # Hence we keep just the current position inside the input stream and convert
43 # it to the line number only when reporting an error.
45 my $input = substr(${$current_string}, 0, $current_pos);
46 my @newlines = ($input =~ m{\n}g);
47 my $line = @newlines + ($offset // 1);
48 die "$current_file_name:$line: $msg\n";
55 my ($r,$left,$right) = @_;
60 if ($$r =~ /\G([^$left$right]+)/cgs) {
61 } elsif ($$r =~ /\G([$left])/cgs) {
63 } elsif ($$r =~ /\G([$right])/cgs) {
66 error "File ended when looking for matching $right";
76 my $res = eval "package T; $x";
77 return $res unless $@;
80 if (my ($msg, $line) = $m =~ m{(.*) at \(eval \d+\) line (\d+)\.$}) {
92 if ($f =~ /^(if|fi|else|elif)$/) {
94 $a ne "()" or error "\@if requires an argument";
96 unshift @cond, (eval_if_ok($a) ? 1 : -1);
100 } elsif ($f eq "fi") {
101 $a eq "()" or error "\@fi takes no arguments";
102 $#cond or error "\@fi without \@if";
104 } elsif ($f eq "else") {
105 $a eq "()" or error "\@else takes no arguments";
106 $#cond or error "\@else without \@if";
107 $cond[0] = -$cond[0];
108 } elsif ($f eq "elif") {
109 $a ne "()" or error "\@elif requires an argument";
110 $#cond or error "\@elif without \@if";
113 } elsif ($cond[0] < 0) {
115 $cond[0] = (eval_if_ok($a) ? 1 : -1);
118 # print "Cond stack: @cond\n";
120 my $res = eval_if_ok("$f $a");
121 out $res if defined $res;
125 sub parse_string($$) {
127 my ($old_fn, $old_str, $old_pos) = ($current_file_name, $current_string, $current_pos);
128 $current_file_name = $name;
129 $current_string = \$t;
132 $current_pos = pos $t;
133 if ($t =~ /\G([^\@]+)/cgs) {
134 out $1 if $cond[0] > 0;
135 } elsif ($t =~ /\G\@\s*\n/cgs) {
136 # @ at end of line is ignored and eats the end of line
137 } elsif ($t =~ /\G\@#[^\n]*\n/cgs) {
138 # a comment, which is ignored
139 } elsif ($t =~ /\G\@\@/cgs) {
140 out "\@" if $cond[0] > 0;
141 } elsif ($t =~ /\G\@{/cgs) {
142 my $x = get_nested(\$t, "{", "}");
144 } elsif ($t =~ /\G\@\[/cgs) {
145 my $x = get_nested(\$t, '\[', '\]');
149 } elsif ($t =~ /\G\@\(/cgs) {
150 my $x = get_nested(\$t, '\(', '\)');
153 out (defined($arguments->{$x}) ? $arguments->{$x} : "");
154 } elsif ($t =~ /\G\@(\w+)\(/cgs) {
156 my $args = get_nested(\$t, '(', ')');
157 eval_func($func, $args);
158 } elsif ($t =~ /\G\@(\w+)([^\n]*)\n/cgs) {
159 eval_func($1, "($2)");
160 } elsif ($t =~ /\G\@(\$\w+)/cgs) {
162 } elsif ($t =~ /\G(\@[^\n]*)/cgs) {
163 error "Unknown control sequence $1";
164 } elsif ($t =~ /\G$/cgs) {
166 } elsif ($t =~ /\G([^\n]*)/cgs) {
167 error "Internal parser error";
170 ($current_file_name, $current_string, $current_pos) = ($old_fn, $old_str, $old_pos);
176 my $fh = new IO::File $name;
177 error "Unable to open $name: $!" unless defined $fh;
179 { local $/; undef $/; $text = <$fh>; }
181 parse_string($text, $name);
192 $#cond and error "Unterminated \@if (depth $#cond)";
195 sub process_file($;$) {
196 my ($name, $args) = @_;
202 sub process_string($;$) {
203 my ($string, $args) = @_;
205 parse_string($string, '<string>');
209 ### Perl commands embedded in the templates are evaluated in this package ###
215 # Beware: Functions in the T package should report errors via die(), not error().
218 our $out_func = sub { print @_; };
225 UCW::Temple::eval_if_ok("\$$v = \$temp");
227 UCW::Temple::parse_file($fn);
233 (!defined($f) || @_) and die "\@load requires only one argument";
234 UCW::Temple::add_depend($f);