//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; } #}}} @@ -633,10 +696,11 @@ sub preprocess ($$$;$$) { #{{{ 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}; @@ -644,7 +708,7 @@ sub preprocess ($$$;$$) { #{{{ # consider it significant. my @params; while ($params =~ m{ - (?:(\w+)=)? # 1: named parameter key? + (?:([-\w]+)=)? # 1: named parameter key? (?: """(.*?)""" # 2: triple-quoted value | @@ -687,41 +751,81 @@ sub preprocess ($$$;$$) { #{{{ $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 (?: - """.*?""" # 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; + } else { + $regex = qr{ + (\\?) # 1: escape? + \[\[(!?) # directive open; 2: optional prefix + ([-\w]+) # 3: command + \s+ + ( # 4: 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 + }sx; + } + + $content =~ s{$regex}{$handle->($1, $2, $3, $4)}eg; return $content; } #}}} @@ -772,7 +876,8 @@ sub lockwiki (;$) { #{{{ } #}}} sub unlockwiki () { #{{{ - return close($wikilock); + return close($wikilock) if $wikilock; + return; } #}}} my $commitlock; @@ -798,37 +903,57 @@ sub disable_commit_hook () { #{{{ } #}}} sub enable_commit_hook () { #{{{ - return close($commitlock); + return close($commitlock) if $commitlock; + return; } #}}} sub loadindex () { #{{{ - 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); + %oldrenderedfiles=%pagectime=(); + if (! $config{rebuild}) { + %pagesources=%pagemtime=%oldlinks=%links=%depends= + %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; + $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); } #}}} @@ -836,28 +961,47 @@ sub loadindex () { #{{{ sub saveindex () { #{{{ run_hooks(savestate => sub { shift->() }); + my %hookids; + foreach my $type (keys %hooks) { + $hookids{$_}=1 foreach keys %{$hooks{$type}}; + } + 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}}) { + $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; } #}}} @@ -882,7 +1026,7 @@ sub template_params (@) { #{{{ my @ret=( filter => sub { my $text_ref = shift; - ${$text_ref} = Encode::decode_utf8(${$text_ref}); + ${$text_ref} = decode_utf8(${$text_ref}); }, filename => $filename, loop_context_vars => 1, @@ -993,6 +1137,8 @@ sub add_depends ($$) { #{{{ my $page=shift; my $pagespec=shift; + return unless pagespec_valid($pagespec); + if (! exists $depends{$page}) { $depends{$page}=$pagespec; } @@ -1007,15 +1153,17 @@ sub file_pruned ($$) { #{{{ require File::Spec; my $file=File::Spec->canonpath(shift); my $base=File::Spec->canonpath(shift); - $file =~ s#^\Q$base\E/*##; + $file =~ s#^\Q$base\E/+##; my $regexp='('.join('|', @{$config{wiki_file_prune_regexps}}).')'; - return $file =~ m/$regexp/; + return $file =~ m/$regexp/ && $file ne $base; } #}}} sub gettext { #{{{ # Only use gettext in the rare cases it's needed. - if (exists $ENV{LANG} || exists $ENV{LC_ALL} || exists $ENV{LC_MESSAGES}) { + if ((exists $ENV{LANG} && length $ENV{LANG}) || + (exists $ENV{LC_ALL} && length $ENV{LC_ALL}) || + (exists $ENV{LC_MESSAGES} && length $ENV{LC_MESSAGES})) { if (! $gettext_obj) { $gettext_obj=eval q{ use Locale::gettext q{textdomain}; @@ -1052,8 +1200,6 @@ sub pagespec_merge ($$) { #{{{ } #}}} 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. @@ -1090,18 +1236,22 @@ sub pagespec_translate ($) { #{{{ } 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 ($$;@) { #{{{ @@ -1114,9 +1264,16 @@ 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; @@ -1167,13 +1324,22 @@ sub match_glob ($$;@) { #{{{ $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); @@ -1197,7 +1363,7 @@ sub match_link ($$;@) { #{{{ if $bestlink eq IkiWiki::bestlink($page, $p); } else { - return IkiWiki::SuccessReason->new("$page links to page matching $link") + return IkiWiki::SuccessReason->new("$page links to page $p matching $link") if match_glob($p, $link, %params); } } @@ -1269,19 +1435,4 @@ sub match_creation_year ($$;@) { #{{{ } } #}}} -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