]> sipb.mit.edu Git - ikiwiki.git/blobdiff - IkiWiki.pm
* Change yet again how unhandled and escaped preprocessor directives are
[ikiwiki.git] / IkiWiki.pm
index 70fc6ff736cf7ef9a19104b75ba0d952c1b8942d..37c73cb9fa48ce3716b93576005b3b5c2a3b2fcf 100644 (file)
@@ -33,13 +33,28 @@ sub defaultconfig () { #{{{
                qr/\.x?html?$/, qr/\.ikiwiki-new$/,
                qr/(^|\/).svn\//, qr/.arch-ids\//, qr/{arch}\//,
                qr/\.dpkg-tmp$/],
-       wiki_link_regexp => qr/\[\[(?:([^\]\|]+)\|)?([^\s\]#]+)(?:#([^\s\]]+))?\]\]/,
+       wiki_link_regexp => qr{
+               \[\[                    # beginning of link
+               (?:
+                       ([^\]\|]+)      # 1: link text
+                       \|              # followed by '|'
+               )?                      # optional
+
+               ([^\s\]#]+)             # 2: page to link to
+               (?:
+                       \#              # '#', beginning of anchor
+                       ([^\s\]]+)      # 3: anchor text
+               )?                      # optional
+
+               \]\]                    # end of link
+       }x,
        wiki_file_regexp => qr/(^[-[:alnum:]_.:\/+]+$)/,
        web_commit_regexp => qr/^web commit (by (.*?(?=: |$))|from (\d+\.\d+\.\d+\.\d+)):?(.*)/,
        verbose => 0,
        syslog => 0,
        wikiname => "wiki",
        default_pageext => "mdwn",
+       htmlext => "html",
        cgi => 0,
        post_commit => 0,
        rcs => '',
@@ -69,15 +84,17 @@ sub defaultconfig () { #{{{
        setup => undef,
        adminuser => undef,
        adminemail => undef,
-       plugin => [qw{mdwn inline htmlscrubber passwordauth signinedit
+       plugin => [qw{mdwn inline htmlscrubber passwordauth openid signinedit
                      lockedit conditional}],
+       libdir => undef,
        timeformat => '%c',
        locale => undef,
        sslcookie => 0,
        httpauth => 0,
        userdir => "",
-       usedirs => 0,
+       usedirs => 1,
        numbacklinks => 10,
+       account_creation_password => "",
 } #}}}
    
 sub checkconfig () { #{{{
@@ -111,7 +128,7 @@ sub checkconfig () { #{{{
                unless exists $config{wikistatedir};
        
        if ($config{rcs}) {
-               eval qq{require IkiWiki::Rcs::$config{rcs}};
+               eval qq{use IkiWiki::Rcs::$config{rcs}};
                if ($@) {
                        error("Failed to load RCS module IkiWiki::Rcs::$config{rcs}: $@");
                }
@@ -124,8 +141,12 @@ sub checkconfig () { #{{{
 } #}}}
 
 sub loadplugins () { #{{{
+       if (defined $config{libdir}) {
+               unshift @INC, $config{libdir};
+       }
+
        loadplugin($_) foreach @{$config{plugin}};
-       
+
        run_hooks(getopt => sub { shift->() });
        if (grep /^-/, @ARGV) {
                print STDERR "Unknown option: $_\n"
@@ -139,11 +160,20 @@ sub loadplugin ($) { #{{{
 
        return if grep { $_ eq $plugin} @{$config{disable_plugins}};
 
+       foreach my $dir ($config{libdir}, "$installdir/lib/ikiwiki") {
+               if (defined $dir && -x "$dir/plugins/$plugin") {
+                       require IkiWiki::Plugin::external;
+                       import IkiWiki::Plugin::external "$dir/plugins/$plugin";
+                       return 1;
+               }
+       }
+
        my $mod="IkiWiki::Plugin::".possibly_foolish_untaint($plugin);
        eval qq{use $mod};
        if ($@) {
                error("Failed to load plugin $mod: $@");
        }
+       return 1;
 } #}}}
 
 sub error ($;$) { #{{{
@@ -191,7 +221,7 @@ sub log_message ($$) { #{{{
 
 sub possibly_foolish_untaint ($) { #{{{
        my $tainted=shift;
-       my ($untainted)=$tainted=~/(.*)/;
+       my ($untainted)=$tainted=~/(.*)/s;
        return $untainted;
 } #}}}
 
@@ -241,7 +271,7 @@ sub targetpage ($$) { #{{{
 sub htmlpage ($) { #{{{
        my $page=shift;
        
-       return targetpage($page, "html");
+       return targetpage($page, $config{htmlext});
 } #}}}
 
 sub srcfile ($) { #{{{
@@ -451,7 +481,7 @@ sub displaytime ($) { #{{{
 sub beautify_url ($) { #{{{
        my $url=shift;
 
-       $url =~ s!/index.html$!/!;
+       $url =~ s!/index.$config{htmlext}$!/!;
        $url =~ s!^$!./!; # Browsers don't like empty links...
 
        return $url;
@@ -525,7 +555,12 @@ sub htmllink ($$$;@) { #{{{
                $bestlink.="#".$opts{anchor};
        }
 
-       return "<a href=\"$bestlink\">$linktext</a>";
+       my @attrs;
+       if (defined $opts{rel}) {
+               push @attrs, ' rel="'.$opts{rel}.'"';
+       }
+
+       return "<a href=\"$bestlink\"@attrs>$linktext</a>";
 } #}}}
 
 sub htmlize ($$$) { #{{{
@@ -598,7 +633,17 @@ sub preprocess ($$$;$$) { #{{{
                        # Note: preserve order of params, some plugins may
                        # consider it significant.
                        my @params;
-                       while ($params =~ /(?:(\w+)=)?(?:"""(.*?)"""|"([^"]+)"|(\S+))(?:\s+|$)/sg) {
+                       while ($params =~ m{
+                               (?:(\w+)=)?             # 1: named parameter key?
+                               (?:
+                                       """(.*?)"""     # 2: triple-quoted value
+                               |
+                                       "([^"]+)"       # 3: single-quoted value
+                               |
+                                       (\S+)           # 4: unquoted value
+                               )
+                               (?:\s+|$)               # delimiter to next param
+                       }sgx) {
                                my $key=$1;
                                my $val;
                                if (defined $2) {
@@ -646,16 +691,38 @@ sub preprocess ($$$;$$) { #{{{
                }
        };
        
-       $content =~ s{(\\?)\[\[(\w+)\s+((?:(?:\w+=)?(?:""".*?"""|"[^"]+"|[^\s\]]+)\s*)*)\]\]}{$handle->($1, $2, $3)}seg;
+       $content =~ s{
+               (\\?)           # 1: escape?
+               \[\[            # directive open
+               (\w+)           # 2: command
+               \s+
+               (               # 3: the parameters..
+                       (?:
+                               (?:\w+=)?               # named parameter key?
+                               (?:
+                                       """.*?"""       # triple-quoted value
+                                       |
+                                       "[^"]+"         # single-quoted value
+                                       |
+                                       [^\s\]]+        # unquoted value
+                               )
+                               \s*                     # whitespace or end
+                                                       # of directive
+                       )
+               *)              # 0 or more parameters
+               \]\]            # directive closed
+       }{$handle->($1, $2, $3)}sexg;
        return $content;
 } #}}}
 
-sub filter ($$) { #{{{
+sub filter ($$$) { #{{{
        my $page=shift;
+       my $destpage=shift;
        my $content=shift;
 
        run_hooks(filter => sub {
-               $content=shift->(page => $page, content => $content);
+               $content=shift->(page => $page, destpage => $destpage, 
+                       content => $content);
        });
 
        return $content;
@@ -665,7 +732,8 @@ sub indexlink () { #{{{
        return "<a href=\"$config{url}\">$config{wikiname}</a>";
 } #}}}
 
-sub lockwiki () { #{{{
+sub lockwiki (;$) { #{{{
+       my $wait=@_ ? shift : 1;
        # Take an exclusive lock on the wiki to prevent multiple concurrent
        # run issues. The lock will be dropped on program exit.
        if (! -d $config{wikistatedir}) {
@@ -674,15 +742,21 @@ sub lockwiki () { #{{{
        open(WIKILOCK, ">$config{wikistatedir}/lockfile") ||
                error ("cannot write to $config{wikistatedir}/lockfile: $!");
        if (! flock(WIKILOCK, 2 | 4)) { # LOCK_EX | LOCK_NB
-               debug("wiki seems to be locked, waiting for lock");
-               my $wait=600; # arbitrary, but don't hang forever to 
-                             # prevent process pileup
-               for (1..$wait) {
-                       return if flock(WIKILOCK, 2 | 4);
-                       sleep 1;
+               if ($wait) {
+                       debug("wiki seems to be locked, waiting for lock");
+                       my $wait=600; # arbitrary, but don't hang forever to 
+                                     # prevent process pileup
+                       for (1..$wait) {
+                               return if flock(WIKILOCK, 2 | 4);
+                               sleep 1;
+                       }
+                       error("wiki is locked; waited $wait seconds without lock being freed (possible stuck process or stale lock?)");
+               }
+               else {
+                       return 0;
                }
-               error("wiki is locked; waited $wait seconds without lock being freed (possible stuck process or stale lock?)");
        }
+       return 1;
 } #}}}
 
 sub unlockwiki () { #{{{
@@ -736,9 +810,9 @@ sub loadindex () { #{{{
                        $depends{$page}=$items{depends}[0] if exists $items{depends};
                        $destsources{$_}=$page foreach @{$items{dest}};
                        $renderedfiles{$page}=[@{$items{dest}}];
-                       $oldrenderedfiles{$page}=[@{$items{dest}}];
                        $pagecase{lc $page}=$page;
                }
+               $oldrenderedfiles{$page}=[@{$items{dest}}];
                $pagectime{$page}=$items{ctime}[0];
        }
        close IN;
@@ -788,7 +862,6 @@ sub template_params (@) { #{{{
                return "";
        }
 
-       require HTML::Template;
        my @ret=(
                filter => sub {
                        my $text_ref = shift;
@@ -803,6 +876,7 @@ sub template_params (@) { #{{{
 } #}}}
 
 sub template ($;@) { #{{{
+       require HTML::Template;
        HTML::Template->new(template_params(@_));
 } #}}}
 
@@ -967,7 +1041,21 @@ sub pagespec_translate ($) { #{{{
 
        # Convert spec to perl code.
        my $code="";
-       while ($spec=~m/\s*(\!|\(|\)|\w+\([^\)]+\)|[^\s()]+)\s*/ig) {
+       while ($spec=~m{
+               \s*             # ignore whitespace
+               (               # 1: match a single word
+                       \!              # !
+               |
+                       \(              # (
+               |
+                       \)              # )
+               |
+                       \w+\([^\)]*\)   # command(params)
+               |
+                       [^\s()]+        # any other text
+               )
+               \s*             # ignore whitespace
+       }igx) {
                my $word=$1;
                if (lc $word eq "and") {
                        $code.=" &&";
@@ -1046,8 +1134,8 @@ sub match_glob ($$;@) { #{{{
        
        # relative matching
        if ($glob =~ m!^\./!) {
-               $from=~s!/?[^/]+$!!;
-               $glob=~s!^\./!!;
+               $from=~s#/?[^/]+$##;
+               $glob=~s#^\./##;
                $glob="$from/$glob" if length $from;
        }
 
@@ -1073,18 +1161,23 @@ sub match_link ($$;@) { #{{{
 
        # relative matching
        if ($link =~ m!^\.! && defined $from) {
-               $from=~s!/?[^/]+$!!;
-               $link=~s!^\./!!;
+               $from=~s#/?[^/]+$##;
+               $link=~s#^\./##;
                $link="$from/$link" if length $from;
        }
 
        my $links = $IkiWiki::links{$page} or return undef;
        return IkiWiki::FailReason->new("$page has no links") unless @$links;
        my $bestlink = IkiWiki::bestlink($from, $link);
-       return IkiWiki::FailReason->new("no such link") unless length $bestlink;
        foreach my $p (@$links) {
-               return IkiWiki::SuccessReason->new("$page links to $link")
-                       if $bestlink eq IkiWiki::bestlink($page, $p);
+               if (length $bestlink) {
+                       return IkiWiki::SuccessReason->new("$page links to $link")
+                               if $bestlink eq IkiWiki::bestlink($page, $p);
+               }
+               else {
+                       return IkiWiki::SuccessReason->new("$page links to page matching $link")
+                               if match_glob($p, $link, %params);
+               }
        }
        return IkiWiki::FailReason->new("$page does not link to $link");
 } #}}}