Merge remote branch 'smcv/ready/link-types'

master
Joey Hess 2010-04-06 22:50:19 -04:00
commit f6fd7639da
10 changed files with 228 additions and 31 deletions

View File

@ -14,7 +14,7 @@ use open qw{:utf8 :std};
use vars qw{%config %links %oldlinks %pagemtime %pagectime %pagecase use vars qw{%config %links %oldlinks %pagemtime %pagectime %pagecase
%pagestate %wikistate %renderedfiles %oldrenderedfiles %pagestate %wikistate %renderedfiles %oldrenderedfiles
%pagesources %destsources %depends %depends_simple %hooks %pagesources %destsources %depends %depends_simple %hooks
%forcerebuild %loaded_plugins}; %forcerebuild %loaded_plugins %typedlinks %oldtypedlinks};
use Exporter q{import}; use Exporter q{import};
our @EXPORT = qw(hook debug error template htmlpage deptype our @EXPORT = qw(hook debug error template htmlpage deptype
@ -24,7 +24,7 @@ our @EXPORT = qw(hook debug error template htmlpage deptype
add_underlay pagetitle titlepage linkpage newpagefile add_underlay pagetitle titlepage linkpage newpagefile
inject add_link inject add_link
%config %links %pagestate %wikistate %renderedfiles %config %links %pagestate %wikistate %renderedfiles
%pagesources %destsources); %pagesources %destsources %typedlinks);
our $VERSION = 3.00; # plugin interface version, next is ikiwiki version our $VERSION = 3.00; # plugin interface version, next is ikiwiki version
our $version='unknown'; # VERSION_AUTOREPLACE done by Makefile, DNE our $version='unknown'; # VERSION_AUTOREPLACE done by Makefile, DNE
our $installdir='/usr'; # INSTALLDIR_AUTOREPLACE done by Makefile, DNE our $installdir='/usr'; # INSTALLDIR_AUTOREPLACE done by Makefile, DNE
@ -1502,7 +1502,7 @@ sub loadindex () {
if (! $config{rebuild}) { if (! $config{rebuild}) {
%pagesources=%pagemtime=%oldlinks=%links=%depends= %pagesources=%pagemtime=%oldlinks=%links=%depends=
%destsources=%renderedfiles=%pagecase=%pagestate= %destsources=%renderedfiles=%pagecase=%pagestate=
%depends_simple=(); %depends_simple=%typedlinks=%oldtypedlinks=();
} }
my $in; my $in;
if (! open ($in, "<", "$config{wikistatedir}/indexdb")) { if (! open ($in, "<", "$config{wikistatedir}/indexdb")) {
@ -1568,6 +1568,14 @@ sub loadindex () {
if (exists $d->{state}) { if (exists $d->{state}) {
$pagestate{$page}=$d->{state}; $pagestate{$page}=$d->{state};
} }
if (exists $d->{typedlinks}) {
$typedlinks{$page}=$d->{typedlinks};
while (my ($type, $links) = each %{$typedlinks{$page}}) {
next unless %$links;
$oldtypedlinks{$page}{$type} = {%$links};
}
}
} }
$oldrenderedfiles{$page}=[@{$d->{dest}}]; $oldrenderedfiles{$page}=[@{$d->{dest}}];
} }
@ -1616,6 +1624,10 @@ sub saveindex () {
$index{page}{$src}{depends_simple} = $depends_simple{$page}; $index{page}{$src}{depends_simple} = $depends_simple{$page};
} }
if (exists $typedlinks{$page} && %{$typedlinks{$page}}) {
$index{page}{$src}{typedlinks} = $typedlinks{$page};
}
if (exists $pagestate{$page}) { if (exists $pagestate{$page}) {
foreach my $id (@hookids) { foreach my $id (@hookids) {
foreach my $key (keys %{$pagestate{$page}{$id}}) { foreach my $key (keys %{$pagestate{$page}{$id}}) {
@ -1925,12 +1937,17 @@ sub inject {
use warnings; use warnings;
} }
sub add_link ($$) { sub add_link ($$;$) {
my $page=shift; my $page=shift;
my $link=shift; my $link=shift;
my $type=shift;
push @{$links{$page}}, $link push @{$links{$page}}, $link
unless grep { $_ eq $link } @{$links{$page}}; unless grep { $_ eq $link } @{$links{$page}};
if (defined $type) {
$typedlinks{$page}{$type}{$link} = 1;
}
} }
sub pagespec_translate ($) { sub pagespec_translate ($) {
@ -2211,6 +2228,11 @@ sub match_link ($$;@) {
$link=derel($link, $params{location}); $link=derel($link, $params{location});
my $from=exists $params{location} ? $params{location} : ''; my $from=exists $params{location} ? $params{location} : '';
my $linktype=$params{linktype};
my $qualifier='';
if (defined $linktype) {
$qualifier=" with type $linktype";
}
my $links = $IkiWiki::links{$page}; my $links = $IkiWiki::links{$page};
return IkiWiki::FailReason->new("$page has no links", $page => $IkiWiki::DEPEND_LINKS, "" => 1) return IkiWiki::FailReason->new("$page has no links", $page => $IkiWiki::DEPEND_LINKS, "" => 1)
@ -2218,19 +2240,22 @@ sub match_link ($$;@) {
my $bestlink = IkiWiki::bestlink($from, $link); my $bestlink = IkiWiki::bestlink($from, $link);
foreach my $p (@{$links}) { foreach my $p (@{$links}) {
if (length $bestlink) { if (length $bestlink) {
return IkiWiki::SuccessReason->new("$page links to $link", $page => $IkiWiki::DEPEND_LINKS, "" => 1) if ((!defined $linktype || exists $IkiWiki::typedlinks{$page}{$linktype}{$p}) && $bestlink eq IkiWiki::bestlink($page, $p)) {
if $bestlink eq IkiWiki::bestlink($page, $p); return IkiWiki::SuccessReason->new("$page links to $link$qualifier", $page => $IkiWiki::DEPEND_LINKS, "" => 1)
}
} }
else { else {
return IkiWiki::SuccessReason->new("$page links to page $p matching $link", $page => $IkiWiki::DEPEND_LINKS, "" => 1) if ((!defined $linktype || exists $IkiWiki::typedlinks{$page}{$linktype}{$p}) && match_glob($p, $link, %params)) {
if match_glob($p, $link, %params); return IkiWiki::SuccessReason->new("$page links to page $p$qualifier, matching $link", $page => $IkiWiki::DEPEND_LINKS, "" => 1)
}
my ($p_rel)=$p=~/^\/?(.*)/; my ($p_rel)=$p=~/^\/?(.*)/;
$link=~s/^\///; $link=~s/^\///;
return IkiWiki::SuccessReason->new("$page links to page $p_rel matching $link", $page => $IkiWiki::DEPEND_LINKS, "" => 1) if ((!defined $linktype || exists $IkiWiki::typedlinks{$page}{$linktype}{$p_rel}) && match_glob($p_rel, $link, %params)) {
if match_glob($p_rel, $link, %params); return IkiWiki::SuccessReason->new("$page links to page $p_rel$qualifier, matching $link", $page => $IkiWiki::DEPEND_LINKS, "" => 1)
} }
} }
return IkiWiki::FailReason->new("$page does not link to $link", $page => $IkiWiki::DEPEND_LINKS, "" => 1); }
return IkiWiki::FailReason->new("$page does not link to $link$qualifier", $page => $IkiWiki::DEPEND_LINKS, "" => 1);
} }
sub match_backlink ($$;@) { sub match_backlink ($$;@) {

View File

@ -6,8 +6,6 @@ use warnings;
use strict; use strict;
use IkiWiki 3.00; use IkiWiki 3.00;
my %tags;
sub import { sub import {
hook(type => "getopt", id => "tag", call => \&getopt); hook(type => "getopt", id => "tag", call => \&getopt);
hook(type => "getsetup", id => "tag", call => \&getsetup); hook(type => "getsetup", id => "tag", call => \&getsetup);
@ -71,9 +69,8 @@ sub preprocess_tag (@) {
foreach my $tag (keys %params) { foreach my $tag (keys %params) {
$tag=linkpage($tag); $tag=linkpage($tag);
$tags{$page}{$tag}=1;
# hidden WikiLink # hidden WikiLink
add_link($page, tagpage($tag)); add_link($page, tagpage($tag), 'tag');
} }
return ""; return "";
@ -87,15 +84,13 @@ sub preprocess_taglink (@) {
return join(" ", map { return join(" ", map {
if (/(.*)\|(.*)/) { if (/(.*)\|(.*)/) {
my $tag=linkpage($2); my $tag=linkpage($2);
$tags{$params{page}}{$tag}=1; add_link($params{page}, tagpage($tag), 'tag');
add_link($params{page}, tagpage($tag));
return taglink($params{page}, $params{destpage}, $tag, return taglink($params{page}, $params{destpage}, $tag,
linktext => pagetitle($1)); linktext => pagetitle($1));
} }
else { else {
my $tag=linkpage($_); my $tag=linkpage($_);
$tags{$params{page}}{$tag}=1; add_link($params{page}, tagpage($tag), 'tag');
add_link($params{page}, tagpage($tag));
return taglink($params{page}, $params{destpage}, $tag); return taglink($params{page}, $params{destpage}, $tag);
} }
} }
@ -110,17 +105,19 @@ sub pagetemplate (@) {
my $destpage=$params{destpage}; my $destpage=$params{destpage};
my $template=$params{template}; my $template=$params{template};
my $tags = $typedlinks{$page}{tag};
$template->param(tags => [ $template->param(tags => [
map { map {
link => taglink($page, $destpage, $_, rel => "tag") link => taglink($page, $destpage, $_, rel => "tag")
}, sort keys %{$tags{$page}} }, sort keys %$tags
]) if exists $tags{$page} && %{$tags{$page}} && $template->query(name => "tags"); ]) if defined $tags && %$tags && $template->query(name => "tags");
if ($template->query(name => "categories")) { if ($template->query(name => "categories")) {
# It's an rss/atom template. Add any categories. # It's an rss/atom template. Add any categories.
if (exists $tags{$page} && %{$tags{$page}}) { if (defined $tags && %$tags) {
$template->param(categories => [map { category => $_ }, $template->param(categories => [map { category => $_ },
sort keys %{$tags{$page}}]); sort keys %$tags]);
} }
} }
} }
@ -128,9 +125,7 @@ sub pagetemplate (@) {
package IkiWiki::PageSpec; package IkiWiki::PageSpec;
sub match_tagged ($$;@) { sub match_tagged ($$;@) {
my $page = shift; return match_link($_[0], IkiWiki::Plugin::tag::tagpage($_[1]), linktype => 'tag');
my $glob = shift;
return match_link($page, IkiWiki::Plugin::tag::tagpage($glob));
} }
1 1

View File

@ -167,6 +167,7 @@ sub scan ($) {
else { else {
$links{$page}=[]; $links{$page}=[];
} }
delete $typedlinks{$page};
run_hooks(scan => sub { run_hooks(scan => sub {
shift->( shift->(
@ -398,6 +399,7 @@ sub find_del_files ($) {
push @del, $pagesources{$page}; push @del, $pagesources{$page};
} }
$links{$page}=[]; $links{$page}=[];
delete $typedlinks{$page};
$renderedfiles{$page}=[]; $renderedfiles{$page}=[];
$pagemtime{$page}=0; $pagemtime{$page}=0;
} }
@ -499,6 +501,29 @@ sub remove_unrendered () {
} }
} }
sub link_types_changed ($$) {
# each is of the form { type => { link => 1 } }
my $new = shift;
my $old = shift;
return 0 if !defined $new && !defined $old;
return 1 if !defined $new || !defined $old;
while (my ($type, $links) = each %$new) {
foreach my $link (keys %$links) {
return 1 unless exists $old->{$type}{$link};
}
}
while (my ($type, $links) = each %$old) {
foreach my $link (keys %$links) {
return 1 unless exists $new->{$type}{$link};
}
}
return 0;
}
sub calculate_changed_links ($$$) { sub calculate_changed_links ($$$) {
my ($changed, $del, $oldlink_targets)=@_; my ($changed, $del, $oldlink_targets)=@_;
@ -525,6 +550,14 @@ sub calculate_changed_links ($$$) {
} }
$linkchangers{lc($page)}=1; $linkchangers{lc($page)}=1;
} }
# we currently assume that changing the type of a link doesn't
# change backlinks
if (!exists $linkchangers{lc($page)}) {
if (link_types_changed($typedlinks{$page}, $oldtypedlinks{$page})) {
$linkchangers{lc($page)}=1;
}
}
} }
return \%backlinkchanged, \%linkchangers; return \%backlinkchanged, \%linkchangers;

10
debian/NEWS vendored
View File

@ -1,3 +1,13 @@
ikiwiki (3.UNRELEASED) UNRELEASED; urgency=low
Starting from this version, the tagged(x) pagespec only matches links that
were set up by the [[!tag]] or [[!taglink]] directives. To make this change,
all wikis need to be rebuilt on upgrade to this version. If you listed your
wiki in /etc/ikiwiki/wikilist this will be done automatically when the
Debian package is upgraded. Or use ikiwiki-mass-rebuild to force a rebuild.
-- Simon McVittie <smcv@debian.org> Tue, 06 Apr 2010 20:53:07 +0100
ikiwiki (3.20091017) unstable; urgency=low ikiwiki (3.20091017) unstable; urgency=low
To take advantage of significant performance improvements, all To take advantage of significant performance improvements, all

2
debian/postinst vendored
View File

@ -4,7 +4,7 @@ set -e
# Change this when some incompatible change is made that requires # Change this when some incompatible change is made that requires
# rebuilding all wikis. # rebuilding all wikis.
firstcompat=3.20091010 firstcompat=3.20100401
if [ "$1" = configure ] && \ if [ "$1" = configure ] && \
dpkg --compare-versions "$2" lt "$firstcompat"; then dpkg --compare-versions "$2" lt "$firstcompat"; then

View File

@ -28,6 +28,8 @@ rationale on this, or what am I doing wrong, and how to achieve what I want?
>> is valid. [[todo/matching_different_kinds_of_links]] is probably >> is valid. [[todo/matching_different_kinds_of_links]] is probably
>> how it will eventually be solved. --[[Joey]] >> how it will eventually be solved. --[[Joey]]
>>> [[Done]]: `tagged` no longer matches other wikilinks. --[[smcv]]
> And this is an illustration why a clean work-around (without changing the software) is not possible: while thinking about [[todo/matching_different_kinds_of_links]], I thought one could work around the problem by simply explicitly including the kind of the relation into the link target (like the tagbase in tags), and by having a separate page without the "tagbase" to link to when one wants simply to refer to the tag without tagging. But this won't work: one has to at least once refer to the real tag page if one wants to talk about it, and this reference will count as tagging (unwanted). --Ivan Z. > And this is an illustration why a clean work-around (without changing the software) is not possible: while thinking about [[todo/matching_different_kinds_of_links]], I thought one could work around the problem by simply explicitly including the kind of the relation into the link target (like the tagbase in tags), and by having a separate page without the "tagbase" to link to when one wants simply to refer to the tag without tagging. But this won't work: one has to at least once refer to the real tag page if one wants to talk about it, and this reference will count as tagging (unwanted). --Ivan Z.
> But well, perhaps there is a workaround without introducing different kinds of links. One could modify the [[tag plugin|plugins/tag]] so that it adds 2 links to a page: for tagging -- `tagbase/TAG`, and for navigation -- `tagdescription/TAG` (displayed at the bottom). Then the `tagdescription/TAG` page would hold whatever list one wishes (with `tagged(TAG)` in the pagespec), and whenever one wants to merely refer to the tag, one should link to `tagdescription/TAG`--this link won't count as tagging. So, `tagbase/TAG` would become completely auxiliary (internal) link targets for ikiwiki, the users would edit or link to only `tagdescription/TAG`. --Ivan Z. > But well, perhaps there is a workaround without introducing different kinds of links. One could modify the [[tag plugin|plugins/tag]] so that it adds 2 links to a page: for tagging -- `tagbase/TAG`, and for navigation -- `tagdescription/TAG` (displayed at the bottom). Then the `tagdescription/TAG` page would hold whatever list one wishes (with `tagged(TAG)` in the pagespec), and whenever one wants to merely refer to the tag, one should link to `tagdescription/TAG`--this link won't count as tagging. So, `tagbase/TAG` would become completely auxiliary (internal) link targets for ikiwiki, the users would edit or link to only `tagdescription/TAG`. --Ivan Z.

View File

@ -633,6 +633,22 @@ reference. Do not modify this hash directly; call `add_link()`.
$links{"foo"} = ["bar", "baz"]; $links{"foo"} = ["bar", "baz"];
### `%typedlinks`
The `%typedlinks` hash records links of specific types. Do not modify this
hash directly; call `add_link()`. The keys are page names, and the values
are hash references. In each page's hash reference, the keys are link types
defined by plugins, and the values are hash references with link targets
as keys, and 1 as a dummy value, something like this:
$typedlinks{"foo"} = {
tag => { short_word => 1, metasyntactic_variable => 1 },
next_page => { bar => 1 },
};
Ordinary [[WikiLinks|ikiwiki/WikiLink]] appear in `%links`, but not in
`%typedlinks`.
### `%pagesources` ### `%pagesources`
The `%pagesources` has can be used to look up the source filename The `%pagesources` has can be used to look up the source filename
@ -939,11 +955,14 @@ Optionally, a third parameter can be passed, to specify the preferred
filename of the page. For example, `targetpage("foo", "rss", "feed")` filename of the page. For example, `targetpage("foo", "rss", "feed")`
will yield something like `foo/feed.rss`. will yield something like `foo/feed.rss`.
### `add_link($$)` ### `add_link($$;$)`
This adds a link to `%links`, ensuring that duplicate links are not This adds a link to `%links`, ensuring that duplicate links are not
added. Pass it the page that contains the link, and the link text. added. Pass it the page that contains the link, and the link text.
An optional third parameter sets the link type (`undef` produces an ordinary
[[ikiwiki/WikiLink]]).
## Miscellaneous ## Miscellaneous
### Internal use pages ### Internal use pages

View File

@ -0,0 +1,58 @@
#!/usr/bin/perl
package IkiWiki;
use warnings;
use strict;
use Test::More tests => 5;
BEGIN { use_ok("IkiWiki"); }
BEGIN { use_ok("IkiWiki::Render"); }
%config=IkiWiki::defaultconfig();
$config{srcdir}=$config{destdir}="/dev/null";
%oldrenderedfiles=%pagectime=();
%pagesources=%pagemtime=%oldlinks=%links=%depends=%typedlinks=%oldtypedlinks=
%destsources=%renderedfiles=%pagecase=%pagestate=();
IkiWiki::checkconfig();
foreach my $page (qw(tags/a tags/b Reorder Add Remove TypeAdd TypeRemove)) {
$pagesources{$page} = "$page.mdwn";
$pagemtime{$page} = $pagectime{$page} = 1000000;
}
$oldlinks{Reorder} = [qw{tags/a tags/b}];
$links{Reorder} = [qw{tags/b tags/a}];
$oldlinks{Add} = [qw{tags/b}];
$links{Add} = [qw{tags/a tags/b}];
$oldlinks{Remove} = [qw{tags/a}];
$links{Remove} = [];
$oldlinks{TypeAdd} = [qw{tags/a tags/b}];
$links{TypeAdd} = [qw{tags/a tags/b}];
# This causes TypeAdd to be rebuilt, but isn't a backlink change, so it doesn't
# cause tags/b to be rebuilt.
$oldtypedlinks{TypeAdd}{tag} = { "tags/a" => 1 };
$typedlinks{TypeAdd}{tag} = { "tags/a" => 1, "tags/b" => 1 };
$oldlinks{TypeRemove} = [qw{tags/a tags/b}];
$links{TypeRemove} = [qw{tags/a tags/b}];
# This causes TypeRemove to be rebuilt, but isn't a backlink change, so it
# doesn't cause tags/b to be rebuilt.
$oldtypedlinks{TypeRemove}{tag} = { "tags/a" => 1 };
$typedlinks{TypeRemove}{tag} = { "tags/a" => 1, "tags/b" => 1 };
my $oldlink_targets = calculate_old_links([keys %pagesources], []);
is_deeply($oldlink_targets, {
Reorder => { "tags/a" => "tags/a", "tags/b" => "tags/b" },
Add => { "tags/b" => "tags/b" },
Remove => { "tags/a" => "tags/a" },
TypeAdd => { "tags/a" => "tags/a", "tags/b" => "tags/b" },
TypeRemove => { "tags/a" => "tags/a", "tags/b" => "tags/b" },
});
my ($backlinkchanged, $linkchangers) = calculate_changed_links([keys %pagesources], [], $oldlink_targets);
is_deeply($backlinkchanged, { "tags/a" => 1 });
is_deeply($linkchangers, { add => 1, remove => 1, typeadd => 1, typeremove => 1 });

View File

@ -4,7 +4,7 @@ use strict;
use IkiWiki; use IkiWiki;
package IkiWiki; # use internal variables package IkiWiki; # use internal variables
use Test::More tests => 27; use Test::More tests => 31;
$config{wikistatedir}="/tmp/ikiwiki-test.$$"; $config{wikistatedir}="/tmp/ikiwiki-test.$$";
system "rm -rf $config{wikistatedir}"; system "rm -rf $config{wikistatedir}";
@ -31,6 +31,7 @@ $renderedfiles{"bar"}=["bar.html", "bar.rss", "sparkline-foo.gif"];
$renderedfiles{"bar.png"}=["bar.png"]; $renderedfiles{"bar.png"}=["bar.png"];
$links{"Foo"}=["bar.png"]; $links{"Foo"}=["bar.png"];
$links{"bar"}=["Foo", "new-page"]; $links{"bar"}=["Foo", "new-page"];
$typedlinks{"bar"}={tag => {"Foo" => 1}};
$links{"bar.png"}=[]; $links{"bar.png"}=[];
$depends{"Foo"}={}; $depends{"Foo"}={};
$depends{"bar"}={"foo*" => 1}; $depends{"bar"}={"foo*" => 1};
@ -45,7 +46,7 @@ ok(-s "$config{wikistatedir}/indexdb", "index file created");
# Clear state. # Clear state.
%oldrenderedfiles=%pagectime=(); %oldrenderedfiles=%pagectime=();
%pagesources=%pagemtime=%oldlinks=%links=%depends= %pagesources=%pagemtime=%oldlinks=%links=%depends=%typedlinks=%oldtypedlinks=
%destsources=%renderedfiles=%pagecase=%pagestate=(); %destsources=%renderedfiles=%pagecase=%pagestate=();
ok(loadindex(), "load index"); ok(loadindex(), "load index");
@ -104,10 +105,16 @@ is_deeply(\%destsources, {
"sparkline-foo.gif" => "bar", "sparkline-foo.gif" => "bar",
"bar.png" => "bar.png", "bar.png" => "bar.png",
}, "%destsources generated correctly"); }, "%destsources generated correctly");
is_deeply(\%typedlinks, {
bar => {tag => {"Foo" => 1}},
}, "%typedlinks loaded correctly");
is_deeply(\%oldtypedlinks, {
bar => {tag => {"Foo" => 1}},
}, "%oldtypedlinks loaded correctly");
# Clear state. # Clear state.
%oldrenderedfiles=%pagectime=(); %oldrenderedfiles=%pagectime=();
%pagesources=%pagemtime=%oldlinks=%links=%depends= %pagesources=%pagemtime=%oldlinks=%links=%depends=%typedlinks=%oldtypedlinks=
%destsources=%renderedfiles=%pagecase=%pagestate=(); %destsources=%renderedfiles=%pagecase=%pagestate=();
# When state is loaded for a wiki rebuild, only ctime and oldrenderedfiles # When state is loaded for a wiki rebuild, only ctime and oldrenderedfiles
@ -140,5 +147,9 @@ is_deeply(\%pagecase, {
}, "%pagecase generated correctly"); }, "%pagecase generated correctly");
is_deeply(\%destsources, { is_deeply(\%destsources, {
}, "%destsources generated correctly"); }, "%destsources generated correctly");
is_deeply(\%typedlinks, {
}, "%typedlinks cleared correctly");
is_deeply(\%oldtypedlinks, {
}, "%oldtypedlinks cleared correctly");
system "rm -rf $config{wikistatedir}"; system "rm -rf $config{wikistatedir}";

44
t/tag.t 100755
View File

@ -0,0 +1,44 @@
#!/usr/bin/perl
package IkiWiki;
use warnings;
use strict;
use Test::More tests => 10;
BEGIN { use_ok("IkiWiki"); }
BEGIN { use_ok("IkiWiki::Plugin::tag"); }
ok(! system("rm -rf t/tmp; mkdir t/tmp"));
$config{userdir} = "users";
$config{tagbase} = "tags";
%oldrenderedfiles=%pagectime=();
%pagesources=%pagemtime=%oldlinks=%links=%depends=%typedlinks=%oldtypedlinks=
%destsources=%renderedfiles=%pagecase=%pagestate=();
foreach my $page (qw(tags/numbers tags/letters one two alpha beta)) {
$pagesources{$page} = "$page.mdwn";
$pagemtime{$page} = $pagectime{$page} = 1000000;
}
$links{one}=[qw(tags/numbers alpha tags/letters)];
$links{two}=[qw(tags/numbers)];
$links{alpha}=[qw(tags/letters one)];
$links{beta}=[qw(tags/letters)];
$typedlinks{one}={tag => {"tags/numbers" => 1 }};
$typedlinks{two}={tag => {"tags/numbers" => 1 }};
$typedlinks{alpha}={tag => {"tags/letters" => 1 }};
$typedlinks{beta}={tag => {"tags/letters" => 1 }};
ok(pagespec_match("one", "tagged(numbers)"));
ok(!pagespec_match("two", "tagged(alpha)"));
ok(pagespec_match("one", "link(tags/numbers)"));
ok(pagespec_match("one", "link(alpha)"));
ok(pagespec_match("one", "typedlink(tag tags/numbers)"));
ok(!pagespec_match("one", "typedlink(tag tags/letters)"));
# invalid syntax
ok(pagespec_match("one", "typedlink(tag)")->isa("IkiWiki::ErrorReason"));
1;