URI: 
       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;