+ my $spec=join(" or ", @spec);
+ if (@skip) {
+ my $skip=join(" and ", @skip);
+ if (length $spec) {
+ $spec="$skip and ($spec)";
+ }
+ else {
+ $spec=$skip;
+ }
+ }
+ 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 pagespec_merge ($$) { #{{{
+ my $a=shift;
+ my $b=shift;
+
+ # 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 =~ /^(link|backlink|created_before|created_after|creation_month|creation_year|creation_day)\((.+)\)$/) {
+ $code.=" match_$1(\$page, ".safequote($2).")";
+ }
+ else {
+ $code.=" match_glob(\$page, ".safequote($word).")";
+ }
+ }
+
+ return $code;
+} #}}}
+
+sub pagespec_match ($$) { #{{{
+ my $page=shift;
+ my $spec=shift;
+
+ return eval pagespec_translate($spec);
+} #}}}
+
+sub match_glob ($$) { #{{{
+ my $page=shift;
+ my $glob=shift;
+
+ # 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=shift;
+
+ my $links = $links{$page} or return undef;
+ foreach my $p (@$links) {
+ return 1 if lc $p eq $link;