+ return $spec;
+} #}}}
+
+sub is_globlist ($) { #{{{
+ my $s=shift;
+ $s=~/[^\s]+\s+([^\s]+)/ && $1 ne "and" && $1 ne "or";
+} #}}}
+
+sub safequote ($) { #{{{
+ my $s=shift;
+ $s=~s/[{}]//g;
+ return "q{$s}";
+} #}}}
+
+sub add_depends ($$) { #{{{
+ my $page=shift;
+ my $pagespec=shift;
+
+ if (! exists $depends{$page}) {
+ $depends{$page}=$pagespec;
+ }
+ else {
+ $depends{$page}=pagespec_merge($depends{$page}, $pagespec);
+ }
+} # }}}
+
+sub file_pruned ($$) { #{{{
+ require File::Spec;
+ my $file=File::Spec->canonpath(shift);
+ my $base=File::Spec->canonpath(shift);
+ $file=~s#^\Q$base\E/*##;
+
+ my $regexp='('.join('|', @{$config{wiki_file_prune_regexps}}).')';
+ $file =~ m/$regexp/;
+} #}}}
+
+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 (! $gettext_obj) {
+ $gettext_obj=eval q{
+ use Locale::gettext q{textdomain};
+ Locale::gettext->domain('ikiwiki')
+ };
+ if ($@) {
+ print STDERR "$@";
+ $gettext_obj=undef;
+ return shift;
+ }
+ }
+ return $gettext_obj->get(shift);
+ }
+ else {
+ return shift;
+ }
+} #}}}
+
+sub pagespec_merge ($$) { #{{{
+ my $a=shift;
+ my $b=shift;
+
+ return $a if $a eq $b;
+
+ # Support for old-style GlobLists.
+ if (is_globlist($a)) {
+ $a=globlist_to_pagespec($a);
+ }
+ if (is_globlist($b)) {
+ $b=globlist_to_pagespec($b);
+ }
+
+ return "($a) or ($b)";
+} #}}}
+
+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.
+ if (is_globlist($spec)) {
+ $spec=globlist_to_pagespec($spec);
+ }
+
+ # Convert spec to perl code.
+ my $code="";
+ while ($spec=~m/\s*(\!|\(|\)|\w+\([^\)]+\)|[^\s()]+)\s*/ig) {
+ my $word=$1;
+ if (lc $word eq "and") {
+ $code.=" &&";
+ }
+ elsif (lc $word eq "or") {
+ $code.=" ||";
+ }
+ elsif ($word eq "(" || $word eq ")" || $word eq "!") {
+ $code.=" ".$word;
+ }
+ elsif ($word =~ /^(\w+)\((.*)\)$/) {
+ if (exists $IkiWiki::PageSpec::{"match_$1"}) {
+ $code.="IkiWiki::PageSpec::match_$1(\$page, ".safequote($2).", \$from)";
+ }
+ else {
+ $code.=" 0";
+ }
+ }
+ else {
+ $code.=" IkiWiki::PageSpec::match_glob(\$page, ".safequote($word).", \$from)";
+ }
+ }
+
+ return $code;
+} #}}}
+
+sub pagespec_match ($$;$) { #{{{
+ my $page=shift;
+ my $spec=shift;
+ my $from=shift;
+
+ return eval pagespec_translate($spec);
+} #}}}
+
+package IkiWiki::PageSpec;
+
+sub match_glob ($$$) { #{{{
+ my $page=shift;
+ my $glob=shift;
+ my $from=shift;
+ if (! defined $from){
+ $from = "";
+ }
+
+ # relative matching
+ if ($glob =~ m!^\./!) {
+ $from=~s!/?[^/]+$!!;
+ $glob=~s!^\./!!;
+ $glob="$from/$glob" if length $from;
+ }
+
+ # turn glob into safe regexp
+ $glob=quotemeta($glob);
+ $glob=~s/\\\*/.*/g;
+ $glob=~s/\\\?/./g;
+
+ return $page=~/^$glob$/i;
+} #}}}
+
+sub match_link ($$$) { #{{{
+ my $page=shift;
+ my $link=lc(shift);
+ my $from=shift;
+ if (! defined $from){
+ $from = "";
+ }
+
+ # relative matching
+ if ($link =~ m!^\.! && defined $from) {
+ $from=~s!/?[^/]+$!!;
+ $link=~s!^\./!!;
+ $link="$from/$link" if length $from;
+ }
+
+ my $links = $IkiWiki::links{$page} or return undef;
+ return 0 unless @$links;
+ my $bestlink = IkiWiki::bestlink($from, $link);
+ return 0 unless length $bestlink;
+ foreach my $p (@$links) {
+ return 1 if $bestlink eq IkiWiki::bestlink($page, $p);
+ }
+ return 0;
+} #}}}
+
+sub match_backlink ($$$) { #{{{
+ match_link($_[1], $_[0], $_[3]);
+} #}}}
+
+sub match_created_before ($$$) { #{{{
+ my $page=shift;
+ my $testpage=shift;
+
+ if (exists $IkiWiki::pagectime{$testpage}) {
+ return $IkiWiki::pagectime{$page} < $IkiWiki::pagectime{$testpage};
+ }
+ else {
+ return 0;
+ }
+} #}}}
+
+sub match_created_after ($$$) { #{{{
+ my $page=shift;
+ my $testpage=shift;
+
+ if (exists $IkiWiki::pagectime{$testpage}) {
+ return $IkiWiki::pagectime{$page} > $IkiWiki::pagectime{$testpage};
+ }
+ else {
+ return 0;
+ }
+} #}}}
+
+sub match_creation_day ($$$) { #{{{
+ return ((gmtime($IkiWiki::pagectime{shift()}))[3] == shift);
+} #}}}
+
+sub match_creation_month ($$$) { #{{{
+ return ((gmtime($IkiWiki::pagectime{shift()}))[4] + 1 == shift);
+} #}}}
+
+sub match_creation_year ($$$) { #{{{
+ return ((gmtime($IkiWiki::pagectime{shift()}))[5] + 1900 == shift);