1 # A really simple template engine
2 # (c) 2004--2015 Martin Mares <mj@ucw.cz>
12 our @ISA = qw(Exporter);
13 our @EXPORT = qw(out);
14 our @EXPORT_OK = qw(set);
39 my $current_file_name;
45 my ($msg, $offset) = @_;
46 if (defined $current_file_name && !$reported_error) {
48 # This is rather tricky. We want to report the exact place, where the error
49 # occurred, but the cost of keeping track of the current line number is too high.
50 # Hence we keep just the current position inside the input stream and convert
51 # it to the line number only when reporting an error.
53 my $input = substr(${$current_string}, 0, $current_pos);
54 my @newlines = ($input =~ m{\n}g);
55 my $line = @newlines + ($offset // 1);
57 die "$current_file_name:$line: $msg\n";
64 my ($r, $left, $right) = @_;
65 pos $$r = pos($$r) - 1;
69 if ($$r =~ /\G([^$left$right]+)/cgs) {
70 } elsif ($$r =~ /\G([$left])/cgs) {
72 } elsif ($$r =~ /\G([$right])/cgs) {
75 error "File ended when looking for matching $right";
82 # In addition to get_nested(), skip single-quoted and double-quoted string literals
83 sub get_function_args($$$) {
84 my ($r, $left, $right) = @_;
85 pos $$r = pos($$r) - 1;
89 if ($$r =~ /\G([^$left$right"']+)/cgs
90 || $$r =~/\G("(\\.|[^"\\])*")/cgs # double-quoted string
91 || $$r =~/\G('(\\.|[^'\\])*')/cgs # single-quoted string
93 } elsif ($$r =~ /\G([$left])/cgs) {
95 } elsif ($$r =~ /\G([$right])/cgs) {
98 error "File ended when looking for matching $right";
108 my $res = eval "package T; $x";
109 return $res unless $@;
112 if (my ($msg, $line) = $m =~ m{(.*) at \(eval \d+\) line (\d+)\.$}) {
124 if ($f =~ /^(if|fi|else|elif)$/) {
126 $a ne "()" or error "\@if requires an argument";
128 unshift @cond, (eval_if_ok($a) ? 1 : -1);
132 } elsif ($f eq "fi") {
133 $a eq "()" or error "\@fi takes no arguments";
134 $#cond or error "\@fi without \@if";
136 } elsif ($f eq "else") {
137 $a eq "()" or error "\@else takes no arguments";
138 $#cond or error "\@else without \@if";
139 $cond[0] = -$cond[0];
140 } elsif ($f eq "elif") {
141 $a ne "()" or error "\@elif requires an argument";
142 $#cond or error "\@elif without \@if";
145 } elsif ($cond[0] < 0) {
147 $cond[0] = (eval_if_ok($a) ? 1 : -1);
150 # print "Cond stack: @cond\n";
152 my $res = eval_if_ok("$f $a");
153 out $res if defined $res;
157 sub parse_string($$) {
159 my ($old_fn, $old_str, $old_pos) = ($current_file_name, $current_string, $current_pos);
160 $current_file_name = $name;
161 $current_string = \$t;
164 $current_pos = pos $t;
165 # Scan for the first occurrence of an active character ("@", "~")
166 if ($t =~ /\G([^\@~]+)/cgs) {
167 out $1 if $cond[0] > 0;
168 } elsif ($t =~ /\G~/cgs) {
169 out $T::tilde if $cond[0] > 0;
170 } elsif ($t =~ /\G\@~/cgs) {
171 out "~" if $cond[0] > 0;
172 } elsif ($t =~ /\G\@\s*\n/cgs) {
173 # @ at end of line is ignored and eats the end of line
174 } elsif ($t =~ /\G\@#[^\n]*\n/cgs) {
175 # a comment, which is ignored
176 } elsif ($t =~ /\G\@\@/cgs) {
177 out "\@" if $cond[0] > 0;
178 } elsif ($t =~ /\G\@\{/cgs) {
179 my $x = get_nested(\$t, '\{', '\}');
181 } elsif ($t =~ /\G\@\[/cgs) {
182 my $x = get_nested(\$t, '\[', '\]');
186 } elsif ($t =~ /\G\@\(/cgs) {
187 my $x = get_nested(\$t, '\(', '\)');
190 out (defined($arguments->{$x}) ? $arguments->{$x} : "");
191 } elsif ($t =~ /\G\@(\w+)\(/cgs) {
193 my $args = get_function_args(\$t, '(', ')');
194 eval_func($func, $args);
195 } elsif ($t =~ /\G\@(\w+)([^\n]*)\n/cgs) {
196 eval_func($1, "($2)");
197 } elsif ($t =~ /\G\@(\$\w+)/cgs) {
199 } elsif ($t =~ /\G(\@[^\n]*)/cgs) {
200 error "Unknown control sequence $1";
201 } elsif ($t =~ /\G$/cgs) {
203 } elsif ($t =~ /\G([^\n]*)/cgs) {
204 error "Internal parser error";
207 ($current_file_name, $current_string, $current_pos) = ($old_fn, $old_str, $old_pos);
213 my $fh = new IO::File $name;
214 error "Unable to open $name: $!" unless defined $fh;
216 { local $/; undef $/; $text = <$fh>; }
218 parse_string($text, $name);
228 $#cond and error "Unterminated \@if (depth $#cond)";
231 sub process_file($;$) {
232 my ($name, $args) = @_;
238 sub process_string($;$) {
239 my ($string, $args) = @_;
241 parse_string($string, '<string>');
245 ### Perl commands embedded in the templates are evaluated in this package ###
251 # Beware: Functions in the T package should report errors via die(), not error().
254 our $out_func = sub { print @_; };
262 UCW::Temple::eval_if_ok("\$$v = \$temp");
264 UCW::Temple::parse_file($fn);
270 (!defined($f) || @_) and die "\@load requires only one argument";
271 UCW::Temple::add_depend($f);
277 return $UCW::Temple::arguments->{$_[0]};