# Blosxom3 Plugin: DynamicTemplate # Author: Bernie Simon # Version: 2005-01-22 # Documentation is at the end of the file use strict; use FileHandle; package Blosxom::Plugin::DynamicTemplate; use base 'Blosxom'; #---------------------------------------------------------------------- # Configurable variables # List of template commands. Substitute rest of line for %% or call sub use vars qw(%cmd); %cmd = (if => "if (%%) {", else => "} else {", elsif => "} elsif (%%) {", endif => "}", include => \&include_file, require => "require %%;", set => \&set_variable, source => \&source_file, ); #---------------------------------------------------------------------- # Install reblesses $self into this package sub install { bless ($_[0]); 1; } #---------------------------------------------------------------------- # Get the template then compile it to a subroutine sub get_template { my $self = shift; my $component = shift; my $source = $self->SUPER::get_template ($component); my $template = $self->compile_template ($source); return $template; } #---------------------------------------------------------------------- # Call the compiled template subroutine sub interpolate { my $self = shift; my $template = shift; return &$template ($self); } #---------------------------------------------------------------------- # Compile a template into a subroutine which when called fills itself sub compile_template { my $self = shift (@_); my $source = shift (@_); my $code = <<'EOQ'; sub { my $self = shift (@_); my $text; EOQ $source .= "\n"; $code .= $self->parse_template ($source); $code .= <<'EOQ'; chomp $text; return $text; } EOQ my $sub = eval ($code); die $@ if $@; return $sub; } #---------------------------------------------------------------------- # Replace variable references with calls to interpolate sub encode_expression { my $self = shift (@_); my $value = shift (@_); my $pre = '$self->SUPER::interpolate(\''; my $post = '\')'; $value =~ s/(\$[\w:]+)/$pre$1$post/g; return $value; } #---------------------------------------------------------------------- # Include the contents of a file as literal (non-interpolated) text sub include_file { my $self = shift (@_); my $template = shift (@_); my $content = slurp_file ($template); my $code .= "\$text .= <<'EOQ';\n${content}EOQ\n"; return $code; } #---------------------------------------------------------------------- # Parse the template source sub parse_template { my $self = shift (@_); my $source = shift (@_); my ($code, $filter, $stash); foreach (split (/^(\#[^\n]*)\n/m, $source)) { if (s/^\#//) { my ($name, $value) = split (' ', $_, 2); if (defined $filter) { if ($name eq "end$filter") { $code .= "\$text .= $filter (\$self->SUPER::interpolate"; $code .= "(<<'EOQ'));\n"; $code .= "${stash}EOQ\n"; undef $filter; } else { die "Undefined command or missing end: $filter"; } } elsif (exists $cmd{$name}) { my $cmd = $cmd{$name}; my $ref = ref ($cmd); if (! $ref) { $value = $self->encode_expression ($value); $cmd =~ s/%%/$value/; $code .= "$cmd\n"; } elsif ($ref eq 'CODE') { $code .= &$cmd($self, $value); } else { die "I don't know how to handle a $ref: $cmd"; } } else { $filter = $name; } } elsif (defined $filter) { $stash = $_; } elsif (length ($_)) { $code .= "\$text .= \$self->SUPER::interpolate (<<'EOQ');\n"; $code .= "${_}EOQ\n"; } } die "Undefined command or missing end: $filter" if defined $filter; return $code; } #---------------------------------------------------------------------- # Generate code for the set command, which stores a value in current_entry sub set_variable { my $self= shift (@_); my $cmd = shift (@_); my ($var, $expr) = $cmd =~ /\$([\w:]+)\s*=\s*(.*)/; die "Invalid expression: $cmd\n" unless $var; $var = '$self->{state}->{current_entry}->' . join ('->', map ("{$_}", split (/::/, $var))); $expr = $self->encode_expression ($expr); return "$var = ($expr);\n"; } #---------------------------------------------------------------------- # Include the contents of a file as template code sub source_file { my $self = shift (@_); my $template = shift (@_); return $self->parse_template (slurp_file ($template)); } #---------------------------------------------------------------------- # Read a file into a string sub slurp_file { my ($source) = @_; $source =~ s/\s+$//; local $/; my $text; my $in = FileHandle->new ($source); return $text unless defined $in; flock ($in, 1); $text = <$in>; $in->close; return $text; } 1; __END__ =pod =head1 NAME DynamicTemplate.pm =head1 SYNOPSIS An improved interpolator for Blosxom 3 that is upwardly compatible with the old one. =head1 INSTALLATION Place this file in the plugins directory. Add the line Blosxom::Plugin::DynamicTemplate::install to the data/.settings/handlers.flow file after Blosxom::handle_handlers and before Blosxom::run_entries. =head1 BUGS Because the version of Blosxom 3 that is currently being distributed ignores the handlers.flow file, you need to first install a patch to Blosxom 3 to fix this problem, such as the one distributed on this site. =head1 DESCRIPTION DynamicTemplate replaces the interpolation routine in Blosxom 3 with a new routine that allows commands to be interspersed in the usual template code. Commands start with a sharp character (C<#>). command. The command name must immediately follow the sharp character and continues up to the first white space character. The text following the initial span of whitespace is the command argument. The argument continues up to the end of the line. DynamicTemplate has the following commands =over 4 =item #if The text until the matching C<#endif> is included only if the expression in the C<#if> command is true.If false, the text is skipped. The C<#if> command can contain an C<#else>, in which case the text before the C<#else> is included if the expression in the C<#if> command is true and the text after the C<#else> is included if it is false. You can also place an C<#elsif> command in the C<#if> block, which includes the following text if its expression is true. #if $highlight eq 'y' $text #else $text #endif =item #include Include the contents of the named file in the line's place. No interpolation is done on the contents, so your dollar signs are safe. #include blogroll.inc Relative file names (those names not starting with a slash) are relative to the location of your Blosxom script. =item #require Requires the Perl module named as the argument. Used when the template calls a subroutine in an external module. #require Cwd =item #set Add a new value to the crrent_entry hash. The argument following the command name looks like any Perl assignment statement minus the trailing semicolon. The expression may contain variables, which look like any other interploated variable in the template. #set $now = localtime Variables used in the expression are translated into calls to Interpolate and can only be used in contexts where a subroutine call is allowed in Perl. In particular, string interpolation will not work. =item #source Include the contents of the named file in the line's place. Variables and commands within the sourced file are evaluated as if they were present in the template. For example, #source logo.inc Relative file names (those names not starting with a slash) are relative to the location of your Blosxom script. =item filters Any word that follows a sharp character other than the reserved keywords invokes a filter. A filter is a subroutine that takes the text following it as its only argument and returns a modified version of the text. The filter is applied after variable substitution is done. The filter command must be followed by an endfilter command, whose name is C<#end> followed by the filter name. No other commands, such as C<#set>, can intervene between the two. The endfilter command indicates the end of the text that the filter operates on. For example, #uc $title #enduc will convert the title to upper case. You can write your own filters. Filters are subroutines that take a single input variable, which is a string consisting of the text enclosed by the filter. The output of the filter is the transformed text. Filters should start with the statement package Blosxom::Plugin::DynamicTemplate; and should be placed in the plugins directory. =back =head1 AUTHOR Bernie Simon (http://careleshand.net/) =head1 LICENSE Copyright Bernard Simon, 2005. You may use this file as you wish as long as this copyright notice is maintained.