Template.pm (6268B)
1 #!/usr/bin/env perl 2 3 # LSG::Template - template processor for the LSG 4 # Written by lumidify <nobody@lumidify.org> 5 # 6 # To the extent possible under law, the author has dedicated 7 # all copyright and related and neighboring rights to this 8 # software to the public domain worldwide. This software is 9 # distributed without any warranty. 10 # 11 # You should have received a copy of the CC0 Public Domain 12 # Dedication along with this software. If not, see 13 # <http://creativecommons.org/publicdomain/zero/1.0/>. 14 15 package LSG::Template; 16 use strict; 17 use warnings; 18 use utf8; 19 use open qw< :encoding(UTF-8) >; 20 binmode STDIN, ":encoding(UTF-8)"; 21 binmode STDOUT, ":encoding(UTF-8)"; 22 binmode STDERR, ":encoding(UTF-8)"; 23 use File::Spec::Functions qw(catfile); 24 use Storable 'dclone'; 25 use LSG::Config qw($config); 26 use LSG::Metadata; 27 28 sub parse_template { 29 my $template_name = shift; 30 my $state = 0; 31 my $IN_BRACE = 1; 32 my $IN_BLOCK = 2; 33 my $txt = ""; 34 my $bs = 0; 35 36 # Note: there needs to be a line between metadata and content since the 37 # metadata parser takes a line to realize it is not in fm anymore 38 my $inpath = catfile("templates", $template_name); 39 open(my $in, "<", $inpath) or die "ERROR: template: Can't open $inpath for reading."; 40 my $template = LSG::Metadata::parse_metadata_file($in); 41 42 foreach (<$in>) { 43 foreach my $char (split //, $_) { 44 if ($char eq "\\") { 45 $bs++; 46 if (!($bs %= 2)) {$txt .= "\\"}; 47 } elsif ($bs % 2) { 48 $txt .= $char; 49 $bs = 0; 50 } elsif ($char eq "{" && !($state & $IN_BRACE)) { 51 $state |= $IN_BRACE; 52 if ($txt ne "") { 53 if ($state & $IN_BLOCK) { 54 push(@{$template->{"contents"}->[-1]->{"contents"}}, 55 {type => "txt", contents => $txt}); 56 } else { 57 push(@{$template->{"contents"}}, {type => "txt", contents => $txt}); 58 } 59 $txt = ""; 60 } 61 } elsif ($char eq "}" && $state & $IN_BRACE) { 62 $state &= ~$IN_BRACE; 63 my @brace = split(/ /, $txt); 64 if (!@brace) { 65 die("ERROR: empty brace in $inpath:\n$_\n"); 66 } else { 67 if ($brace[0] eq "endblock") { 68 $state &= ~$IN_BLOCK 69 } elsif ($brace[0] eq "block") { 70 $state |= $IN_BLOCK; 71 if ($#brace != 1) { 72 die("ERROR: wrong number of arguments for block in $inpath\n"); 73 } else { 74 push(@{$template->{"contents"}}, {type => $brace[0], 75 id => $brace[1], 76 contents => []}); 77 } 78 } else { 79 my %tmp = (type => $brace[0]); 80 if ($#brace > 0) { 81 @{$tmp{"args"}} = @brace[1..$#brace]; 82 } 83 if ($state & $IN_BLOCK) { 84 push(@{$template->{"contents"}->[-1]->{"contents"}}, \%tmp); 85 } else { 86 push(@{$template->{"contents"}}, \%tmp); 87 } 88 } 89 } 90 $txt = ""; 91 } else { 92 $txt .= $char; 93 } 94 } 95 } 96 if ($state & ($IN_BRACE | $IN_BLOCK)) { 97 die("ERROR: unclosed block or brace in $inpath\n"); 98 } elsif ($txt ne "") { 99 push(@{$template->{"contents"}}, {type => "txt", contents => $txt}); 100 } 101 close($in); 102 my $modified_date = (stat($inpath))[9]; 103 $template->{"modified"} = $modified_date; 104 return $template; 105 } 106 107 sub handle_parent_template { 108 my $parentid = shift; 109 my $childid = shift; 110 if (exists $config->{"templates"}->{$parentid}->{"extends"}) { 111 handle_parent_template($config->{"templates"}->{$parentid}->{"extends"}, $parentid); 112 } 113 if ($config->{"templates"}->{$parentid}->{"modified"} > $config->{"templates"}->{$childid}->{"modified"}) { 114 $config->{"templates"}->{$childid}->{"modified"} = $config->{"templates"}->{$parentid}->{"modified"}; 115 } 116 my $parent = $config->{"templates"}->{$parentid}->{"contents"}; 117 my $child = $config->{"templates"}->{$childid}->{"contents"}; 118 my $child_new = dclone($parent); 119 # Replace blocks from parent template with child blocks 120 # Not very efficient... 121 foreach my $item (@{$child_new}) { 122 if ($item->{"type"} eq "block") { 123 foreach my $item_new (@{$child}) { 124 if ($item_new->{"type"} eq "block" && $item_new->{"id"} eq $item->{"id"}) { 125 $item->{"contents"} = $item_new->{"contents"}; 126 last; 127 } 128 } 129 } 130 } 131 $config->{"templates"}->{$childid}->{"contents"} = $child_new; 132 delete $config->{"templates"}->{$childid}->{"extends"}; 133 } 134 135 sub do_template_inheritance { 136 foreach my $template_id (keys %{$config->{"templates"}}) { 137 if (exists $config->{"templates"}->{$template_id}->{"extends"}) { 138 handle_parent_template($config->{"templates"}->{$template_id}->{"extends"}, $template_id); 139 } 140 } 141 } 142 143 sub init_templates { 144 opendir(my $dir, "templates") or die "ERROR: couldn't open dir templates/\n"; 145 my @files = grep {!/\A\.\.?\z/} readdir($dir); 146 closedir($dir); 147 foreach my $filename (@files) { 148 $config->{"templates"}->{$filename} = parse_template($filename); 149 } 150 do_template_inheritance(); 151 } 152 153 # FIXME: more error checking - arg numbers 154 # -> not too important though since these are just templates (won't be edited too often) 155 sub do_template_items { 156 my $main_content = shift; 157 my $lang = shift; 158 my $pageid = shift; 159 my $template = shift; 160 my $final = ""; 161 for my $item (@{$template->{"contents"}}) { 162 if ($item->{"type"} eq "txt") { 163 $final .= $item->{"contents"}; 164 } elsif ($item->{"type"} eq "var") { 165 $final .= $config->{"metadata"}->{$pageid}->{$lang}->{$item->{"args"}->[0]}; 166 } elsif ($item->{"type"} eq "content") { 167 $final .= $main_content; 168 } elsif ($item->{"type"} eq "block") { 169 $final .= do_template_items($main_content, $lang, $pageid, $item); 170 } elsif ($item->{"type"} eq "func") { 171 my $func = $item->{"args"}->[0]; 172 my @func_args = @{$item->{"args"}}[1..$#{$item->{"args"}}]; 173 # Pass in the array rather than a reference, so these arguments 174 # are received like all other arguments 175 if (!exists($config->{"funcs"}->{$func})) { 176 # FIXME: need more information to give for error 177 die "ERROR: undefined function \"$func\" in template.\n"; 178 } 179 $final .= $config->{"funcs"}->{$func}->($pageid, $lang, @func_args); 180 } 181 } 182 return $final; 183 } 184 185 sub render_template { 186 my $html = shift; 187 my $lang = shift; 188 my $pageid = shift; 189 my $template = $config->{"metadata"}->{$pageid}->{"template"}; 190 if (!exists($config->{"templates"}->{"$template.$lang.html"})) { 191 die "ERROR: can't open template $template.$lang.html\n"; 192 } 193 return do_template_items($html, $lang, $pageid, $config->{"templates"}->{"$template.$lang.html"}); 194 } 195 196 1;