Template.pm - lsg - Lumidify Site Generator
HTML git clone git://lumidify.org/lsg.git (fast, but not encrypted)
HTML git clone https://lumidify.org/lsg.git (encrypted, but very slow)
HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/lsg.git (over tor)
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
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;