#!/usr/bin/perl
##
## h3. Annotation credits
##
## This is version 0.9 of the Blosxom 2 annotations, by Frank Hecker
## <hecker@hecker.org>. These
## annotations to the blosxom.cgi source code are made available
## under the same license terms as Blosxom itself. Comments and suggestions
## for changes are welcome.
##
## The "online Perl documentation":http://www.perldoc.com/ was indispensable
## in creating these notes. The notes2html script was used to
## create HTML documents from the inline notes.
##
## For more information see the following URLs:
##
## http://www.blosxom.com/downloads/blosxom.zip
## http://www.blosxom.com/license.html
## http://www.hecker.org/blosxom/
###
### h3. For people learning Perl: Comments and #!
###
### All lines starting with '#' are comments, not part of the code itself.
### The first line uses the standard Unix #! convention to identify
### the location of the Perl interpreter. You would need to change this
### line if for some reason the Perl interpreter were in a different directory
### or had a different name.
###
### For more information see the following URL:
###
### http://www.perldoc.com/perl5.8.4/pod/perlrun.html
## h3. Overview of the Blosxom 2 source code
##
## The Blosxom source file is divided into three major sections:
##
## * initial code (lines 1-8) and the configurable variable section (lines
## 12-65) containing variables that need to be modified in order to set up a
## new weblog (as described in the
## "Blosxom user documentation":http://www.blosxom.com/documentation/users/configure/).
## * the main Blosxom code (lines 69-424)
## * the data section defining the default html and rss flavours (lines
## 429-443)
##
## The main Blosxom code itself is further divided into various sections as
## discussed below.
##
## h4. Blosxom initialization (lines 69-214)
##
## In this section the following tasks are carried out:
##
## # declare various global variables (line 69)
## # import needed Perl modules (lines 71-76)
## # set the Blosxom version number (line 78)
## # define additional variables needed (lines 80-83):
## ** $fh: a FileHandle for reading files (line 80)
## ** %month2num: hash to convert a month abbreviation to a
## month number (line 82)
## ** @num2month: array to convert a month number to a month
## abbreviation (line 83)
## # tweak the values of selected configuration variables (lines 86-94):
## ** $url: if a value for the base URL wasn't defined in the
## configurable variables section, set a new value as described below (lines
## 86-88)
## ** $datadir: strip any trailing slash if present (line 91)
## ** $depth: adjust to account for the number of path components
## in $datadir (line 94)
## # extract information about the current requested page (lines 86-88,
## 97-125):
## ** $url: the part of the requested URL corresponding to the
## Blosxom script itself (e.g.,
## http://www.example.com/cgi-bin/blosxom.cgi) (lines 86-88)
## ** $static_or_dynamic: set to 'static' if Blosxom is running
## in static mode, 'dynamic' if Blosxom isrunning in dynamic mode, i.e.,
## through CGI (line 99)
## ** $path_info: the part of the requested URL identifying a
## particular category or individual entry to be displayed, e.g.,
## /society/literature or
## /cooking/italian/bruschetta.html (lines 97, 104-107, 121)
## ** $flavour: the particular flavour of data being requested,
## e.g., 'html' or 'rss' (lines 110-118)
## ** $path_info_yr, $path_info_mo, and
## $path_info_da: the dates for which we are requesting that
## entries be displayed (lines 124-125)
## # template initialization (lines 128-145, 161)
## ## define a default template routine and store a reference to it in
## $template (lines 128-137)
## ## read the default templates from the data section and store them in the
## %template hash keyed by the content type (e.g., 'html' or
## 'rss') and template component (e.g., 'head' or 'foot') (lines 139-145)
## ## override the default template subroutine with the template subroutine
## provided by the first plugin that defines one, if any do (line 161)
## # plugin initialization (lines 148-155)
## ## find all plugins in the plugin directory (lines 148-149)
## ## sort the plugins, taking into account prefixes like '00', '01', etc.
## (line 149)
## ## determine whether a given plugin is enabled or disabled (lines 150-151)
## ## import the plugin packages (line 152)
## ## populate the @plugins array (a list of plugin names, minus
## prefixes) and the %plugins hash (which stores the
## enabled/disabled status for each plugin, keyed by the plugin name)
## (line 153)
## # define a default subroutine to find entries in the data directory
## (storing a reference to the subroutine in $entries) (lines
## 169-214) and then allow overriding it by the first plugin that defines an
## alternate entries subroutine (line 219)
##
## h4. Finding (and filtering) Blosxom entries (lines 221-226)
##
## This section of the code looks for Blosxom entries and related items
## of interest, performing the following tasks:
##
## # search for entries using the subroutine referenced by
## $entries, and build up three hashes (lines 221-222):
## ** %files: files representing individual Blosxom entries
## (e.g., foo.txt if '.txt' is the standard Blosxom file
## extension)
## ** %indexes: directories for which index files might need
## to be created or updated as part of static page generation, as well as
## individual entry files for which static pages might need to be generated
## ** %others: all other files not falling into the above two
## categories
## # filter the list of files in %files and %others
## by invoking the filter subroutine for each and every plugin
## that defines one (line 225)
##
## The hashes %files and %others are keyed by the
## name of the entry file (for %files) or other item (for
## %others), in the form of an absolute pathname; the value for
## each element in %files or %others is the
## date/time last modified for the corresponding entry file or other item.
##
## The hash %indexes is keyed by the name of the directory or
## entry file for which static page generation should be done, expressed as a
## relative pathname relative to the Blosxom data directory (e.g., 'a/b' or
## '2004/05/22'); the value for elements in %indexes is 1 for
## elements corresponding to category directories or individual entries, and
## for elements corresponding to date directories is the same as the key
## (e.g., '2004/05/22').
##
## h4. Generating output (lines 228-270)
##
## The next section of the Blosxom code generates HTML or other output.
## For dynamic invocation of Blosxom this is relatively simple, since we need
## to generate only one page in response to the requested URL (lines 260-267):
##
## # determine the content type for the requested flavour and create the
## appropriate HTTP header (lines 261-264)
## # call the generate subroutine to create the page output,
## based on the category, date, entry, and flavour information from the
## requested URL (line 266)
## # print the output returned by generate (which includes the
## HTTP header for the appropriate content type) (line 266)
##
## For static invocation of Blosxom page generation is more complex, since
## we may need to generate several pages (lines 230-257):
##
## # loop through each element of %indexes (lines 234-256) and
## then for each element loop through each directory component of the item
## (directory or entry file) corresponding to the element (lines 236-255)
## ## create new directories wherever needed in order to hold index pages
## (line 241)
## ## for each of the required flavours specified by
## @static_flavours (lines 242-254)
## ### create (or rewrite) the required index page
## or static entry page (lines 245-247)
## ### call the generate subroutine to create the output for the
## page (lines 250-252)
## ### write the output to the static file and then close the file (lines
## 249-253)
##
## Finally, we loop through the plugins and call each plugin's end subroutine
## in order to do any final processing (line 270).
##
## h4. The generate subroutine (lines 273-412)
##
## The generate subroutine creates the actual output for a
## page of the desired flavour, taking as input the path information for
## the category, entry file, and/or date, along with the flavour and content
## type, and an indication of whether static or dynamic page generation is
## desired. The generate subroutine also uses the hashes
## %files, %indexes, and %others
## previously populated.
##
## The specific tasks performed by the generate subroutine are as
## follows:
##
## # define a default interpolate subroutine for variable
## interpolation in templates (lines 283-290)
## # call each plugin's skip subroutine and decide if page generation should
## be skipped, otherwise proceeding as described below (lines 280, 292)
## # allow one of the plugins to override the default interpolate
## subroutine (line 297)
## # generate output for the 'head' section (lines 300-307):
## ## determine the proper 'head' template to use, based on the default
## template subroutine or one provided by a plugin (line 300)
## ## allow the plugins to modify the 'head' template (line 303)
## ## interpolate the values of variables (e.g., $blog_title) in
## the 'head' template and add the result to the output (lines 305-307)
## # tweak the $currentdir argument, which holds information on
## the category and/or individual entry for which a page needs to be generated
## (lines 313-319)
## # if a page for an individual entry is to be generated, tweak the
## %f hash (a copy of %files) so that it contains
## information for just that entry (line 315)
## # define a default subroutine for sorting entries (by file modification
## times) and then allow a plugin to override it (lines 322-330)
## # loop through the (sorted) elements of the hashes %f and
## %others, each representing an entry to be added to the
## generated page (lines 332-392)
## ## for category index pages and the main index page, stop looking at
## entries once we've reached the maximum entries per page configured using
## $num_entries (line 333)
## ## skip entries that are in categories other than the one for which a
## page is being generated (line 338)
## ## skip entries whose dates don't match the date(s) for which a page is
## being generated (line 344-354)
## ## do date processing (lines 357-364):
## ### get the appropriate template for the 'date' section (line 357)
## ### allow plugins to modify the template (line 360)
## ### interpolate variables into the template, including the actual date
## values (line 362)
## ### generate output for the date if needed, e.g., at the beginning of a set
## of entries for the same date (line 364)
## ## read the entry file to obtain the entry title (the first line of the
## file) and body (the rest of the file), and generate output for the entry
## (lines 366-389):
## ### get the appropriate template for the 'story' section (line 373)
## ### allow plugins to modify the story template (line 376)
## ### for RSS and similar types of XML-based content, replace problematic
## characters in the story template with the corresponding character entities
## (lines 378-384)
## ### interpolate variables into the story template (line 386)
## ### generate output for the story (line 388) and prepare to process the
## next entry file, if any (lines 389-391)
## # generate output for the 'foot' section (lines 395-401)
## ## determine the proper 'foot' template to use, based on the default
## template subroutine or one provided by a plugin (line 395)
## ## allow the plugins to modify the 'foot' template (line 398)
## ## interpolate the values of variables in the 'foot' template and add the
## result to the output (lines 400-401)
## # call the last subroutines for each and every plugin that defines one, to
## do any final processing for the page (line 404)
## # prepend the HTTP header if needed, and return the generated output
## (lines 409-411)
##
## h4. The nice_date subroutine (lines 415-424)
##
## The nice_date subroutine converts OS-provided time values
## (expressed as the number of seconds since some fixed date) into year,
## month, day, etc., values that we can use for printing date/times and
## creating date-based URLs. For more information see the notes for lines
## 415-424.
##
# Blosxom
# Author: Rael Dornfest
# Version: 2.0
# Home/Docs/Licensing: http://www.raelity.org/apps/blosxom/
## h3. For people learning Perl: Packages
##
## package defines a namespace for variables, subroutines, etc.,
## so that their names won't conflict with names defined in other Perl code
## used by Blosxom and pulled in from other places.
##
## See the following URL for more information:
##
## http://www.perldoc.com/perl5.8.4/pod/perlmod.html#Packages
##
package blosxom;
## h3. For people learning Perl: Package "global" variables
##
## The scope of the configurable variables is within the blosxom package.
## We put "global" in quotes because, as the Perl documentation notes,
## "there's really no such thing as a global variable in Perl", in the sense
## of global variables as used in C and similar languages. However the
## configurable variables are like global variables in that their values are
## visible anywhere in the Blosxom code (unless "hidden" by other variable
## declarations as described in the notes to line 171). See also the notes
## to line 69.
##
## The configurable variables can be referenced from Blosxom plugins as
## $blosxom::foo where $foo is a variable.
## Alternatively, a Blosxom plugin can include a package blosxom
## statement prior to a section of code to allow Blosxom configurable variables
## to be referenced within that code section without having to preface the
## variables' names with "blosxom::". (For example, a plugin would do this
## when defining its own version of the interpolate subroutine;
## see the notes to lines 283 and 285 for more information.)
##
## See the following URL for more information on variable scope:
##
## http://www.perldoc.com/perl5.8.4/pod/perlmod.html
##
## Note when reading the documentation that the configurable variables are
## considered to be "dynamic" (as opposed to "lexical") variables.
##
# --- Configurable variables -----
# What's this blog's title?
##
## h3. For people learning Perl: Scalar variables
##
## In Perl a variable starting with '$' is a scalar (i.e., single-valued)
## variable. Note that unlike shell syntax the '$' is used when assigning
## to the variable as well as when using its value.
##
## The $blog_title variable is used to hold a string. Like shell
## variables Perl scalar variables can have either string or numeric values.
## String values can be delimited by either single quotes or double quotes;
## like the Unix shell, if the string is within double quotes then it can
## include references to other Perl variables (e.g., "A Blog by
## $author") and the values of those variables will be interpolated
## into the string, replacing the variable references.
##
## Because of this variable interpolation, if you want to use a '$' in your
## blog title or description then you need to either precede the '$' with a
## '\' ("My \$64,000 Blog") or use single quotes
## to delimit the string ('My $64,000 Blog'). (If you use single
## quotes for your string delimiter then you will also need to escape any
## single quote character in the string itself by preceding it with a '\',
## e.g., 'John\'s $64,000 Blog'; a similar rule holds when you
## want to include a double quote in a string delimited by double quotes.)
##
## For more information on Perl scalar variables see the following URL:
##
## http://www.perldoc.com/perl5.8.4/pod/perldata.html#Scalar-values
##
$blog_title = "My Weblog";
# What's this blog's description (for outgoing RSS feed)?
$blog_description = "Yet another Blosxom weblog.";
# What's this blog's primary language (for outgoing RSS feed)?
$blog_language = "en";
# Where are this blog's entries kept?
$datadir = "/Library/WebServer/Documents/blosxom";
# What's my preferred base URL for this blog (leave blank for automatic)?
$url = "";
# Should I stick only to the datadir for items or travel down the
# directory hierarchy looking for items? If so, to what depth?
# 0 = infinite depth (aka grab everything), 1 = datadir only, n = n levels down
$depth = 0;
# How many entries should I show on the home page?
$num_entries = 40;
# What file extension signifies a blosxom entry?
$file_extension = "txt";
# What is the default flavour?
$default_flavour = "html";
# Should I show entries from the future (i.e. dated after now)?
$show_future_entries = 0;
# --- Plugins (Optional) -----
# Where are my plugins kept?
$plugin_dir = "";
# Where should my modules keep their state information?
$plugin_state_dir = "$plugin_dir/state";
# --- Static Rendering -----
# Where are this blog's static files to be created?
$static_dir = "/Library/WebServer/Documents/blog";
# What's my administrative password (you must set this for static rendering)?
$static_password = "";
# What flavours should I generate statically?
##
## h3. For people learning Perl: Array variables and qw
##
## In Perl a variable starting with '@' is an array variable that holds
## an ordered list of values indexed by array position (starting from 0
## as the first position).
##
## Here we define a 2-element array with the string values 'html' and 'rss'.
## qw is a function that returns a list of words extracted out
## of a string enclosed within delimiters, e.g., qw/a b/ is the
## same as 'a', 'b'. (Alternately you could use
## qw(a b) or qw! a b ! or whatever.) This is a
## very common Perl idiom, as it eliminates the need to quote each and every
## word within the list.
##
## For more information see the following URLs:
##
## http://www.perldoc.com/perl5.8.4/pod/perldata.html
## http://www.perldoc.com/perl5.8.4/pod/perlop.html#Regexp-Quote-Like-Operators
##
@static_flavours = qw/html rss/;
# Should I statically generate individual entries?
# 0 = no, 1 = yes
$static_entries = 0;
# --------------------------------
## h3. For people learning Perl: use vars
##
## Here we declare global variables used in this package (actually,
## within the file, but the file just contains a single package).
## Note that use vars was deemed obsolete as of Perl 5.6, being
## replaced by our, but as used here supports use of Blosxom with
## earlier Perl 5.x versions.
##
## For more information see the following URLs:
##
## http://www.perldoc.com/perl5.8.4/pod/perlmodlib.html#Pragmatic-Modules
## http://search.cpan.org/~nwclark/perl-5.8.4/lib/vars.pm
##
use vars qw! $version $blog_title $blog_description $blog_language $datadir $url %template $template $depth $num_entries $file_extension $default_flavour $static_or_dynamic $plugin_dir $plugin_state_dir @plugins %plugins $static_dir $static_password @static_flavours $static_entries $path_info $path_info_yr $path_info_mo $path_info_da $path_info_mo_num $flavour $static_or_dynamic %month2num @num2month $interpolate $entries $output $header $show_future_entries %files %indexes %others !;
## h3. For people learning Perl: use strict
##
## use strict tells Perl to produce compiler warnings for all
## sorts of things, such as references to variables that were not previously
## defined or declared.
##
## For more information see the following URL:
##
## http://search.cpan.org/~nwclark/perl-5.8.4/lib/strict.pm
##
use strict;
##
## h3. For people learning Perl: Importing modules with
## the use function
##
## The next few lines import various Perl modules, making their functions and
## global variables available without needing to qualify the names with package
## names. (In other words, we can refer to bar() rather than
## foo::bar() where bar is a function in the
## package foo.)
##
## On packages vs. modules: per the documentation, "A module is just a set of
## related functions in a library file, i.e., a Perl package with the same
## name as the file." Strictly speaking Blosxom 2.0 is a package but not a
## module; however Blosxom 3.0 will be a full-fledged module.
##
## For more information see the following URL:
##
## http://www.perldoc.com/perl5.8.4/pod/func/use.html
###
### h3. FileHandle
###
### The FileHandle module contains functions for basic file I/O operations:
### open, new, getc, gets,
### seek, close, etc.
###
### For more information see the following URL:
###
### http://search.cpan.org/~nwclark/perl-5.8.4/lib/FileHandle.pm
###
use FileHandle;
##
## h3. File::Find
##
## The File::Find module contains functions to traverse a directory tree in
## the file system, analogous to the Unix find command. Blosxom
## uses File::Find functions and variables in its own find subroutine below.
##
## For more information see the following URL:
##
## http://search.cpan.org/~nwclark/perl-5.8.4/lib/File/Find.pm
##
use File::Find;
##
## h3. File::stat
##
## The File::stat module gets a file's attributes, like the Unix
## stat kernel routine.
## Blosxom uses File::stat functions and variables to get the date/time
## modified for entry files and related information.
##
## For more information see the following URL:
##
## http://search.cpan.org/~nwclark/perl-5.8.4/lib/File/stat.pm
##
use File::stat;
##
## h3. Time::localtime
##
## The Time::localtime module gets the current date and time and performs
## other date/time-related operations, like the corresponding Unix functions.
## Blosxom uses Time::localtime functions in the subroutine
## nice_date and elsewhere.
##
## For more information see the following URL:
##
## http://search.cpan.org/~nwclark/perl-5.8.4/lib/Time/localtime.pm
##
use Time::localtime;
##
## h3. CGI
##
## The CGI module is used to parse incoming HTTP requests (e.g., to get
## the URL being requested) and to create HTTP headers and HTML pages sent
## in response (see the subroutine generate for an example).
##
## Note that :standard imports a standard set of functions and
## :netscape imports optional functions for Netscape-specific
## HTML extensions.
##
## For more information see the following URL:
##
## http://search.cpan.org/~lds/CGI.pm-3.05/CGI.pm
##
use CGI qw/:standard :netscape/;
## h3. The Blosxom version
##
## Blosxom 2.0 is considered stable. Blosxom 3.0 is currently in development.
##
$version = "2.0";
## h3. For people learning Perl: my variables
##
## my creates a private variable visible only within the lexical
## scope within which it is defined (e.g., within a given code block enclosed
## by curly braces), and not visible anywhere else (including subroutines
## called from a given code block). In this case the lexical scope is
## considered to be the entire blosxom package within the
## blosxom.cgi source file.
##
## For more information see
##
## http://www.perldoc.com/perl5.8.4/pod/perlintro.html#Variable-scoping
## http://www.perldoc.com/perl5.8.4/pod/perlsub.html#Private-Variables-via-my()
###
### h3. For people learning Perl: Creating objects with new
###
### The FileHandle module presents an object-oriented interface, so
### new in this context produces a new instance of the
### FileHandle class.
###
### In object-oriented terms new is a "constructor", i.e.,
### a so-called "class method" that creates and initializes new objects.
### Unlike object-oriented languages like C++, in Perl a constructor could
### be called something other than "new", but it's a common convention.
###
### For more information see
###
### http://search.cpan.org/~nwclark/perl-5.8.4/lib/FileHandle.pm
### http://www.perldoc.com/perl5.8.4/pod/perlobj.html
###
my $fh = new FileHandle;
## h3. For people learning Perl: Hashes
##
## We create a hash table (or plain "hash" in Perl jargon) with month names
## being the keys and month numbers (as strings) being the values. Hashes
## are initialized by providing a list in which the odd entries are the key
## values and the even entries are the corresponding values, e.g.,
## ('key1', 'value1', 'key2', 'value2'). The syntax
## (a=>'b', c=>'d') is
## equivalent to ('a','b','c','d') and is intended to make hash
## initialization more understandable.
##
## For more information see the following URL:
##
## http://www.perldoc.com/perl5.8.4/pod/perldata.html
##
%month2num = (nil=>'00', Jan=>'01', Feb=>'02', Mar=>'03', Apr=>'04', May=>'05', Jun=>'06', Jul=>'07', Aug=>'08', Sep=>'09', Oct=>'10', Nov=>'11', Dec=>'12');
##
## h3. For people learning Perl: The keys and sort
## functions
##
## This takes the list of keys in the previously-defined hash table, i.e.,
## the list ('nil', 'Jan', 'Feb', ..., 'Dec'), sorts it using a
## comparison function that compares the corresponding values in the hash
## table for each key, i.e., the values '00', '01', etc., and then assigns the
## resulting sorted list of keys to an array indexed by month number.
##
## This is equivalent to defining the array as follows:
##
## @num2month = ('nil', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
## 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
##
## (Note that the 'nil' value is included because Perl arrays are indexed
## from 0 but month numbers start at 1.)
##
## keys is a function that takes as an argument a hash and
## returns either a list consisting of all the keys in the hash (if used in
## list context) or the number of keys in the hash (if used in scalar
## context). Here we're using keys in list context, because as
## noted below the sort function expects a list as an argument.
##
## sort is a function that takes as arguments the list of items
## to be sorted and (as an optional first argument) a subroutine defining how
## sort comparisons are to be done; in this case that subroutine is an
## "anonymous" inline routine enclosed in curly braces. $a and
## $b are special global variables used to hold the values being
## sorted at each step of the sort algorithm; <=> is a comparison
## operator that returns -1, 0, or 1 depending on whether the first item is
## respectively less than, equal to, or greater than the second. (This is a
## numeric comparison.)
##
## For more information see the following URLs:
##
## http://www.perldoc.com/perl5.8.4/pod/func/keys.html
## http://www.perldoc.com/perl5.8.4/pod/func/sort.html
## http://www.perldoc.com/perl5.8.4/pod/perlop.html#Equality-Operators
##
@num2month = sort { $month2num{$a} <=> $month2num{$b} } keys %month2num;
# Use the stated preferred URL or figure it out automatically
##
## h3. For people learning Perl: The ||= operator
##
## This defines $url to be its existing value (if it has one) or
## (if it has no value) the value returned by the url
## function (part of the CGI module) as described in the next note.
## (Perl has different namespaces for variables and functions, which
## is why we can name the variable the same as the function.)
##
## The || operator is a logical "or" operator similar (but not
## identical) to that used in shell or C programming;
## $url ||= url(); is equivalent to
## $url = $url || url(); where the original value of
## $url is considered false if it is undefined or its value
## is the empty string '', and true otherwise. So if $url
## already has a value then the second part of the conditional expression
## (after ||) is not executed, and that existing value is
## (re)assigned to $url; otherwise the second part is executed
## to obtain the returned value from url(), and that value is
## assigned to $url.
##
## For more information see the following URLs:
##
## http://www.perldoc.com/perl5.8.4/pod/perlop.html#C-style-Logical-Or
## http://www.perldoc.com/perl5.8.4/pod/perlop.html#Assignment-Operators
###
### h3. The value of url()
###
### Note that url() returns only the URL of the Blosxom CGI
### script itself, not the full URL being requested. Thus (for example) if
### the HTTP request were for the URL
###
### http://www.example.com/cgi-bin/blosxom.cgi/2004/05/22
###
### then url() would return (and $url would be set
### to) the URL
###
### http://www.example.com/cgi-bin/blosxom.cgi
###
### If you have configured the web server to hide the blosxom.cgi
### part of the URL (as described in the FAQ referenced below) then the value
### of url() will be that part of the full URL which was
### translated into the script location. For example, if you configured Apache
### using the ScriptAlias directive as follows:
###
### ScriptAlias /blog "/var/www/cgi-bin/blosxom.cgi"
###
### then if the requested URL were
###
### http://www.example.com/blog/2004/05/22
###
### then url() would return (and $url would be set
### to) the URL
###
### http://www.example.com/blog
###
### For more information see the following URLs:
###
### http://search.cpan.org/~lds/CGI.pm-3.05/CGI.pm#OBTAINING_THE_SCRIPT'S_URL
### http://www.blosxom.com/faq/cgi/hide_cgi_bit.htm
###
$url ||= url();
##
## h3. Server Side Includes and Blosxom
##
## We assign $url a new value consisting of its previous value
## with the initial string "included:" (if present) replaced with "http:".
## This is intended for the case when output from bloxsom.cgi is
## included in an HTML file by a Server Side Include directive like the
## following:
##
##
##
## When invoked in this way the URL returned by url() above
## would be (for example)
##
## included://www.example.com/cgi-bin/blosxom.cgi
##
## instead of
##
## http://www.example.com/cgi-bin/blosxom.cgi
##
## For more information see the following URL:
##
## http://httpd.apache.org/docs-2.0/howto/ssi.html
##
### h3. For people learning Perl: The =~ operator and regular
### expression matching
###
### =~ is a special operator that takes the left side
### ($url) and applies to it a pattern match specified on the
### right side (s/^included:/http:/),
### in this case a pattern match that actually does substitution, using
### regular expressions modeled on those used in the Unix shell and utilities.
### (So, for example, in this case the '^' tells Perl to look for a match
### starting at the beginning of the string.) The result is that the value
### of $url is modified if the match succeeds.
###
### For more information see the following URLs:
###
### http://www.perldoc.com/perl5.8.4/pod/perlop.html#Binding-Operators
### http://www.perldoc.com/perl5.8.4/pod/perlop.html#Regexp-Quote-Like-Operators
###
$url =~ s/^included:/http:/; # Fix for Server Side Includes (SSI)
##
## h3. For people learning Perl: More on regular expressions and substitution
##
## This statement strips off a trailing slash from the URL value if present;
## the '$' in the regular expression tells Perl to look for a match at
## the end of the string.
##
## Note that the value returned by the url() function doesn't
## have a trailing slash, but the person configuring Blosxom may have
## included a trailing slash when specifying a non-default value for the
## $url variable.
##
$url =~ s!/$!!;
# Drop ending any / from dir settings
$datadir =~ s!/$!!; $plugin_dir =~ s!/$!!; $static_dir =~ s!/$!!;
# Fix depth to take into account datadir's path
##
## h3. Adjusting $depth
##
## If $depth is non-zero (i.e., limiting search to n
## directories deep) then we take the $datadir path, count the
## number of path components, subtract 1, and add that to $depth
## to get the new value. For example, if the value of $data_dir
## is /a/b/c then a $depth value of 2 would get
## changed to a value of 4.
##
## The new value of $depth can be interpreted as follows: Search
## through a directory only if the number of components in its path is
## $depth or less. So in the previous example the directory
## /a/b/c/d would be searched but the directory
## /a/b/c/d/e would not.
##
### h3. For people learning Perl: The tr and and
### operators
###
### Counting the number of path components is done using the tr
### operator, which is typically used to modify a string by transliterating
### one set of characters with another, e.g., $s =~ tr[a-z][A-Z]
### to change lowercase characters in $s to uppercase. However in
### this case the set of replacement characters is empty ([]) so
### no replacement is done; instead we simply use the standard return value
### from tr, namely the number of times the character(s) in the
### search list (i.e., the '/' character in this case) was found.
###
### Since the value of $datadir is an absolute path (i.e., it
### starts with '/') and we trimmed any trailing '/' characters (see above)
### the number of '/' characters will be equal to the number of components
### in the path.
###
### [Note: There is a minor potential bug here: If the value of
### $datadir were specified with multiple trailing slashes, e.g.,
### /a/b/c//, then the code above would remove only a single
### trailing '/', leaving one extra '/' at the end, and the number of
### directory components would be miscounted as being higher than it actually
### is. The fix is simple: Replace the existing statement
### $datadir =~ s!/$!!; with the statement
### $datadir =~ s!/*$!!; to look for zero or more '/' characters
### at the end of the string and remove any found; even safer would be
### $datadir =~ s!/*\s*$!!; to remove trailing whitespace as
### well.]
###
### The and operator here is used to conditionally change
### $depth only if it is non-zero; if $depth is
### zero then it is interpreted as false and the expression after the
### and is not executed. However any non-zero value will be
### interpreted as true and $depth modified as described above.
###
### For more information see the following URLs:
###
### http://www.perldoc.com/perl5.8.4/pod/perlop.html#Quote-and-Quote-like-Operators
### http://www.perldoc.com/perl5.8.4/pod/perlreref.html
### http://www.perldoc.com/perl5.8.4/pod/perlop.html#Logical-And
###
$depth and $depth += ($datadir =~ tr[/][]) - 1;
# Global variable to be used in head/foot.{flavour} templates
$path_info = '';
## h3. Static vs. dynamic mode
##
## We set the variable $static_or_dynamic to 'static' or
## 'dynamic' to reflect the mode we're in. We're in static mode if all the
## following are true:
##
## * the GATEWAY_INTERFACE environment variable is not set (i.e., we
## are not executing as a CGI routine)
##
## * the parameter -password has a value
##
## * the variable $static_password is defined (see above)
##
## * the value of the -password parameter is the same as the
## value of $static_password
##
## Otherwise we're in dynamic mode.
##
### h3. For people learning Perl: The eq operator and
### CGI::param() function
###
### We saw the and operator above. The eq operator
### tests for string equality. The expression $a ? $b : $c is like
### that used in C: if $a is true then return $b,
### otherwise return $c.
###
### param() is a CGI function, but it can also return values when
### the Perl script is invoked from the command line, e.g.
###
### perl blosxom.cgi -password='secret'
###
### would assign the string value 'secret' to the parameter
### -password.
###
### For more information see the following URLs:
###
### http://www.perldoc.com/perl5.8.4/pod/perlop.html#Equality-Operators
### http://www.perldoc.com/perl5.8.4/pod/perlop.html#Conditional-Operator
### http://search.cpan.org/~lds/CGI.pm-3.05/CGI.pm#DEBUGGING
### http://www.blosxom.com/documentation/users/configure/static.html
###
$static_or_dynamic = (!$ENV{GATEWAY_INTERFACE} and param('-password') and $static_password and param('-password') eq $static_password) ? 'static' : 'dynamic';
##
## h3. For people learning Perl: Setting parameters using the
## CGI::param() function
##
## If we're in dynamic mode then we set the value of the -quiet
## parameter to be 1. When setting parameters the param()
## function takes an argument list similar in syntax to the way hashes are
## initialized, e.g., param(-name=>'a', -value=>'b') would set
## the parameter a to the value b.
##
## For more information see the following URL:
##
## http://search.cpan.org/~lds/CGI.pm-3.05/CGI.pm#SETTING_THE_VALUE(S)_OF_A_NAMED_PARAMETER:
##
$static_or_dynamic eq 'dynamic' and param(-name=>'-quiet', -value=>1);
# Path Info Magic
# Take a gander at HTTP's PATH_INFO for optional blog name, archive yr/mo/day
##
## h3. PATH_INFO
##
## PATH_INFO (the CGI environment variable whose value is
## returned by the path_info function) contains any path
## information in the URL after the part of the URL that identifies the CGI
## script. For example, if the requested URL were
## http://www.example.com/cgi-bin/blosxom.cgi/2004/05
## then the value returned by path_info() would be
## /2004/05.
##
## For more information see the following URL:
##
## http://search.cpan.org/~lds/CGI.pm-3.05/CGI.pm#FETCHING_ENVIRONMENT_VARIABLES
##
### h3. For people learning Perl: The split function
###
### We use my to define a private array variable
### @path_info. To set this variable we first use the
### split function on the string returned by the
### path_info() function (if it's non-empty), splitting that
### value into different components separated by the '/' character.
### (m{/} is a regular expression that will match a single '/'.)
### The split function returns a list of strings, which is why
### we use an array to hold the result.
###
### If for some reason path_info() returns an empty string then
### we split the value of the path CGI parameter instead. This
### would allow you to use a URL like
###
### http://www.example.com/cgi-bin/blosxom.cgi?path=/2004/05/22
###
### if you wished to do so. Note that the || operator has a
### higher precedence than the comma operator, so the decision whether to
### use the value of path_info() or param('path')
### is made before that value is passed to the split function.
###
### For more information see the following URL:
###
### http://www.perldoc.com/perl5.8.4/pod/func/split.html
### http://www.perldoc.com/perl5.8.4/pod/perlop.html#Operator-Precedence-and-Associativity
###
my @path_info = split m{/}, path_info() || param('path');
###
### h3. For people learning Perl: The shift function
###
### The shift function discards the first element of an array.
### We do this because path_info() returns a path with an initial
### '/', and the split function as used above on that path will
### produce an empty string as the first element of the returned array; for
### example, the expression split m{/}, "/a/b/c" will return the
### list ('', 'a', 'b', 'c'). We don't want the initial empty
### string so we use shift to get the list
### ('a', 'b', 'c') instead.
###
### For more information see the following URL:
###
### http://www.perldoc.com/perl5.8.4/pod/func/shift.html
###
shift @path_info;
## h3. Interpreting Blosxom URLs
##
## Recall that after the part of the URL that references the Blosxom script
## itself (stored in $url), a Blosxom URL can contain an
## additional path consisting of three
## possible parts: an optional set of categories, an optional set of year,
## month, and day values, and an optional reference to an individual entry. For
## example, the following are values that might be returned by the
## path_info() function as applied to Blosxom URLs:
##
## /society/literature
## /2004/05/19
## /music/index.rss
## /personal/resolutions/2003/07
## /cooking/italian/bruschetta.html
##
## From the path returned by path_info() we end up setting the
## following variables:
##
## * $path_info: either an individual entry path including
## categories, subcategories, and entry name (e.g.,
## /cooking/italian/bruschetta.html) or a category/subcategory
## path for which we wish to see all entries (e.g.,
## /society/literature or /music)
## * $flavour: the desired flavour, whether explicitly specified
## in the URL (e.g., 'html' for /cooking/italian/bruschetta.html
## or 'rss' for /music/index.rss) or defaulted (e.g., as in
## /society/literature/)
## * $path_info_yr, $path_info_mo_num, and
## $path_info_da: the year, month, and day if present in the URL
## (e.g., for /personal/resolutions/2003/07 the year and month
## would be '2003' and '07' respectively while the day would be undefined)
##
## Our first task is to extract the path information relating to categories;
## since we know that category names can't begin with a digit we can simply
## look for path components starting with alphabetic characters. However
## we have to stop before we get to any reference to an individual entry;
## we identify such entries by the presence of a '.' character in their
## names.
##
## [Note: This implies two additional restrictions in Blosxom as
## currently designed: you can't have a category name containing a '.',
## and you can't reference individual entries using URLs that don't have
## a file extension at the end (as recommended by the W3C, among others.]
##
## For more information see the following URLs:
##
## http://www.blosxom.com/documentation/users/view.html
## http://www.w3.org/Provider/Style/URI
##
### h3. For people learning Perl: The while loop
###
### A while loop executes a block of code (in curly braces) as
### long as a given condition (in parentheses) is true. In this case before
### executing the code block we first check to see if the first element of
### @path_info is defined and non-empty; otherwise there are no
### more components and we're done. ($a[i] is the i'th
### element of the array @a; note that it's distinct from the
### scalar variable $a.)
###
### If we have a further component, we then check to see if its value starts
### with an alphabetic character, by trying to match it against the regular
### expression character class [a-zA-Z] starting at the beginning
### of the string ('^'); otherwise the component represents a date and not a
### category, and we're done.
###
### Finally we check to verify that the component's value does not have a
### literal period (\.) in it; otherwise the component represents
### an individual entry (e.g., "a.html") and we're done. (The operator
### !~ is the reverse of =~, returning a true value
### when the pattern match fails.)
###
### See the notes for line 112 below for the meaning of the parentheses
### in the regular expression /(.*)\.(.*)/ used to check for a
### period in in the path component. For now we simply note that as used here
### the regular expression could have been replaced with the simpler regular
### expression /.*\..*/ without affecting things.
###
### If the first element of @path_info looks like a category then
### we append it to the scalar variable $path_info, preceded by a
### '/', and remove the element from the @path_info array.
### ($path_info was defined above, with its initial value set to
### the empty string.) Note that shift @path_info both does the
### removal and returns the removed element as a result. The .
### operator concatenates two strings, in this case '/' and the removed
### first element. The .= assignment operator is like the
### ||= and += operators seen above, so that
### $a .= 'b' is the same as $a = $a . 'b', where
### the . operator concatenates two strings.
###
### For more information see the following URL:
###
### http://www.perldoc.com/perl5.8.4/pod/perlsyn.html#Compound-Statements
### http://www.perldoc.com/perl5.8.4/pod/perlreref.html#CHARACTER-CLASSES
### http://www.perldoc.com/perl5.8.4/pod/perlop.html#Additive-Operators
###
while ($path_info[0] and $path_info[0] =~ /^[a-zA-Z].*$/ and $path_info[0] !~ /(.*)\.(.*)/) { $path_info .= '/' . shift @path_info; }
# Flavour specified by ?flav={flav} or index.{flav}
$flavour = '';
## h3. Determining the flavour, part 1
##
## If the flavour is specified by index.{flav}, as in
##
## http://www.example.com/cgi-bin/blosxom.cgi/music/index.rss
##
## then it must be parsed from the PATH_INFO value stored in
## @path_info. However if the flavour is specified by
## ?flav={flav}, as in
##
## http://www.example.com/cgi-bin/blosxom.cgi/music?flav=rss
##
## then its value must be obtained using param(), since anything
## in the URL after a '?' is considered a CGI parameter and not part of
## PATH_INFO.
##
### h3. For people learning Perl: $#path_info
###
### $#path_info returns the index of the last element of the array
### @path_info. We match the value of that last element against a
### regular expression consisting of one or more characters followed by a
### literal '.' character followed by one or more characters to the end of the
### string. This match will succeed when the last element looks like, e.g.,
### 'a.b', where we'll interpret 'b' as the flavour.
###
### (Note that this regular expression is slightly different from the one
### used in the while loop on line 107; the previous expression
### matched zero or more characters followed by a '.' followed by zero or more
### characters. In other words, the test at line 107 will match .
### by itself, .a, a., and so on, while the test
### here will not. In practice this doesn't matter: the first test was simply
### intended to reject path components that weren't categories, which can't
### contain '.'; the second test is intended to find flavour values, and for
### that purpose we need a component that actually has something after the
### '.', as well as before.)
###
### The regular expression uses parentheses to save parts of the component
### that are matched, for later use. In particular, the regular expression
### /(.+)\.(.+)/ is used (instead of the simpler
### /.+\..+/) to save the flavour value (matched by the
### expression in the second set of parentheses) and the entry name (matched
### by the expression in the first set of parentheses). The saved values can
### then be referenced by the special variables $1 (first part
### matched, the entry name) and $2 (second part matched, the
### flavour).
###
### For more information see the following URLs:
###
### http://www.perldoc.com/perl5.8.4/pod/perldata.html#Variable-names
### http://www.perldoc.com/perl5.8.4/pod/perlreref.html#SYNTAX
###
if ( $path_info[$#path_info] =~ /(.+)\.(.+)$/ ) {
##
## h3. For people learning Perl: "Greedy" matching
##
## If the last value in @path_info does contain a '.' character
## then as noted above the value of the variable $2 will be the
## string to the right of the '.', and we save that value in
## $flavour.
##
## Because of the way regular expression matching works, if the final
## component actually has two or more periods, e.g., "example.com-news.html",
## $2 will be set to the string after the final '.', not the
## string after the first one. This "greedy" matching (i.e., match as many
## characters as you can) is exactly what we want to happen.
##
## For more information see the following URL:
##
## http://www.perldoc.com/perl5.8.4/pod/perlreref.html#QUANTIFIERS
##
$flavour = $2;
##
## h3. Indexes vs. individual entries
##
## If the first part (before the '.') of the last path component is not
## equal to 'index' then that component points to an individual entry,
## and we save both the entry name and flavour by appending them to the
## $path_info variable that stores the category components of
## the path.
##
## On the other hand, if the first part is 'index' then the original URL
## was not a request for an individual entry but rather a request for
## all entries in a particular category or for a particular day, month,
## or year, displayed using a specified flavour. For such requests the path
## might be something like /a/b/index.rss or
## /2004/05/index.rss.
## In this case we don't need to save the value 'index.rss' (or whatever)
## as part of $path_info, since all we need is the flavour value.
##
$1 ne 'index' and $path_info .= "/$1.$2";
##
## h3. For people learning Perl: The pop function
##
## Now that we've extracted the needed information from the last element
## of @path_info we use the pop function to remove
## it.
##
## For more information see the following URL:
##
## http://www.perldoc.com/perl5.8.4/pod/func/pop.html
##
pop @path_info;
} else {
##
## h3. Determining the flavour, part 2
##
## If the final component of the path does not contain a period then either
## the flavour was specified using the flav parameter, as in the
## URL
##
## http://www.example.com/cgi-bin/blosxom.cgi/a/b?flav=rss
##
## or the flavour was omitted entirely. In the latter case we set
## $flavour to the default flavor defined in the configurable
## variables section.
##
$flavour = param('flav') || $default_flavour;
}
# Strip spurious slashes
##
## h3. For people learning Perl: Alternative patterns in regular expressions
##
## Using | in a regular expression lets you search for (and in
## this case replace) two or more alternative patterns, in this case zero or
## more '/' characters at the beginning of $path_info and zero
## or more at the end. The 'g' option replaces all patterns found, so we
## replace both '/' characters found at the beginning and any found at the
## end.
##
## For more information see the following URL:
##
## http://www.perldoc.com/perl5.8.4/pod/perlretut.html#Matching-this-or-that
##
$path_info =~ s!(^/*)|(/*$)!!g;
# Date fiddling
##
## h3. Date references
##
## At this point we've extracted from @path_info any category
## names (at the beginning of the path) and any final path component
## associated with either an individual entry or an index.{flav}
## reference. So the only components left in @path_info should be
## date references (if any) from URLs containing sequences like
## /2004/05/19, /2004/05, or /2004.
##
### h3. For people learning Perl: Assigning into a list
###
### This statement assigns $path_info[0] (i.e., the
### first element in the array @path_info) to
### $path_info_yr, $path_info[1] to
### $path_info_mo, and $path_info[2] to
### $path_info_da. If @path_info doesn't have
### three elements then some or all of the three variables may end up
### undefined (starting with $path_info_da).
###
### In general you can assign a list of scalar values into a list of scalar
### variables:
###
### ($a, $b, $c) = (1, 2, 3);
### ($a, $b, $c) = @d;
###
### where the righthand side could be a constructed list (using ','), an
### array, a function returning a list, or any other expression returning
### a list.
###
### For more information see the following URL:
###
### http://www.perldoc.com/perl5.8.4/pod/perldata.html#List-value-constructors
###
($path_info_yr,$path_info_mo,$path_info_da) = @path_info;
##
## h3. Month abbreviations in Blosxom URLs
##
## [Note: Although I don't believe the online documentation mentions this,
## based on this code it appears that you can use Blosxom URLs that identify
## months by their three-letter abbreviations instead of month numbers; so,
## for example, rather than identifying the date as /2004/01/31
## it appears that you could request it as /2004/Jan/31.
##
## If so, there's no danger in mistaking a month abbreviation for a
## category name since the month must be preceded by a four-digit year,
## and Blosxom stops parsing the URL for categories as soon as it hits
## a component starting with a digit.]
##
### h3. For people learning Perl: The lc, ucfirst,
### and undef functions
###
### This statement can be paraphrased as follows: if
### $path_info_mo has a (non-empty) value, then check to see if
### that value is a string with (at least) two digits (i.e., it matches the
### regular expression \d{2}); if so, assign the value
### of $path_info_mo to $path_info_num. If
### $path_info_mo has a value that doesn't contain
### two digits, then put the value in "initial cap" form and look it up in
### the %month2num hash to see if the value is a month
### abbreviation; if so, assign the month number from the hash to
### $path_info_num.
###
### If the value of $path_info_mo doesn't look like a month
### number or month abbreviation, or if it's empty or undefined, then
### $path_info_mo_num is undefined as well.
###
### The function lc returns the lower-case equivalent of its
### string argument, and the ucfirst function returns a copy of
### its argument with the first letter (only) capitalized. Hence
### ucfirst(lc 'jaN') returns the value 'Jan', which is
### the capitalization style used in %month2num.
###
### The function undef returns an undefined value that (as in this
### case) can be assigned to a variable.
###
### For more information see the following URLs:
###
### http://www.perldoc.com/perl5.8.4/pod/perlreref.html#CHARACTER-CLASSES
### http://www.perldoc.com/perl5.8.4/pod/perlreref.html#QUANTIFIERS
### http://www.perldoc.com/perl5.8.4/pod/func/lc.html
### http://www.perldoc.com/perl5.8.4/pod/func/ucfirst.html
### http://www.perldoc.com/perl5.8.4/pod/func/undef.html
###
$path_info_mo_num = $path_info_mo ? ( $path_info_mo =~ /\d{2}/ ? $path_info_mo : ($month2num{ucfirst(lc $path_info_mo)} || undef) ) : undef;
# Define standard template subroutine, plugin-overridable at Plugins: Template
##
## h3. The template subroutine
##
## The template subroutine is used to look for and return the contents of
## flavour template files (e.g., head.html,
## foot.html, etc.). It can be overridden by a plugin that defines
## its own template subroutine; see the notes for line 161.
###
### h3. For people learning Perl: Anonymous subroutines and references
###
### sub { ... } defines an "anonymous" (i.e., not named)
### subroutine, a reference to which is then assigned to the variable
### $template. (References are basically names that can be used
### to refer to variables and subroutines, and are the third type of value
### that a scalar variable can have, along with numbers and strings.) The
### subroutine can then be called using the syntax &$template()
### where you can put subroutine arguments inside the parentheses.
###
### The template subroutine is defined in this way (using a reference stored
### in a variable rather than a named subroutine) so that the
### subroutine can be overridden; a plugin can define its own template
### subroutine, and a reference to that can be assigned to
### $template, replacing the reference to the original
### subroutine defined here.
###
### For more information see the following URLs:
###
### http://www.perldoc.com/perl5.8.4/pod/perlsub.html
### http://www.perldoc.com/perl5.8.4/pod/perlref.html
###
$template =
sub {
## h3. Template subroutine arguments
##
## $flavour is the flavour for which we are looking, e.g., 'html',
## 'rss', etc. $chunk is the type of template we are looking for,
## e.g., 'head', 'foot', 'story', etc. $path is the directory
## at which we should start our search, expressed as a relative pathname
## relative to the Blosxom data directory.
##
###
### h3. For people learning Perl: Argument passing using @_
###
### Arguments to the subroutine are passed in a special array variable
### @_, with the first three elements of that assigned to the
### private variables $path, $chunk, and
### $flavour respectively.
###
my ($path, $chunk, $flavour) = @_;
## h3. For people learning Perl: The do while loop
##
## A do while loop is like a
## while loop except that the condition is checked at the
## bottom (after the loop is executed at least once) instead of at the top.
##
## (The similarity between while and do while loops
## is only superficial, since in Perl the do {...} while construct
## isn't considered to be a true loop. In particular, you can't put
## next and last statements within a do {...}
## while; see the notes for lines 141 and 240.)
##
## For more information see the following URL:
##
## http://www.perldoc.com/perl5.8.4/pod/func/do.html
##
do {
##
## h3. For people learning Perl: The join and open
## functions
##
## The following statement is basically a backwards if statement: First
## we use the FileHandle $fh (created above) and try to open
## a template file for read access ("<"), constructing a template pathname
## from the values of $datadir, $path,
## $chunk, and $flavour. So, for example, if
## $datadir is '/blosxom', $path is '/a/b',
## $chunk is 'head', and $flavour is 'html', we look for a
## flavour template file '/blosxom/a/b/head.html'.
##
## (Because the FileHandle module provides an object-oriented interface,
## we use the method invocation $fh->open(...) rather than
## the function call open($fh, ...). Also note that if we have
## already opened a file using the FileHandle $fh that file
## will be closed first before we open a new one.)
##
## If the open succeeds (i.e., the template file exists and is readable) then
## we read in all the lines of the template file using the $fh
## FileHandle and return a string containing all those lines concatenated
## together.
##
## (<$fh> would normally read only one line of the file,
## but using the join function causes <$fh>
## to be used in a list context -- because join expects a list
## as its second argument -- and that causes <$fh> to read
## all lines and return them as an array, with each array element being
## a newline-terminated line. The join function then returns a
## string consisting of all the array elements concatenated together
## separated by the join function's first argument, which in
## this case happens to be the empty string. So the returned result is a
## single string containing all the lines in the flavour template file, each
## terminated by a newline, e.g.,
##
## \n\nA Blog
\n...
##
## for a typical head section.)
##
## For more information see the following URLs:
##
## http://search.cpan.org/~nwclark/perl-5.8.4/lib/FileHandle.pm
## http://www.perldoc.com/perl5.8.4/pod/perlopentut.html
## http://www.perldoc.com/perl5.8.4/pod/func/open.html
## http://www.perldoc.com/perl5.8.4/pod/func/join.html
## http://www.perldoc.com/perl5.8.4/pod/func/return.html
##
return join '', <$fh> if $fh->open("< $datadir/$path/$chunk.$flavour");
##
## h3. For people learning Perl: Retrying the template file search
##
## If the open fails (e.g., there was no file at the location we looked)
## then we modify the value of $path by stripping off the last
## path component (e.g., if $path has the value /a/b
## we change it to /a) and then we go back to the top of the
## loop and try the open again. (In other words, we search for the template
## file in the parent directory of the directory we just looked in.)
##
## (To explain the regular expression a bit: '\/' matches a literal '/'
## and '[^\/]' matches anything but a slash, so '\/*[^\/]*' matches zero
## or more '/' characters followed by zero or more other characters. The
## regular expression \/*[^\/]*$ means look for this pattern
## at the end of the string, so that when the substitution is done -- replacing
## the matched pattern by an empty string -- it removes the last component of
## $path. Finally, we use parentheses to save the matched
## pattern in the $1 variable for later checking, hence
## (\/*[^\/]*)$ is the final regular expression used.)
##
## If we never succeed in opening a template file then the loop ends when
## all the path components have been removed, the matched pattern is an
## empty string so that $1 is empty and hence false, and the
## and test fails.
##
} while ($path =~ s/(\/*[^\/]*)$// and $1);
## h3. For people learning Perl: Values in nested hashes
##
## If we never succeed in opening a template file (i.e., we drop out of the
## do while loop) then we return a string consisting of lines
## from a flavour template already stored in a multidimensional hash, using
## $flavour and $chunk as keys. (This hash is
## defined below; recall that right now we are defining the subroutine, not
## executing it. See the notes for line 144 for more information.)
##
return join '', ($template{$flavour}{$chunk} || $template{error}{$chunk} || '');
};
# Bring in the templates
###
### h3. For people learning Perl: Initializing a hash to be empty
###
### We set the %template hash variable to contain nothing,
### i.e., no keys and no values.
###
%template = ();
##
## h3. Default templates
##
## Read in and store the default templates defined in the data section of
## this file, saving them in %template.
##
### h3. For people learning Perl: <DATA>
###
### <DATA> causes lines to be read from the data section
### of this file (i.e., blosxom.cgi). The data section starts
### after a line consisting of __DATA__ by itself. In this
### context <DATA> returns a line at a time, returning an
### undefined value (and thus ending the while loop) when we
### reach the end of the file.
###
### For more information see the following URL:
###
### http://www.perldoc.com/perl5.8.4/pod/perldata.html
###
while () {
##
## h3. For people learning Perl: __END__ and last
##
## Using <DATA> would continue to read lines after
## __DATA__ until the end of the blosxom.cgi file.
## However in our case we may want to put some additional text after the
## __END__ line (which marks the end of what the Perl compiler
## parses). We therefore explicitly check for the presence of
## __END__ on a line by itself, and if we find it we use the
## last command to exit the while loop immediately.
##
## Note that since we are not using the =~ operator the string
## pattern match is done against the special variable $_ that
## holds the line just read from the data section using
## <DATA>.
##
## For more information see the following URL:
##
## http://www.perldoc.com/perl5.8.4/pod/perlsyn.html#Loop-Control
##
## Note that there are a couple of subtle points about the test for
## __END__. First, the test is actually for either zero or one
## occurrence of __END__, so the test would succeed (and
## reading of data end) if the __DATA__ section contained a
## blank line at some point. Second, the pattern match requested is for
## __END__ starting from the beginning of the line (^) and
## ending at the end of the line ($), with nothing else present. But the
## string being tested against (the value of the $_) variable
## does in fact have something else in it, namely a newline at the end of
## the string.
##
## Why then does the test work? Because as noted in the Perl online
## documentation, "the '^' character is guaranteed to match only the
## beginning of the string, the '$' character only the end (or before the
## newline at the end), ..." (emphasis added). In other words, the newline
## at the end of $_ is ignored for the purpose of matching the
## specified pattern /^(__END__)?$/.
##
## For more information see the following URL:
##
## http://www.perldoc.com/perl5.8.4/pod/perlre.html#Regular-Expressions
##
last if /^(__END__)?$/;
##
## h3. For people learning Perl: Parsing template lines and pattern matching
## in a list context
##
## As can be seen by looking at the data sections below, the default
## templates are each defined as a single line containing the flavour,
## the type of template, and the template data itself, each field
## separated by whitespace. We therefore parse each line of the data
## section into three whitespace-separated fields, and then assign the
## values to the private variables $ct, $comp,
## and $txt respectively.
##
## In the code thus far we have seen pattern matching done in a scalar
## context; in that context a pattern match will return the number of
## matches found, or zero if no match exists. However here the pattern match
## is being done in a list context because of the assignment to ($ct,
## $comp, $txt). (Recall that this is comparable to an assignment of
## the form @a = ... where @a is an array variable.)
##
## When done in a list context a pattern match will return an array
## ($1, $2, ...) containing the parts of the string that were
## matched. Hence in this context $ct will be assigned the
## value of $1, $comp will be assigned the value
## of $2, and $txt will be assigned the value of
## $3.
##
## To expand a bit on the regular expression: \s matches a
## whitespace character (space, tab, etc.) and \S matches a
## non-whitespace character. The first field gets matched by
## ^(\S+), the second field gets matched by (\S+),
## and the third field (which can contain spaces) gets matched by
## (.*)$; the field patterns are then separated by the
## \s pattern.
##
## [Note: The regular expression looks for a
## single whitespace character between the fields. On each line in the data
## section there is in fact only a single space between the flavour
## specifier and the template type specifier, on each line, so this works
## out OK. However on some lines there is more than one space between the
## template type specifier and the template content. This does not cause
## any problem in practice, since the pattern for the third field can
## match spaces; the extra spaces are simply included as leading whitespace
## in the value matched for the third field and then assigned to
## $txt.]
##
my($ct, $comp, $txt) = /^(\S+)\s(\S+)\s(.*)$/;
##
## h3. For people learning Perl: Multiline mode in regular expressions
##
## We modify $txt to change literal occurrences of '\n' (i.e.,
## the '\' character followed by the character 'n') to occurrences of the
## newline character.
##
## '\\' in the pattern being searched for matches for a literal
## '\', and '\n' in the replacement string is interpreted as a newline
## character. The g option does a global search and replace as
## noted above while the m option searches in multiline mode.
##
## Multiline mode treats the string as a multiline buffer, so you can use
## '^' and '$' to match at the beginning and end of newline terminated
## substrings within the string as a whole.
##
## [Note: It's not exactly clear why multiline mode is used in this
## context, particularly since the regular expression doesn't use either '^'
## or '$'; in testing the substitution seemed to work fine even without the
## m option.]
##
$txt =~ s/\\n/\n/mg;
##
## h3. For people learning Perl: Nested hashes
##
## We store the default flavour template text read from the data section,
## indexing it by the flavour and type of content.
##
## The usage $a{$b}{$c} is an example of the use of Perl
## references to simulate multi-dimensional arrays or nested hashes.
## To expand on this: the syntax $a{$b}{$c} is equivalent to
## $a{$b}->{$c}, which in turn is equivalent to
## ${$a{$b}}{$c}. Here %a is a hash, the value of
## $b is a key for that hash, and the hash value
## $a{$b} is a reference that points to another hash. (The
## second hash is anonymous, i.e., it has no name of its own.) To refer to a
## value in the second hash we use ${$a{$b}}{$c} where
## the value of $c is a key in the second hash. As noted above
## we can also use the syntax $a{$b}->{$c} instead, and can in
## turn shorten that to $a{$b}{$c}.
##
## When we make an assignment like $a{$b}{$c} = "def" Perl
## automagically creates the anonymous hash and stores a reference to it in
## $a{$b}. If Perl didn't do this then you'd have to go through
## the following machinations to make the same assignment (assuming that the
## hash %a already existed):
##
## %h = (); # Create an empty hash %h
## $h{$c} = "def"; # Store value "def" in %h at key $c
## $a{$b} = \%h; # Store reference to %h in hash %a at key $b
##
## In this example the value could then be referenced as either
## $h{$c} or ${$a{$b}}{$c}. Per the online Perl
## documentation, "Anywhere you'd put an identifier ... as part of a variable
## ... name, you can replace the identifier with a simple scalar variable
## containing a reference of the correct type". So we are replacing the
## identifier "h" in $h{$c} with the scalar variable
## $a{$b} that contains a hash reference. We could
## actually use the syntax $$a{$b}{$c} for this but we use the
## extra pair of curly braces to clarify what's going on.
## ${$a{$b}}{$c} then becomes $a{$b}{$c} through
## the alternative syntax discussed above.
##
## For more information see the following URLs:
##
## http://www.perldoc.com/perl5.8.4/pod/perlreftut.html
## http://www.perldoc.com/perl5.8.4/pod/perlref.html#Using-References
##
$template{$ct}{$comp} = $txt;
}
# Plugins: Start
##
## h3. For people learning Perl: The opendir function
##
## If there's a plugin directory defined we open it and look for plugins,
## using the file handle PLUGINS; we use the opendir
## function instead of open because we are opening a directory,
## not a regular file.
##
## For more information see the following URL:
##
## http://www.perldoc.com/perl5.8.4/pod/func/opendir.html
##
if ( $plugin_dir and opendir PLUGINS, $plugin_dir ) {
##
## h3. For people learning Perl: The readdir, grep,
## and -f functions and the foreach loop
##
## Working backwards from the end of the statement: We use the
## readdir function to return a list of all the entries
## in the plugin directory, and then use the sort function to
## sort those entries in the default (alphabetical) order.
## (readdir returns all directory entries because
## it's being executed in a list context, since sort expects a
## list argument; otherwise readdir would return one directory
## entry at a time.)
##
## We then use the grep function to test each of the sorted
## directory entries against the specified expression (in curly braces) and
## return a list consisting of only those entries for which the expression
## is true. In this case the expression for grep is a compound
## expression consisting of a regular expression and a file test function
## anded together.
##
## We first test using /^\w+$/ to make sure that the directory
## entry starts with and contains only alphanumeric characters or '_'; this
## eliminates directory entries for . (the current directory),
## .. (the parent directory), and hidden files (e.g.,
## .a). (Note that we don't use the =~ operator here
## because we are matching against the special variable $_ that
## grep sets in turn to hold the value of each element of the
## list passed to it.)
##
## We then test using the file test function -f "$plugin_dir/$_"
## to verify that the directory entry actually is a file and not something
## else; this eliminates directory entries for the plugin state directory and
## other subdirectories that might be present, as well as directory entries
## for special files like device files, named pipes, and the like. (Again we
## reference the special $_ variable set by grep.)
##
## [Note: Symbolic links do pass the -f test (at least on
## Unix and Unix-like systems) if (and only if) they point to regular files.
## Unless other considerations apply, this should allow you to put a plugin
## file in another directory and put a symlink in the plugin directory
## itself.]
##
## Finally, we use a foreach loop to iterate over each element in
## the list of plugins, assigning the value of each element to the variable
## $plugin in turn and executing the statements in the following
## code block.
##
## For more information see the following URLs:
##
## http://www.perldoc.com/perl5.8.4/pod/func/readdir.html
## http://www.perldoc.com/perl5.8.4/pod/func/grep.html
## http://www.perldoc.com/perl5.8.4/pod/func/-X.html
## http://www.perldoc.com/perl5.8.4/pod/perlsyn.html#Foreach-Loops
## http://www.perldoc.com/perl5.8.4/pod/perlvar.html
##
foreach my $plugin ( grep { /^\w+$/ && -f "$plugin_dir/$_" } sort readdir(PLUGINS) ) {
##
## h3. For people learning Perl: Parsing plugin names
##
## Recall that plugins can have a (normally two-digit) number at the beginning
## of their names (to enforce a particular plugin order) and can also have an
## underscore character ('_') at the end of their names to disable them from
## being used.
##
## Here we use a regular expression to match and save the actual plugin name
## and look for a concluding '_' if present. (We no longer need the numeric
## prefix since we are now processing the plugins in the proper sort order.)
## Note that the regular expression as written allows underscores to be used
## as part of the plugin name itself; only an underscore at the end is
## special.
##
## The plugin name and the (optional) trailing underscore are saved in the
## special variables $1 and $2 and then assigned to
## the private variables $plugin_name and $off
## respectively. (See the note to line 142 for more information on pattern
## matching in a list context.)
##
my($plugin_name, $off) = $plugin =~ /^\d*(\w+?)(_?)$/;
##
## h3. For people learning Perl: Determining if a plugin is disabled
##
## If the final underscore is present ($off has the value '_')
## we set $on_off to -1 to indicate that the plugin is disabled;
## otherwise $on_off is set to 1 to indicate an active plugin.
##
my $on_off = $off eq '_' ? -1 : 1;
##
## h3. For people learning Perl: The require function
##
## We include the code for the current plugin. (This is somewhat analogous
## to #include in C.) Note that since we are supplying a
## pathname the require function will look for the plugin at
## the pathname (instead of looking in the directories specified by
## @INC, the Perl search path analogous to
## LD_LIBRARY_PATH and similar environment variables in Unix.)
##
## [Note: The Perl online documentation for require mentions
## only searching in @INC directories for a filename, and does
## not explicitly address using a full pathname. This is presumably just an
## oversight.]
##
## For more information see the following URL:
##
## http://www.perldoc.com/perl5.8.4/pod/func/require.html
##
require "$plugin_dir/$plugin";
##
## h3. For people learning Perl: Calling a plugin's start routine
##
## Now that the code for this plugin has been loaded we can call subroutines
## defined in the plugin. We first call the plugin's start routine, using
## the plugin's name in a method invocation (see below). Assuming that the
## start routine exists and returns a true value, we then use the plugin's
## name as a key to put the plugin's $on_off value into the
## %plugins hash. Finally, we create a new element in the
## @plugins array and set its value to the plugin name. (Recall
## that %plugins and @plugins are entirely
## different variables that just happen to share the same name.)
##
## Note that we set $on_off to the value -1 for off instead of
## 0 because otherwise the middle expression (between the two
## and's) would have evaluated false, and we would never have
## executed the third expression to set @plugins.
##
## h4. Start routine invocation
##
## For those wanting a more in-depth explanation, calling the start routine
## works as follows:
##
## A plugin "abc" has to define a package abc, as noted in the
## Blosxom plugin developer documentation. So as a result of the "abc" plugin
## being loaded (by require) we can now refer to subroutines
## and variables defined by the package. (Strictly speaking we can't refer
## to everything defined by the package, but let's ignore that for now.) For
## example, if a scalar variable $foo is defined by plugin "abc"
## (i.e., package abc) then we could refer to it as
## $abc::foo to obtain its value. Similarly we could call the
## start subroutine in package abc using the notation
## abc::start().
##
## However we have a problem: the Blosxom code doesn't know beforehand that
## there's going to be a plugin "abc" (or "foo", or whatever), so the
## Blosxom code can't use abc::start() to invoke package
## abc's start subroutine. The solution is to use a different
## way to call a routine defined in a plugin: Blosxom invokes
## abc::start as a method rather than calling it as a subroutine.
##
## Methods are a concept from object-oriented (OO) programming, in which
## (in theory) everything of interest is an "object", objects can belong to
## "classes", classes can have "methods" that operate on objects of that
## class, classes can be "subclasses" of higher-level classes, and so on.
##
## For Blosxom (at least Blosxom 2.0) we don't need to worry about the
## full OO story, we simply need to know that in Perl terms an object is
## just a reference, a class is simply a package and a method is a
## subroutine defined by a package. So in our example rather than using
## abc::start() to call the start subroutine in package
## abc, we can use the method invocation notation
## abc->start() instead. (Method invocation
## doesn't work exactly like subroutine calling, particularly in terms
## of which arguments are passed, but we can ignore that for now.)
##
## However we still have the problem of Blosxom not knowing about
## package abc beforehand, so using abc->start()
## won't work either. Fortunately in method invocation instead of a package
## identifier to the left of the -> we can substitute a scalar
## variable whose value is a string representing a valid package name. In
## particular, rather than using abc->start() to invoke the
## start subroutine (using the package identifier abc), we can
## set a scalar variable $foo to the value "abc", and then use
## $foo->start() to invoke the subroutine. (We're using
## $foo as an example; Blosxom actually uses the variable
## $plugin_name previously assigned.)
##
## For more information see the following URLs:
##
## http://www.blosxom.com/documentation/developers/plugins.html
## http://www.perldoc.com/perl5.8.4/pod/perlobj.html#Method-Invocation
##
## h4. Method invocation vs. symbolic references
##
## [Note: (This is for people like me who get led astray reading Perl
## documentation.) The usage $foo->start() looks
## similar to the use of -> with Perl references as previously
## discussed, and it's tempting to think of $foo in this context
## as a kind of reference, in particular a symbolic reference, a Perl concept
## where a scalar variable containing the name of a variable or subroutine
## can get interpreted as a (real) reference to that (second) variable or
## subroutine.
##
## However as far I can tell there is no connection between symbolic
## references and use of a scalar variable to specify the package (class)
## name in method invocation. This is supported by the fact that Blosxom
## does a use strict, which flags use of symbolic references as
## an error; however this doesn't affect the use of scalar variables in
## method invocation.]
##
$plugin_name->start() and ( $plugins{$plugin_name} = $on_off ) and push @plugins, $plugin_name;
}
##
## h3. For people learning Perl: The closedir function
##
## Having cycled through all the plugins, we now close the
## PLUGINS file handle we used to open the plugins directory.
##
## For more information see the following URL:
##
## http://www.perldoc.com/perl5.8.4/pod/func/closedir.html
##
closedir PLUGINS;
}
# Plugins: Template
# Allow for the first encountered plugin::template subroutine to override the
# default built-in template subroutine
##
## h3. Overriding the default template subroutine
##
## We loop through the @plugins array, which now contains a list
## of plugin names for both active and disable plugins. For each plugin we
## look up its name in the %plugin hash and determine whether the
## plugin is enabled (1) or disabled (-1). If a plugin is enabled we then use
## the plugin name to invoke the can method to see if a template
## subroutine is defined by the plugin's package.
##
## If so then we invoke the plugin's template subroutine, which returns a
## reference to a new (anonymous) subroutine to handle templates; we save the
## reference to that subroutine in the $template variable
## (overriding the value set earlier, representing the default template
## subroutine), and then we exit the loop (and don't bother to look at the
## other plugins).
##
### h3. For people learning Perl: The can method
###
### Earlier we saw a method invocation used to call a plugin's start
### subroutine, using the expression $plugin_name->start()
### where the value of the scalar variable $plugin_name was a
### string with the plugin's name (which is the same name as its package).
### The expression $plugin->can('start') looks similar, except
### for the addition of an argument to be passed to the method.
###
### However plugins don't actually define a can method; where
### then does it come from? Here we see more of the object-oriented features
### of Perl: When doing method invocation (but not when doing a standard
### subroutine call) Perl will look for a method not only in that
### package/class (recall that they are the same in Perl), but also in
### higher-level classes from which the class in question inherits methods.
###
### In particular, Perl has a package UNIVERSAL from which all
### packages inherit the can method. The expression
### abc->can('foo') will invoke the can method and
### check to see if the package abc has the foo
### method defined; if so, it returns a reference to the method, or an
### undefined value if no such method exists. The Blosxom code uses a similar
### expression but using a scalar variable holding the package name instead
### of the package identifier (which it can't know a priori).
###
### For more information see the following URL:
###
### http://www.perldoc.com/perl5.8.4/pod/perlobj.html#Default-UNIVERSAL-methods
###
#### h3. For people learning Perl: Inside the can method
####
#### By knowing a little bit about how Perl represents subroutines internally
#### we can get a general idea of how the can method works. Internally Perl
#### identifiers for variables, subroutines, etc., are stored in a special
#### hash known as a "symbol table". Every package has its own symbol table,
#### among other things to support the Perl feature that different packages
#### can have different variables that happen to have the same names. A
#### package's symbol table has entries for variables and subroutines defined
#### in that package (except for lexically-scoped items, which we ignore here).
####
#### So if package abc (corresponding to the "abc" plugin) has
#### defined a template subroutine/method, then in the symbol table for
#### package abc (which can be accessed from Perl as the hash
#### variable %abc::) there will be a hash element with key
#### 'template' that will have as its value a special data object called a
#### "typeglob" (the typeglob value is accessible from Perl as
#### $abc::{'template'} or *abc::template); that
#### typeglob in turn can be used to find a reference to the template
#### subroutine (accessible as $abc::template{CODE} using a
#### hash-like notation).
####
#### Given a package and a string with the name of the desired method, the
#### can method looks in the package's symbol table to find an
#### entry for that name, and then looks at the typeglob to see if there's
#### actually a subroutine defined with that name. (After all, the package
#### might have a scalar variable, hash, or array with the same name as the
#### subroutine.) The can method then returns the subroutine
#### reference obtained from the typeglob, or an undefined value if no such
#### reference was found.
####
#### One question remains: How does the can method know the
#### package for which it's searching for a method? Because when the
#### can method is invoked Perl passes it an extra argument
#### containing the name of the package/class on which the can
#### method was originally invoked (the abc package in our
#### example).
####
#### Such an extra argument is passed as the first argument to any subroutine
#### invoked as a method (although in some types of method invocation the
#### first argument is a reference and not a class/package name). The presence
#### of this additional argument is another way in which method invocation is
#### different than a subroutine call.
####
#### If you happen to read code for plugins, this is why some subroutines
#### have an argument $pkg (or whatever) that's not shown in the
#### Blosxom code invoking that subroutine. The $pkg argument is
#### present only for plugin subroutines that take arguments in the first
#### place, since in that case the subroutine has to skip over the
#### $pkg argument before getting to the "real" arguments. Plugin
#### subroutines that don't take arguments (like the start and template
#### subroutines) don't worry about this; they just ignore any arguments
#### passed, including the package name argument.
####
#### For more information see the following URLs:
####
#### http://www.perldoc.com/perl5.8.4/pod/perlmod.html#Symbol-Tables
#### http://www.perldoc.com/perl5.8.4/pod/perlref.html
####
my $tmp; foreach my $plugin ( @plugins ) { $plugins{$plugin} > 0 and $plugin->can('template') and defined($tmp = $plugin->template()) and $template = $tmp and last; }
# Provide backward compatibility for Blosxom < 2.0rc1 plug-ins
##
## h3. The load_template subroutine
##
## [Note: Since I've never seen code for Blosxom versions earlier than 2.0
## I'm just going to ignore this code and not worry about it. It doesn't seem
## relevant for current 2.0-based plugins.]
##
sub load_template {
return &$template(@_);
}
# Define default find subroutine
##
## h3. Default entries subroutine
##
## We define a default subroutine to find entries, just as we previously
## defined a default subroutine to handle flavour templates. We define this
## as an anonymous subroutine and then store a reference to that subroutine
## in the variable $entries. A plugin can then have a
## subroutine entries (not to be confused with
## $entries) that defines a new anonymous subroutine
## and returns its reference as a replacement for the reference in
## $entries.
##
$entries =
sub {
##
## h3. The return values from the entries subroutine
##
## The entries subroutine returns a list containing three things: a hash
## of all the files representing individual entries (%files),
## (for static rendering only) a hash of all directories needing index pages
## generated and individual entry files needing static pages generated
## (%indexes), and a hash of all other files found
## (%others).
##
## Note that the private lexical variables %files,
## %indexes, and %others declared here are entirely
## distinct from the global variables of the same names declared on line 69.
## In general using my to declare a private variable within a
## given lexical scope will "hide" any global variables of the same name, as
## well as private variables of the same name declared at a higher-level
## lexical scope. (See the notes to lines 333 and 357 for another example of
## such hiding.)
##
my(%files, %indexes, %others);
##
## h3. For people learning Perl: The find subroutine
##
## The default entries subroutine uses the find subroutine from
## File:Find to do all the work. The find subroutine is
## analogous to the Unix find command and takes two arguments,
## a list of directories in which to search (here just $datadir,
## the Blosxom data directory) and a reference to a subroutine that will be
## called by find for each directory entry (e.g., file,
## subdirectory, symlink, etc.) found in the search. (Here we define that
## subroutine as an anonymous subroutine, which automatically produces the
## reference to be passed in.)
##
## To help clarify how find is used, if we wanted to mimic the
## operation of the simple Unix command
##
## find /blosxom/data -name 'index.*' -print
##
## (find all items whose filenames start with "index.", and print their
## pathnames) we could call find as follows:
##
## find( sub { /^index\..*\z/s && print "$name\n"; }, '/blosxom/data');
##
## Here $name (also known as $File::Find::name) is
## a variable that find sets to the current pathname being
## processed.
##
## For more information see the following URL:
##
## http://search.cpan.org/~nwclark/perl-5.8.4/lib/File/Find.pm
##
find(
sub {
my $d;
##
## h3. For people learning Perl: $File::Find::dir
##
## The value of $File::Find:dir is the absolute path of the
## directory currently being searched. We count '/' characters in the path
## using tr to obtain the number of directory components in
## the path.
##
my $curr_depth = $File::Find::dir =~ tr[/][];
##
## h3. Interpretation of $depth
##
## We don't process the entries in a directory if it exceeds a specified
## search depth limit. Recall that if $depth was originally set
## to a non-zero value (i.e., to limit the depth of search) then that value
## was adjusted to account for the number of components in the path to the
## Blosxom data directory. See the notes for line 94 for more information.
##
return if $depth and $curr_depth > $depth;
## h3. Recognizing a Blosxom entry file
##
## As noted above, $File::Find::name contains the absolute
## pathname of the item we are currently processing. We check to see if the
## current item appears to be a Blosxom entry: Its filename has the proper
## extension (.txt by default), it's not an index file or
## hidden file, and it's readable as a file (e.g., as opposed to being a
## directory with a name that looks like a blosxom entry). If so, we do
## further processing on the item as described below to build the
## %files list. Otherwise we consider adding the item to the
## %others list, as described in the notes for line 208.
##
if (
# a match
##
## h3. For people learning Perl: Regexp matching for entries
##
## The regular expression matching here has some subtleties worth
## exploring. Items that are entries are going to look like, e.g.,
## /blosxom/data/foo.txt (for an entry in the Blosxom data
## directory itself) or /blosxom/data/a/b/bar.txt (for an entry
## in a subdirectory somewhere below the Blosxom data directory). For reasons
## that will become more clear below, we want to save the basename of the
## item's filename (e.g., foo or bar respectively
## in our example) as well as the sequence of subdirectories between the data
## directory and the filename (e.g., '' and a/b respectively in
## our example).
##
## With that in mind let's look more closely at the pattern match. First,
## we use m!...! to delimit the pattern to be matched, as
## opposed to the usual /.../, because the '/' character is part
## of the pattern itself and we don't want to have to escape it (i.e., as
## "\/"). We then match the beginning of the path against the data directory
## with ^$datadir/; this would match /blosxom/data/
## in our example above..
##
## To match the subdirectory components we use the pattern
## (?:(.*)/)?. The pattern .*/ by itself would
## match subdirectories up to the final '/' (e.g., in the example item
## ...a/b/bar.txt above), and in order to save the subdirectory
## components (minus the trailing '/') we could use the pattern
## (.*)/. However we also have to account for the possibility
## that the entry might be in the data directory itself, in which case there
## wouldn't be any subdirectory names and no second '/' character; we could
## handle this case using the pattern ((.*)/)? (i.e., match
## either one or zero occurrences of (.*)/).
##
## However now we're capturing the subdirectory part of the path twice:
## one without trailing '/' (e.g., a/b) and once with it (e.g.,
## a/b/); to avoid this redundancy we instead use the pattern
## (?:(.*)/)?. (?:...) is like (...)
## except that it doesn't capture the matched string; as a result
## (?:(.*)/)? captures only the string matched by
## (.*), and puts it into $1.
##
## To match the item's filename we would use a pattern like
## (.+)\.txt$ if we knew the extension would always be
## .txt: we look for one or characters, then a literal '.',
## then the extension at the end of the string, and we capture the basename
## (i.e., the characters before the '.') for later use. In the case of
## Blosxom the value of the extension we're looking for is in a variable,
## so we use the pattern (.+)\.$file_extension$ instead,
## where the value of $file_extension gets interpolated into
## the pattern as it would into a double-quoted string.
##
## For more information see the following URL:
##
## http://www.perldoc.com/perl5.8.4/pod/perlre.html#Extended-Patterns
##
$File::Find::name =~ m!^$datadir/(?:(.*)/)?(.+)\.$file_extension$!
# not an index, .file, and is readable
##
## h3. Skipping faux entries
##
## We skip over faux entries like index.txt,
## .foo.txt, and foo.txt where foo.txt
## can't be read as a file (e.g., it's a directory instead).
## Note that the last test also implies that Blosxom will silently ignore
## entry files if the web server userid (e.g., "http") does not have
## permission to read them (but does have permission to search the directory
## in which they're located).
##
## [Note: I need more information on the treatment of symlinks by
## Blosxom. A symlink can pass the -r test if it points to a
## readable file. Are there any other considerations that come into
## play here?]
##
## For more information see the following URL:
##
## http://www.perldoc.com/perl5.8.4/pod/func/-X.html
##
and $2 ne 'index' and $2 !~ /^\./ and (-r $File::Find::name)
) {
##
## h3. For people learning Perl: Chaining and operators
##
## If the current item passes our initial test to see if it might be
## an entry, we do a series of additional tests and operations, in the
## form of a series of expressions a and b and c and ... f
## where each expression is evaluated and we stop if any expression evaluates
## to a false value. Note that in this case some of the expressions anded
## together are parenthesized expressions of the form (x or y or
## ... z).
# to show or not to show future entries
##
## h3. For people learning Perl: File modification times
##
## If we're showing future entries (i.e., $show_future_entries
## is true) then we proceed to the next test, otherwise
## the "last modified" time of the current item must be less than the
## current time. Note that stat(...)->mtime is a method call
## where the left hand side of the -> operator is an object
## reference (as opposed to a class/package name), in this case a
## File::stat object returned by stat(...). Both
## mtime and time are expressed in seconds since
## some fixed date ("the epoch") and hence are directly comparable.
##
## For more information see the following URLs:
##
## http://search.cpan.org/~nwclark/perl-5.8.4/lib/File/stat.pm
## http://www.perldoc.com/perl5.8.4/pod/func/stat.html
## http://www.perldoc.com/perl5.8.4/pod/func/time.html
(
$show_future_entries
or stat($File::Find::name)->mtime < time
)
# add the file and its associated mtime to the list of files
##
## h3. The %files hash
##
## The %files hash has the entry's absolute pathname as a key
## and its modification time as a value. If we didn't have to worry about
## modification times we could just use an array of entry pathnames but we
## need to keep a record of the entry modification times, in particular to
## do sorting of entries and to display the dates for entries.
##
and $files{$File::Find::name} = stat($File::Find::name)->mtime
# static rendering bits
##
## h3. Regenerating index files when static rendering
##
## Here we figure out which index.* files we will need to generate
## (or regenerate) when we're doing static rendering, passed on the presence
## of new and/or updated entries.
##
## [Note: It appears that the %indexes hash will be populated
## even if we are doing dynamic rendering, although it's not clear that
## %indexes will be used in that case.]
##
## In general we will have two types of index files that need to be
## generated: index files for directories corresponding to categories (e.g.,
## a/b for an entry foo.txt in that directory) and
## index files corresponding to dates (e.g., 2004,
## 2004/05, and 2004/05/22 for an entry
## foo.txt last modified on May 22, 2004). (Note that the main
## Blosxom data directory is a special case of a category directory.)
##
## As we determine which index files need to be (re)generated we build up
## a list (in %indexes) of the directories in which they need
## to be created. We also use %indexes to build up a list of
## individual entries for which static pages need to be generated.
##
and (
##
## h3. The -all parameter for static rendering
##
## If the -all parameter was passed in with value 1 (i.e.,
## -all=1 on the command line) then we (re)generate all
## index.* files.
##
## For more information see the following URL:
##
## http://www.blosxom.com/documentation/users/configure/static.html
##
param('-all')
##
## h3. Creating new index files
##
## If there is no index file of the default flavour (e.g.,
## index.html) for the directory in which the entry is located
## ($static_dir/$1) then we generate a new one. Recall that
## @static_flavours is the list of flavours to be generated
## statically; $static_flavours[0] is 'html' by default.
##
## (Note that we actually end up generating index files for all the flavours
## in @static_flavours, not just the first flavour. It's just
## more convenient to check for only one flavour, assuming that if its index
## file needs to be generated then the index files for the other static
## flavours do too.)
##
or !-f "$static_dir/$1/index." . $static_flavours[0]
##
## h3. Updating old index files for new or updated entries
##
## If the default index file (e.g., index.html) is older than
## the entry being processed then we update the index.* files
## in the entry's directory.
##
## (Again, we're checking the index file for one flavour and extrapolating
## the results for the other static flavours.)
##
or stat("$static_dir/$1/index." . $static_flavours[0])->mtime < stat($File::Find::name)->mtime
)
##
## h3. The %indexes hash and category directories
##
## The %indexes hash uses the directory pathname relative to the
## Blosxom data directory (e.g., a/b) as a key. This relative
## pathname can also be thought of as a relative URL, with the base URL
## being the URL that resolves to the Blosxom script.
##
## For an %indexes element corresponding to a category directory
## (e.g., a/b) we set the value of the element (e.g.,
## $indexes{'a/b'}) to 1. (See the note for line 203
## for the value of %indexes entries for date directories,
## e.g., 2004/05/22.)
##
and $indexes{$1} = 1
##
## h3. For people learning Perl: Date directories and array slices
##
## If an entry was created on a certain date (e.g., May 22, 2004) then we
## need to create index files in a subdirectory corresponding to that date
## (e.g., 2004/05/22/index.html) so that date-based Blosxom
## URLs will work properly.
##
## Note that the nice_date subroutine (defined below) takes a
## time in seconds since the epoch (here the entry's "last modified" time as
## stored in %files) and returns a list containing the various
## parts of the date/time broken out.
##
## Here we need only the year, month number, and day, so rather than using
## the entire list returned by nice_date we just use the
## elements we need, using Perl slice notation: @a[5,2,3] means
## a list consisting of $a[5], $a[2], and
## $a[3], where here @a is replaced by
## (nice_date(...)). (The parentheses around
## nice_date(...) are needed for proper Perl syntax.)
##
## We then take the slice, e.g., ("2004", "05", "22"), and
## join the elements with '/' to get the relative path we need, e.g.,
## 2004/05/22.
##
## For more information see the following URL:
##
## http://www.perldoc.com/perl5.8.4/pod/perldata.html#Slices
##
and $d = join('/', (nice_date($files{$File::Find::name}))[5,2,3])
##
## h3. The %indexes hash and date directories
##
## We add the date subdirectory (e.g., 2004/05/22) to the
## %indexes hash.
##
## For an %indexes element corresponding to a date directory
## we set the value of the element (e.g., $indexes{'2004/05/22'})
## to the relative pathname of the date directory itself (e.g.,
## 2004/05/22, same as the key). (Recall from the notes for line
## 200 that for %indexes elements corresponding to category
## directories we set the values of the elements to 1.)
##
and $indexes{$d} = $d
##
## h3. The %indexes hash and static entry pages
##
## If we are generating static entry pages then we also add the entry's
## relative pathname (e.g., a/b/foo.txt) to
## %indexes. (We can't just use $File::Find:name
## as the key here, as we did in %files, because that's an
## absolute pathname that includes the Blosxom data directory.)
##
## We use the conditional expression ($1 ? "$1/" : "") because
## we have to handle specially the case when the entry is in the Blosxom
## data directory itself and not in a subdirectory somewhere underneath it;
## in that case the subdirectory part of the entry's pathname (the middle
## part stored in $1) will be empty, and we don't want to add
## an extra '/' we don't need.
##
## For a %indexes element corresponding to an individual entry we
## set the value of the element (e.g., $indexes{'a/b/foo.txt'})
## to 1, the same as for %indexes elements for category
## directories.
##
and $static_entries and $indexes{ ($1 ? "$1/" : '') . "$2.$file_extension" } = 1
}
else {
##
## h3. The %others hash and non-entry files
##
## As noted above, we come to the else block when the item being
## processed does not appear to be a Blosxom entry file (e.g., it might be
## an existing file like foo.html).
##
## If the item is not a directory and it's readable then we add it to the
## %others hash, using its absolute pathname as the key and its
## "last modified" time as the value. (This is the same way the
## %files hash is structured.)
##
!-d $File::Find::name and -r $File::Find::name and $others{$File::Find::name} = stat($File::Find::name)->mtime
}
##
## h3. Completing the arguments to find
##
## We've finally come to the end of the first argument to find,
## the anonymous subroutine to process items, and we include
## $datadir as the second argument, the directory at which we
## wish to start searching for items.
##
}, $datadir
);
##
## h3. For people learning Perl: Returning reference values
##
## At the end of the default entries subroutine we return a list of
## references to the %files, %indexes, and
## %others hashes.
##
## Recall that %files, %indexes, and
## %others were defined as private variables of this anonymous
## subroutine using my. This makes them so-called
## "lexical" variables whose scope is limited to the subroutine, i.e., they
## would not normally be visible outside this subroutine. However by passing
## back references we make it possible for other parts of the Blosxom code
## to use the values of the %file, %indexes, and
## %others variables, and we
## ensure that the values of the variables stick around as long as we need
## to access them. As the Perl online documentation puts it, "So long as
## something else references a lexical, that lexical won't be freed... This
## means that you can pass back or save away references to lexical
## variables".
##
## For more information see the following URL:
##
## http://www.perldoc.com/perl5.8.4/pod/perlsub.html#Persistent-Private-Variables
##
return (\%files, \%indexes, \%others);
};
# Plugins: Entries
# Allow for the first encountered plugin::entries subroutine to override the
# default built-in entries subroutine
##
## h3. For people learning Perl: Overriding the default entries subroutine
##
## This is exactly the same approach we used earlier to allow plugins to
## override the default template subroutine. See the notes for line 161 for
## more information concerning how this code works.
##
my $tmp; foreach my $plugin ( @plugins ) { $plugins{$plugin} > 0 and $plugin->can('entries') and defined($tmp = $plugin->entries()) and $entries = $tmp and last; }
## h3. For people learning Perl: Calling the entries subroutine
##
## We invoke the entries subroutine to search for entries and build the
## %files, %indexes, and %others
## hashes. Because $entries is a reference to an
## anonymous subroutine (either the one we defined above or one defined by a
## plugin to override the default) we use & to dereference the
## reference and actually call the subroutine.
##
## Also recall that the entries subroutine returns a list of references to
## hashes, not the hashes themselves. That's why we assign the list of
## returned values into scalar variables, e.g., $files will now
## have as its value a reference to the %files hash created in
## the subroutine.
##
## For more information see the following URL:
##
## http://www.perldoc.com/perl5.8.4/pod/perlref.html#Using-References
##
my ($files, $indexes, $others) = &$entries();
##
## h3. For people learning Perl: Using references to create hash copies
##
## We dereference the returned references in $files,
## $indexes, and $others to set our own hash
## variables %files, %indexes, and
## %others.
##
## Note that despite having the same names these are separate and distinct
## variables from the %files, %indexes, and
## %others variables defined in the default entries subroutine:
## the %files, etc., variables in the entries subroutine are
## private lexical variables while the %files, etc., variables
## here are global variables for the Blosxom code (originally defined above
## at line 69 with use vars).
##
## Also note that the assignments %files = %$files, etc.,
## actually create copies of the original hashes constructed by the entries
## subroutine, just like an assignment %files = %h where
## %h is some existing hash.
##
## Finally, the code used to assign %others reflects the fact
## that (as shown in the example in the Blosxom plugin developer's
## documentation) a plugin might not actually build a %others
## list and return a reference to it; in that case $others
## would be undefined or empty. We therefore use the ref function
## to verify that $others is a proper reference before we attempt
## to deference it, otherwise we assign %others to be an empty
## hash.
##
## For more information see the following URLs:
##
## http://www.blosxom.com/documentation/developers/plugins.html
## http://www.perldoc.com/perl5.8.4/pod/func/ref.html
##
%files = %$files; %indexes = %$indexes; %others = ref $others ? %$others : ();
# Plugins: Filter
##
## h3. For people learning Perl: Calling plugin filter subroutines
##
## Having constructed the lists of entries and other files, we allow plugins
## to modify that list themselves by defining a filter subroutine. The code
## here is similar to the code used for the template and entry subroutines:
## we iterate over all plugins, check to see that the plugin is enabled and
## it defines a filter method, and if so then we invoke the method, passing
## references to the %files and %others hashes. The
## major difference here is that we do not terminate the loop early as soon
## as we find a plugin with a filter subroutine; instead we call the filter
## subroutine for each and every plugin that defines one.
##
## Note that although the code passes references to both %files
## and %others the Blosxom developer documentation mentions
## passing only the %files reference. (One more reason to read
## the actual code :-) Since the filter subroutine is passed references to
## the hashes it can modify them directly by deleting, modifying, or even
## adding hash elements.
##
## Finally, note that although the return value from the filter subroutine
## is assigned into the $entries variable used earlier to hold
## a reference to the entries subroutine, the return value is simply a 0 or
## 1 return code indicating whether the filter subroutine for a given plugin
## succeeded or failed. Since we already called the entries subroutine and
## won't do so again, we no longer need the original value of
## $entries; from this point on in the code
## $entries is used simply to save return values from plugin
## subroutines.
##
## [Note: Arguably it's bad coding style to re-use $entries
## in this potentially confusing way. As it happens it doesn't appear to be
## necessary to use a variable for this purpose anyway, since
## $entries is assigned to but never referenced -- why not just
## use code like
##
## foreach my $plugin ( @plugins ) { ... and $plugin->filter(...) }
##
## where we just test the return value directly and don't save it?]
##
## For more information see the following URL:
##
## http://www.blosxom.com/documentation/developers/plugins.html
##
foreach my $plugin ( @plugins ) { $plugins{$plugin} > 0 and $plugin->can('filter') and $entries = $plugin->filter(\%files, \%others) }
# Static
##
## h3. Deciding whether to generate static or dynamic pages
##
## We check to see if we are generating static pages or dynamic pages,
## and execute the appropriate code.
##
## [Note: The conditional expression below exactly duplicates the
## expression used at line 99 above to set the variable
## $static_or_dynamic; why not just use the code
##
## if ($static_or_dynamic eq 'static') {
##
## instead?]
##
if (!$ENV{GATEWAY_INTERFACE} and param('-password') and $static_password and param('-password') eq $static_password) {
## h3. Generating static pages: The -quiet option
##
## We print a status message (on stdout) unless the option
## -quiet was passed on the command line.
##
param('-quiet') or print "Blosxom is generating static index pages...\n";
# Home Page and Directory Indexes
##
## h3. Generating static pages: The %done hash
##
## The %done hash is used to keep track of whether we've done
## static page generation for a particular directory; see the notes for line
## 240 below.
##
my %done;
##
## h3. Generating static pages: Iterating over %indexes
##
## We iterate over all index-related items stored in the
## %indexes hash.
##
## Recall that if we are generating static pages then %indexes
## will contain three types of items, all expressed as relative pathnames
## (relative to the Blosxom data directory): category directories for which
## index.* pages need to be generated (e.g., a/b),
## date directories that need to be created with index.* pages
## to support date-based URLs (e.g., 2004/05/22), and individual
## entries for which static pages need to be generated (e.g.,
## a/b/foo.txt).
##
## For each item we will need to create not only static pages for those
## items, but also the directories needed to contain those static pages,
## the higher-level directories containing those directories (e.g.,
## subdirectory a under the data directory for a category
## directory a/b, or directories 2004 and
## 2004/05 for a date directory 2004/05/22), and
## index pages for those higher-level directories.
##
foreach my $path ( sort keys %indexes) {
##
## h3. Generating static pages: Keeping track of parent directories
##
## As noted above we have to worry not only about static pages corresponding
## directly to each %indexes key (e.g., the index page
## a/b/index.html where $path is the
## %indexes item a/b), but also static pages for
## any higher-level directories (e.g., the index page
## a/index.html for directory a as well as the
## index page index.html for the data directory itself, the
## parent directory of a).
##
## The variable $p is used to iterate over all directory
## components in $path and make sure that the necessary
## directories are created and index pages generated. We start off at
## $p = '', representing the Blosxom data directory itself.
##
my $p = '';
##
## h3. Generating static pages: Iterating over $path
## components
##
## We iterate over each component of the relative pathname stored in
## $path, in order to create higher-level directories and
## their corresponding static pages where appropriate. We include the empty
## string '' as the first element of the foreach list in order
## to handle index files at the level of the Blosxom data directory.
##
## Thus, for example, if $path is a/b then we will
## iterate over '', a, and b. If $path
## is 2004/05/22 then we will iterate over '',
## 2004, 05, and 22.
##
foreach ( ('', split /\//, $path) ) {
##
## h3. Generating static pages: Building the relative pathname $p
##
## We add the current subdirectory component to the relative pathname being
## built up. Since $p is initially the empty string '' and the
## first element of the foreach loop is '' as well,
## $p will be set to '/' the first time through the loop, and
## we'll then need to remove the leading '/'. On subsequent iterations
## $p will end up being set to, e.g., 2004,
## 2004/05, etc., assuming a value for $path of
## 2004/05/22.
##
$p .= "/$_";
$p =~ s!^/!!;
##
## h3. Generating static pages: Make relative path available to plugins
##
## We save the current relative pathname (in $p) as
## $path_info so that plugins will have access to it (as a global
## variable in the blosxom package).
##
$path_info = $p;
##
## h3. Generating static pages: Relative path already processed?
##
## We keep track of whether we have seen this relative path before.
## If not (i.e., if $done{$p} is false) then we increment
## $done{$p} by 1 and proceed to process it. Otherwise we skip
## to the next item in the foreach loop.
##
### h3. For people learning Perl: $a++ vs. ++$a
###
### Note that the check here works because ++ is used as a
### suffix operator, and hence $done{$p} is incremented after
### its value is checked. Also, if $done{$p} is undefined (which
### would be the case initially, since %done is not otherwise
### initialized) then its value will be converted to zero prior to
### incrementing it.
###
### For more information see the following URL:
###
### http://www.perldoc.com/perl5.8.4/pod/perlop.html#Auto-increment-and-Auto-decrement
###
$done{$p}++ and next;
##
## h3. Generating static pages: Creating directories as needed
##
## We check to see if there is already an existing directory corresponding to
## the path we're working on, or if the path represents an individual
## entry. Otherwise the path represents a directory that needs to be created,
## and we use the mkdir function to create the directory. (We
## attempt to set the directory's access permissions to "rwxr-xr-x" so that
## anyone can look up files in the directory, but this may be made more
## restrictive by the umask setting of the user executing
## blosxom.cgi in static mode. Note that at a minimum the userid
## associated with the web server, e.g., "httpd", needs "r" access to the
## static pages and "rx" access to the directories containing them.)
##
## For more information see the following URLs:
##
## http://www.perldoc.com/perl5.8.4/pod/func/-X.html
## http://www.perldoc.com/perl5.8.4/pod/func/mkdir.html
##
(-d "$static_dir/$p" or $p =~ /\.$file_extension$/) or mkdir "$static_dir/$p", 0755;
##
## h3. Generating static pages: Creating pages for all needed flavours
##
## We iterate over all the flavours for which we need to create static pages.
##
foreach $flavour ( @static_flavours ) {
##
## h3. Generating static pages: Determining the content type for a flavour
##
## We use the reference stored in $template to call a subroutine
## to determine what content type we should pass to the generate subroutine.
## (Recall that $template was set to a reference to an anonymous
## subroutine returned by a template subroutine, either the default defined
## in blosxom.cgi or one provided by a plugin to override the
## default.)
##
## In the case of the default subroutine we look for a file with filename
## content_type.$flavour (e.g.,
## content_type.html) in the directory specified
## by $p or in its parent directories (up to and including the
## Blosxom data directory) and, if found, use the value of
## content_type that it defines. Otherwise we use the default
## value of content_type found in the %templates
## hash; for example, for the 'html' flavour we would use
## a content type of 'text/html'.
##
## We look for a newline in the content_type value and delete
## it and anything after it. This might be the case if the content type were
## defined in a file; we only need the first line of the file (prior to
## the first newline) and can ignore the rest.
##
my $content_type = (&$template($p,'content_type',$flavour));
$content_type =~ s!\n.*!!s;
##
## h3. Generating static pages: Relative pathname for the page to be created
##
## We determine the relative pathname for the static page we need to create,
## up to but not including the extension. If the path $p
## represents an individual entry (e.g., a/b/foo.txt) then
## $fn will be, e.g., a/b/foo; otherwise
## $p represents a directory in which index files need to be
## created and $fn will be, e.g., a/b/index.
##
## [Note: Unlike $content_type (which depends on the
## specific flavour for which we need to create a static page), the value of
## $fn could have been determined before entering the static
## flavour foreach loop, since it will be the same no matter
## what the flavour happens to be.]
##
my $fn = $p =~ m!^(.+)\.$file_extension$! ? $1 : "$p/index";
param('-quiet') or print "$fn.$flavour\n";
##
## h3. For people learning Perl: Opening files for writing
##
## We attempt to create (or rewrite, i.e., open and truncate) the static page
## for this favour.
##
## For more information see the following URLs:
##
## http://search.cpan.org/~nwclark/perl-5.8.4/lib/FileHandle.pm
## http://www.perldoc.com/perl5.8.4/pod/func/open.html
##
my $fh_w = new FileHandle "> $static_dir/$fn.$flavour" or die "Couldn't open $static_dir/$p for writing: $!";
##
## h3. Generating static pages: Beginning output for a page
##
## $output is the global variable used by the
## generate subroutine to build up the data for the page.
##
$output = '';
##
## h3. Generating static pages: Generating a page
##
## We call the generate subroutine to generate the data for
## the static page and then write it to the just-opened file.
##
## If the current %indexes element has the value 1 then it
## corresponds to a category directory or individual entry, and we pass
## $p to the generate subroutine as its
## $currentdir argument and the empty string '' as the
## $date argument.
## Otherwise the %indexes element represents a date-related
## index page and we pass $p as the date and the empty string
## '' as the $currentdir argument.
##
## See the notes for lines 266 (generating a dynamic page) and 273 and 274
## (the generate subroutine) for more information about the
## arguments passed.
##
print $fh_w
$indexes{$path} == 1
##
## h3. For people learning Perl: Referencing the generate
## subroutine
##
## Note: In this expression we use
##
## &generate(...)
##
## instead of
##
## generate(...)
##
## as one might expect. According to the online Perl documentation the
## initial '&' is typically optional and may be omitted. Using '&' does
## disable checking of prototypes, but the generate subroutine
## doesn't use prototypes. Is there some other reason for using '&'
## here?
##
## For more information see the following URL:
##
## http://www.perldoc.com/perl5.8.4/pod/perlsub.html
##
? &generate('static', $p, '', $flavour, $content_type)
: &generate('static', '', $p, $fl