lsg

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