use HTML::Entities;
use URI::Escape q{uri_escape_utf8};
use POSIX;
+use Storable;
use open qw{:utf8 :std};
use vars qw{%config %links %oldlinks %pagemtime %pagectime %pagecase
sub defaultconfig () { #{{{
return
- wiki_file_prune_regexps => [qr/^\.\.$/, qr/^\./, qr/\/\./,
+ wiki_file_prune_regexps => [qr/(^|\/)\.\.(\/|$)/, qr/^\./, qr/\/\./,
qr/\.x?html?$/, qr/\.ikiwiki-new$/,
qr/(^|\/).svn\//, qr/.arch-ids\//, qr/{arch}\//,
qr/(^|\/)_MTN\//,
qr/\.dpkg-tmp$/],
- wiki_link_regexp => qr{
- \[\[ # beginning of link
- (?:
- ([^\]\|\n\s]+) # 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,
cgi => 0,
post_commit => 0,
rcs => '',
- notify => 0,
url => '',
cgiurl => '',
historyurl => '',
diffurl => '',
rss => 0,
atom => 0,
+ allowrss => 0,
+ allowatom => 0,
discussion => 1,
rebuild => 0,
refresh => 0,
w3mmode => 0,
wrapper => undef,
wrappermode => undef,
- svnrepo => undef,
svnpath => "trunk",
gitorigin_branch => "origin",
gitmaster_branch => "master",
setup => undef,
adminuser => undef,
adminemail => undef,
- plugin => [qw{mdwn inline htmlscrubber passwordauth openid signinedit
- lockedit conditional}],
+ plugin => [qw{mdwn link inline htmlscrubber passwordauth openid
+ signinedit lockedit conditional recentchanges}],
libdir => undef,
timeformat => '%c',
locale => undef,
usedirs => 1,
numbacklinks => 10,
account_creation_password => "",
+ prefix_directives => 0,
+ hardlink => 0,
} #}}}
sub checkconfig () { #{{{
return;
} #}}}
+sub isinternal ($) { #{{{
+ my $page=shift;
+ return exists $pagesources{$page} &&
+ $pagesources{$page} =~ /\._([^.]+)$/;
+} #}}}
+
sub pagename ($) { #{{{
my $file=shift;
return targetpage($page, $config{htmlext});
} #}}}
-sub srcfile ($) { #{{{
+sub srcfile_stat { #{{{
my $file=shift;
+ my $nothrow=shift;
- return "$config{srcdir}/$file" if -e "$config{srcdir}/$file";
+ return "$config{srcdir}/$file", stat(_) if -e "$config{srcdir}/$file";
foreach my $dir (@{$config{underlaydirs}}, $config{underlaydir}) {
- return "$dir/$file" if -e "$dir/$file";
+ return "$dir/$file", stat(_) if -e "$dir/$file";
}
- error("internal error: $file cannot be found in $config{srcdir} or underlay");
+ error("internal error: $file cannot be found in $config{srcdir} or underlay") unless $nothrow;
return;
} #}}}
+sub srcfile ($;$) { #{{{
+ return (srcfile_stat(@_))[0];
+} #}}}
+
sub add_underlay ($) { #{{{
my $dir=shift;
return $ret;
} #}}}
-sub writefile ($$$;$$) { #{{{
- my $file=shift; # can include subdirs
- my $destdir=shift; # directory to put file in
- my $content=shift;
- my $binary=shift;
- my $writer=shift;
+sub prep_writefile ($$) {
+ my $file=shift;
+ my $destdir=shift;
my $test=$file;
while (length $test) {
}
$test=dirname($test);
}
- my $newfile="$destdir/$file.ikiwiki-new";
- if (-l $newfile) {
- error("cannot write to a symlink ($newfile)");
- }
- my $dir=dirname($newfile);
+ my $dir=dirname("$destdir/$file");
if (! -d $dir) {
my $d="";
foreach my $s (split(m!/+!, $dir)) {
}
}
+ return 1;
+}
+
+sub writefile ($$$;$$) { #{{{
+ my $file=shift; # can include subdirs
+ my $destdir=shift; # directory to put file in
+ my $content=shift;
+ my $binary=shift;
+ my $writer=shift;
+
+ prep_writefile($file, $destdir);
+
+ my $newfile="$destdir/$file.ikiwiki-new";
+ if (-l $newfile) {
+ error("cannot write to a symlink ($newfile)");
+ }
+
my $cleanup = sub { unlink($newfile) };
open (my $out, '>', $newfile) || error("failed to write $newfile: $!", $cleanup);
binmode($out) if ($binary);
return "<a href=\"$bestlink\"@attrs>$linktext</a>";
} #}}}
+sub userlink ($) { #{{{
+ my $user=shift;
+
+ my $oiduser=eval { openiduser($user) };
+ if (defined $oiduser) {
+ return "<a href=\"$user\">$oiduser</a>";
+ }
+ else {
+ eval q{use CGI 'escapeHTML'};
+ error($@) if $@;
+
+ return htmllink("", "", escapeHTML(
+ length $config{userdir} ? $config{userdir}."/".$user : $user
+ ), noimageinline => 1);
+ }
+} #}}}
+
sub htmlize ($$$) { #{{{
my $page=shift;
my $type=shift;
my $content=shift;
+
+ my $oneline = $content !~ /\n/;
if (exists $hooks{htmlize}{$type}) {
$content=$hooks{htmlize}{$type}{call}->(
content => $content,
);
});
+
+ if ($oneline) {
+ # hack to get rid of enclosing junk added by markdown
+ # and other htmlizers
+ $content=~s/^<p>//i;
+ $content=~s/<\/p>$//i;
+ chomp $content;
+ }
return $content;
} #}}}
sub linkify ($$$) { #{{{
- my $lpage=shift; # the page containing the links
- my $page=shift; # the page the link will end up on (different for inline)
+ my $page=shift;
+ my $destpage=shift;
my $content=shift;
- $content =~ s{(\\?)$config{wiki_link_regexp}}{
- defined $2
- ? ( $1
- ? "[[$2|$3".($4 ? "#$4" : "")."]]"
- : htmllink($lpage, $page, linkpage($3),
- anchor => $4, linktext => pagetitle($2)))
- : ( $1
- ? "[[$3".($4 ? "#$4" : "")."]]"
- : htmllink($lpage, $page, linkpage($3),
- anchor => $4))
- }eg;
+ run_hooks(linkify => sub {
+ $content=shift->(
+ page => $page,
+ destpage => $destpage,
+ content => $content,
+ );
+ });
return $content;
} #}}}
my $handle=sub {
my $escape=shift;
+ my $prefix=shift;
my $command=shift;
my $params=shift;
if (length $escape) {
- return "[[$command $params]]";
+ return "[[$prefix$command $params]]";
}
elsif (exists $hooks{preprocess}{$command}) {
return "" if $scan && ! $hooks{preprocess}{$command}{scan};
$command, $page, $preprocessing{$page}).
"]]";
}
- my $ret=$hooks{preprocess}{$command}{call}->(
- @params,
- page => $page,
- destpage => $destpage,
- preview => $preprocess_preview,
- );
+ my $ret;
+ if (! $scan) {
+ $ret=$hooks{preprocess}{$command}{call}->(
+ @params,
+ page => $page,
+ destpage => $destpage,
+ preview => $preprocess_preview,
+ );
+ }
+ else {
+ # use void context during scan pass
+ $hooks{preprocess}{$command}{call}->(
+ @params,
+ page => $page,
+ destpage => $destpage,
+ preview => $preprocess_preview,
+ );
+ $ret="";
+ }
$preprocessing{$page}--;
return $ret;
}
else {
- return "[[$command $params]]";
+ return "[[$prefix$command $params]]";
}
};
- $content =~ s{
- (\\?) # 1: escape?
- \[\[ # directive open
- ([-\w]+) # 2: command
- \s+
- ( # 3: the parameters..
- (?:
- (?:[-\w]+=)? # named parameter key?
+ my $regex;
+ if ($config{prefix_directives}) {
+ $regex = qr{
+ (\\?) # 1: escape?
+ \[\[(!) # directive open; 2: prefix
+ ([-\w]+) # 3: command
+ ( # 4: the parameters..
+ \s+ # Must have space if parameters present
+ (?:
+ (?:[-\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
+ }sx;
+ } else {
+ $regex = qr{
+ (\\?) # 1: escape?
+ \[\[(!?) # directive open; 2: optional prefix
+ ([-\w]+) # 3: command
+ \s+
+ ( # 4: the parameters..
(?:
- """.*?""" # triple-quoted value
- |
- "[^"]+" # single-quoted value
- |
- [^\s\]]+ # unquoted value
+ (?:[-\w]+=)? # named parameter key?
+ (?:
+ """.*?""" # triple-quoted value
+ |
+ "[^"]+" # single-quoted value
+ |
+ [^\s\]]+ # unquoted value
+ )
+ \s* # whitespace or end
+ # of directive
)
- \s* # whitespace or end
- # of directive
- )
- *) # 0 or more parameters
- \]\] # directive closed
- }{$handle->($1, $2, $3)}sexg;
+ *) # 0 or more parameters
+ \]\] # directive closed
+ }sx;
+ }
+
+ $content =~ s{$regex}{$handle->($1, $2, $3, $4)}eg;
return $content;
} #}}}
%oldrenderedfiles=%pagectime=();
if (! $config{rebuild}) {
%pagesources=%pagemtime=%oldlinks=%links=%depends=
- %destsources=%renderedfiles=%pagecase=();
- }
- open (my $in, "<", "$config{wikistatedir}/index") || return;
- while (<$in>) {
- $_=possibly_foolish_untaint($_);
- chomp;
- my %items;
- $items{link}=[];
- $items{dest}=[];
- foreach my $i (split(/ /, $_)) {
- my ($item, $val)=split(/=/, $i, 2);
- push @{$items{$item}}, decode_entities($val);
+ %destsources=%renderedfiles=%pagecase=%pagestate=();
+ }
+ my $in;
+ if (! open ($in, "<", "$config{wikistatedir}/indexdb")) {
+ if (-e "$config{wikistatedir}/index") {
+ system("ikiwiki-transition", "indexdb", $config{srcdir});
+ open ($in, "<", "$config{wikistatedir}/indexdb") || return;
}
-
- next unless exists $items{src}; # skip bad lines for now
-
- my $page=pagename($items{src}[0]);
+ else {
+ return;
+ }
+ }
+ my $ret=Storable::fd_retrieve($in);
+ if (! defined $ret) {
+ return 0;
+ }
+ my %index=%$ret;
+ foreach my $src (keys %index) {
+ my %d=%{$index{$src}};
+ my $page=pagename($src);
+ $pagectime{$page}=$d{ctime};
if (! $config{rebuild}) {
- $pagesources{$page}=$items{src}[0];
- $pagemtime{$page}=$items{mtime}[0];
- $oldlinks{$page}=[@{$items{link}}];
- $links{$page}=[@{$items{link}}];
- $depends{$page}=$items{depends}[0] if exists $items{depends};
- $destsources{$_}=$page foreach @{$items{dest}};
- $renderedfiles{$page}=[@{$items{dest}}];
- $pagecase{lc $page}=$page;
- foreach my $k (grep /_/, keys %items) {
- my ($id, $key)=split(/_/, $k, 2);
- $pagestate{$page}{decode_entities($id)}{decode_entities($key)}=$items{$k}[0];
+ $pagesources{$page}=$src;
+ $pagemtime{$page}=$d{mtime};
+ $renderedfiles{$page}=$d{dest};
+ if (exists $d{links} && ref $d{links}) {
+ $links{$page}=$d{links};
+ $oldlinks{$page}=[@{$d{links}}];
+ }
+ if (exists $d{depends}) {
+ $depends{$page}=$d{depends};
+ }
+ if (exists $d{state}) {
+ $pagestate{$page}=$d{state};
}
}
- $oldrenderedfiles{$page}=[@{$items{dest}}];
- $pagectime{$page}=$items{ctime}[0];
+ $oldrenderedfiles{$page}=[@{$d{dest}}];
+ }
+ foreach my $page (keys %pagesources) {
+ $pagecase{lc $page}=$page;
+ }
+ foreach my $page (keys %renderedfiles) {
+ $destsources{$_}=$page foreach @{$renderedfiles{$page}};
}
return close($in);
} #}}}
my %hookids;
foreach my $type (keys %hooks) {
- $hookids{encode_entities($_)}=1 foreach keys %{$hooks{$type}};
+ $hookids{$_}=1 foreach keys %{$hooks{$type}};
}
- my @hookids=sort keys %hookids;
+ my @hookids=keys %hookids;
if (! -d $config{wikistatedir}) {
mkdir($config{wikistatedir});
}
- my $newfile="$config{wikistatedir}/index.new";
+ my $newfile="$config{wikistatedir}/indexdb.new";
my $cleanup = sub { unlink($newfile) };
open (my $out, '>', $newfile) || error("cannot write to $newfile: $!", $cleanup);
+ my %index;
foreach my $page (keys %pagemtime) {
next unless $pagemtime{$page};
- my $line="mtime=$pagemtime{$page} ".
- "ctime=$pagectime{$page} ".
- "src=$pagesources{$page}";
- $line.=" dest=$_" foreach @{$renderedfiles{$page}};
- my %count;
- $line.=" link=$_" foreach grep { ++$count{$_} == 1 } @{$links{$page}};
+ my $src=$pagesources{$page};
+
+ $index{$src}={
+ ctime => $pagectime{$page},
+ mtime => $pagemtime{$page},
+ dest => $renderedfiles{$page},
+ links => $links{$page},
+ };
+
if (exists $depends{$page}) {
- $line.=" depends=".encode_entities($depends{$page}, " \t\n");
+ $index{$src}{depends} = $depends{$page};
}
+
if (exists $pagestate{$page}) {
foreach my $id (@hookids) {
foreach my $key (keys %{$pagestate{$page}{$id}}) {
- $line.=' '.$id.'_'.encode_entities($key)."=".encode_entities($pagestate{$page}{$id}{$key});
+ $index{$src}{state}{$id}{$key}=$pagestate{$page}{$id}{$key};
}
}
}
- print $out $line."\n" || error("failed writing to $newfile: $!", $cleanup);
}
+ my $ret=Storable::nstore_fd(\%index, $out);
+ return if ! defined $ret || ! $ret;
close $out || error("failed saving to $newfile: $!", $cleanup);
- rename($newfile, "$config{wikistatedir}/index") ||
- error("failed renaming $newfile to $config{wikistatedir}/index", $cleanup);
+ rename($newfile, "$config{wikistatedir}/indexdb") ||
+ error("failed renaming $newfile to $config{wikistatedir}/indexdb", $cleanup);
return 1;
} #}}}
my $page=shift;
my $pagespec=shift;
+ return unless pagespec_valid($pagespec);
+
if (! exists $depends{$page}) {
$depends{$page}=$pagespec;
}
} #}}}
sub pagespec_translate ($) { #{{{
- # This assumes that $page is in scope in the function
- # that evalulates the translated pagespec code.
my $spec=shift;
# Support for old-style GlobLists.
}
elsif ($word =~ /^(\w+)\((.*)\)$/) {
if (exists $IkiWiki::PageSpec::{"match_$1"}) {
- $code.="IkiWiki::PageSpec::match_$1(\$page, ".safequote($2).", \@params)";
+ $code.="IkiWiki::PageSpec::match_$1(\$page, ".safequote($2).", \@_)";
}
else {
$code.=' 0';
}
}
else {
- $code.=" IkiWiki::PageSpec::match_glob(\$page, ".safequote($word).", \@params)";
+ $code.=" IkiWiki::PageSpec::match_glob(\$page, ".safequote($word).", \@_)";
}
}
- return $code;
+ if (! length $code) {
+ $code=0;
+ }
+
+ return eval 'sub { my $page=shift; '.$code.' }';
} #}}}
sub pagespec_match ($$;@) { #{{{
unshift @params, 'location';
}
- my $ret=eval pagespec_translate($spec);
+ my $sub=pagespec_translate($spec);
return IkiWiki::FailReason->new('syntax error') if $@;
- return $ret;
+ return $sub->($page, @params);
+} #}}}
+
+sub pagespec_valid ($) { #{{{
+ my $spec=shift;
+
+ my $sub=pagespec_translate($spec);
+ return ! $@;
} #}}}
package IkiWiki::FailReason;
); #}}}
sub new { #{{{
- return bless \$_[1], $_[0];
+ my $class = shift;
+ my $value = shift;
+ return bless \$value, $class;
} #}}}
package IkiWiki::SuccessReason;
); #}}}
sub new { #{{{
- return bless \$_[1], $_[0];
+ my $class = shift;
+ my $value = shift;
+ return bless \$value, $class;
}; #}}}
package IkiWiki::PageSpec;
$glob=~s/\\\?/./g;
if ($page=~/^$glob$/i) {
- return IkiWiki::SuccessReason->new("$glob matches $page");
+ if (! IkiWiki::isinternal($page) || $params{internal}) {
+ return IkiWiki::SuccessReason->new("$glob matches $page");
+ }
+ else {
+ return IkiWiki::FailReason->new("$glob matches $page, but the page is an internal page");
+ }
}
else {
return IkiWiki::FailReason->new("$glob does not match $page");
}
} #}}}
+sub match_internal ($$;@) { #{{{
+ return match_glob($_[0], $_[1], @_, internal => 1)
+} #}}}
+
sub match_link ($$;@) { #{{{
my $page=shift;
my $link=lc(shift);
}
} #}}}
-sub match_user ($$;@) { #{{{
- shift;
- my $user=shift;
- my %params=@_;
-
- return IkiWiki::FailReason->new('cannot match user')
- unless exists $params{user};
- if ($user eq $params{user}) {
- return IkiWiki::SuccessReason->new("user is $user")
- }
- else {
- return IkiWiki::FailReason->new("user is not $user");
- }
-} #}}}
-
1