From 2b7a003794537ebf8e02f122cc2743834b47a396 Mon Sep 17 00:00:00 2001 From: Amitai Schlair Date: Thu, 13 Aug 2009 16:56:26 -0400 Subject: Add my CVS plugin and related patches. --- t/file_pruned.t | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 't') diff --git a/t/file_pruned.t b/t/file_pruned.t index 7d46c4b7f..a05ad548f 100755 --- a/t/file_pruned.t +++ b/t/file_pruned.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 24; +use Test::More tests => 27; BEGIN { use_ok("IkiWiki"); } @@ -9,6 +9,9 @@ BEGIN { use_ok("IkiWiki"); } ok(IkiWiki::file_pruned("src/.ikiwiki/", "src")); ok(IkiWiki::file_pruned("src/.ikiwiki/index", "src")); +ok(IkiWiki::file_pruned("src/CVS", "src")); +ok(IkiWiki::file_pruned("src/subdir/CVS", "src")); +ok(IkiWiki::file_pruned("src/subdir/CVS/foo", "src")); ok(IkiWiki::file_pruned("src/.svn", "src")); ok(IkiWiki::file_pruned("src/subdir/.svn", "src")); ok(IkiWiki::file_pruned("src/subdir/.svn/foo", "src")); -- cgit v1.2.3 From fe4f176f725b069ac74561600bba982c2d9ad607 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Thu, 18 Jun 2009 15:54:53 +0100 Subject: Optimize the dependencies list On a large wiki you can spend a lot of time reading through large lists of dependencies to see whether files need to be rebuilt (album, with its one-page-per-photo arrangement, suffers particularly badly from this). The dependency list is currently a single pagespec, but it's not used like a normal pagespec - in practice, it's a list of pagespecs joined with the "or" operator. Accordingly, change it to be stored as a list of pagespecs. On a wiki with many tagged photo albums, this reduces the time to refresh after `touch tags/*.mdwn` from about 31 to 25 seconds. Getting the benefit of this change on an existing wiki requires a rebuild. --- IkiWiki.pm | 19 +++++++++++++------ IkiWiki/Render.pm | 24 +++++++++++++----------- ikiwiki-transition | 2 +- t/index.t | 12 ++++++------ 4 files changed, 33 insertions(+), 24 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 43ffb1fd8..3e94c8a25 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -1507,8 +1507,11 @@ sub loadindex () { $links{$page}=$d->{links}; $oldlinks{$page}=[@{$d->{links}}]; } - if (exists $d->{depends}) { - $depends{$page}=$d->{depends}; + if (exists $d->{dependslist}) { + $depends{$page}=$d->{dependslist}; + } + elsif (exists $d->{depends}) { + $depends{$page}=[$d->{depends}]; } if (exists $d->{state}) { $pagestate{$page}=$d->{state}; @@ -1554,7 +1557,8 @@ sub saveindex () { }; if (exists $depends{$page}) { - $index{page}{$src}{depends} = $depends{$page}; + $index{page}{$src}{depends} = join(" or ", @{$depends{$page}}); + $index{page}{$src}{dependslist} = $depends{$page}; } if (exists $pagestate{$page}) { @@ -1724,14 +1728,17 @@ sub rcs_receive () { sub add_depends ($$) { my $page=shift; my $pagespec=shift; - + return unless pagespec_valid($pagespec); if (! exists $depends{$page}) { - $depends{$page}=$pagespec; + $depends{$page}=[$pagespec]; } else { - $depends{$page}=pagespec_merge($depends{$page}, $pagespec); + foreach my $p (@{$depends{$page}}) { + return 1 if $p eq $pagespec; + } + push @{$depends{$page}}, $pagespec; } return 1; diff --git a/IkiWiki/Render.pm b/IkiWiki/Render.pm index 578142d2e..08d484847 100644 --- a/IkiWiki/Render.pm +++ b/IkiWiki/Render.pm @@ -455,20 +455,22 @@ sub refresh () { my @changed=(keys %rendered, @del); # rebuild dependant pages - foreach my $f (@$files) { + F: foreach my $f (@$files) { next if $rendered{$f}; my $p=pagename($f); if (exists $depends{$p}) { - # only consider internal files - # if the page explicitly depends on such files - foreach my $file (@changed, $depends{$p}=~/internal\(/ ? @internal : ()) { - next if $f eq $file; - my $page=pagename($file); - if (pagespec_match($page, $depends{$p}, location => $p)) { - debug(sprintf(gettext("building %s, which depends on %s"), $f, $page)); - render($f); - $rendered{$f}=1; - last; + foreach my $d (@{$depends{$p}}) { + # only consider internal files + # if the page explicitly depends on such files + foreach my $file (@changed, $d=~/internal\(/ ? @internal : ()) { + next if $f eq $file; + my $page=pagename($file); + if (pagespec_match($page, $d, location => $p)) { + debug(sprintf(gettext("building %s, which depends on %s"), $f, $page)); + render($f); + $rendered{$f}=1; + next F; + } } } } diff --git a/ikiwiki-transition b/ikiwiki-transition index 398b1a3c8..60cea3d54 100755 --- a/ikiwiki-transition +++ b/ikiwiki-transition @@ -299,7 +299,7 @@ sub oldloadindex { $pagemtime{$page}=$items{mtime}[0]; $oldlinks{$page}=[@{$items{link}}]; $links{$page}=[@{$items{link}}]; - $depends{$page}=$items{depends}[0] if exists $items{depends}; + $depends{$page}=[$items{depends}[0]] if exists $items{depends}; $destsources{$_}=$page foreach @{$items{dest}}; $renderedfiles{$page}=[@{$items{dest}}]; $pagecase{lc $page}=$page; diff --git a/t/index.t b/t/index.t index e79609902..107dac9d0 100755 --- a/t/index.t +++ b/t/index.t @@ -32,9 +32,9 @@ $renderedfiles{"bar.png"}=["bar.png"]; $links{"Foo"}=["bar.png"]; $links{"bar"}=["Foo", "new-page"]; $links{"bar.png"}=[]; -$depends{"Foo"}=""; -$depends{"bar"}="foo*"; -$depends{"bar.png"}=""; +$depends{"Foo"}=[]; +$depends{"bar"}=["foo*"]; +$depends{"bar.png"}=[]; $pagestate{"bar"}{meta}{title}="a page about bar"; $pagestate{"bar"}{meta}{moo}="mooooo"; # only loaded plugins save state, so this should not be saved out @@ -80,9 +80,9 @@ is_deeply(\%links, { "bar.png" => [], }, "%links loaded correctly"); is_deeply(\%depends, { - Foo => "", - bar => "foo*", - "bar.png" => "", + Foo => [], + bar => ["foo*"], + "bar.png" => [], }, "%depends loaded correctly"); is_deeply(\%pagestate, { bar => { -- cgit v1.2.3 From 7227c2debfeef94b35f7d81f42900aa01820caa3 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Mon, 24 Aug 2009 23:01:42 +0100 Subject: Use a hash to de-duplicate dependencies --- IkiWiki.pm | 19 ++++++------------- IkiWiki/Render.pm | 2 +- ikiwiki-transition | 2 +- t/index.t | 12 ++++++------ 4 files changed, 14 insertions(+), 21 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 734b167cf..21a74adce 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -1508,10 +1508,12 @@ sub loadindex () { $oldlinks{$page}=[@{$d->{links}}]; } if (exists $d->{dependslist}) { - $depends{$page}=$d->{dependslist}; + $depends{$page}={ + map { $_ => 1 } @{$d->{dependslist}} + }; } elsif (exists $d->{depends}) { - $depends{$page}=[$d->{depends}]; + $depends{$page}={$d->{depends} => 1}; } if (exists $d->{state}) { $pagestate{$page}=$d->{state}; @@ -1557,7 +1559,7 @@ sub saveindex () { }; if (exists $depends{$page}) { - $index{page}{$src}{dependslist} = $depends{$page}; + $index{page}{$src}{dependslist} = [ keys %{$depends{$page}} ]; } if (exists $pagestate{$page}) { @@ -1730,16 +1732,7 @@ sub add_depends ($$) { return unless pagespec_valid($pagespec); - if (! exists $depends{$page}) { - $depends{$page}=[$pagespec]; - } - else { - foreach my $p (@{$depends{$page}}) { - return 1 if $p eq $pagespec; - } - push @{$depends{$page}}, $pagespec; - } - + $depends{$page}{$pagespec} = 1; return 1; } diff --git a/IkiWiki/Render.pm b/IkiWiki/Render.pm index 08d484847..b9f1d7754 100644 --- a/IkiWiki/Render.pm +++ b/IkiWiki/Render.pm @@ -459,7 +459,7 @@ sub refresh () { next if $rendered{$f}; my $p=pagename($f); if (exists $depends{$p}) { - foreach my $d (@{$depends{$p}}) { + foreach my $d (keys %{$depends{$p}}) { # only consider internal files # if the page explicitly depends on such files foreach my $file (@changed, $d=~/internal\(/ ? @internal : ()) { diff --git a/ikiwiki-transition b/ikiwiki-transition index 60cea3d54..795ab31cb 100755 --- a/ikiwiki-transition +++ b/ikiwiki-transition @@ -299,7 +299,7 @@ sub oldloadindex { $pagemtime{$page}=$items{mtime}[0]; $oldlinks{$page}=[@{$items{link}}]; $links{$page}=[@{$items{link}}]; - $depends{$page}=[$items{depends}[0]] if exists $items{depends}; + $depends{$page}={ $items{depends}[0] => 1 } if exists $items{depends}; $destsources{$_}=$page foreach @{$items{dest}}; $renderedfiles{$page}=[@{$items{dest}}]; $pagecase{lc $page}=$page; diff --git a/t/index.t b/t/index.t index 107dac9d0..2f23524a7 100755 --- a/t/index.t +++ b/t/index.t @@ -32,9 +32,9 @@ $renderedfiles{"bar.png"}=["bar.png"]; $links{"Foo"}=["bar.png"]; $links{"bar"}=["Foo", "new-page"]; $links{"bar.png"}=[]; -$depends{"Foo"}=[]; -$depends{"bar"}=["foo*"]; -$depends{"bar.png"}=[]; +$depends{"Foo"}={}; +$depends{"bar"}={"foo*" => 1}; +$depends{"bar.png"}={}; $pagestate{"bar"}{meta}{title}="a page about bar"; $pagestate{"bar"}{meta}{moo}="mooooo"; # only loaded plugins save state, so this should not be saved out @@ -80,9 +80,9 @@ is_deeply(\%links, { "bar.png" => [], }, "%links loaded correctly"); is_deeply(\%depends, { - Foo => [], - bar => ["foo*"], - "bar.png" => [], + Foo => {}, + bar => {"foo*" => 1}, + "bar.png" => {}, }, "%depends loaded correctly"); is_deeply(\%pagestate, { bar => { -- cgit v1.2.3 From 1ea8580a5f912c305f8ea89431b39033522c657e Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Sat, 25 Jul 2009 20:04:04 +0100 Subject: Remove now-unused function pagespec_merge Now that dependencies are a list of pagespecs with an implicit "or" operation, there's no need to try to merge pagespecs under normal use. ikiwiki-transition contains the only use of the function, so move it there rather than deleting it entirely (it's used to concatenate all admins' lists of locked pages). --- IkiWiki.pm | 8 -------- ikiwiki-transition | 10 ++++++++++ t/pagespec_merge.t | 44 -------------------------------------------- 3 files changed, 10 insertions(+), 52 deletions(-) delete mode 100755 t/pagespec_merge.t (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 8a8695768..06a0b7a2c 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -1833,14 +1833,6 @@ sub add_link ($$) { unless grep { $_ eq $link } @{$links{$page}}; } -sub pagespec_merge ($$) { - my $a=shift; - my $b=shift; - - return $a if $a eq $b; - return "($a) or ($b)"; -} - sub pagespec_translate ($) { my $spec=shift; diff --git a/ikiwiki-transition b/ikiwiki-transition index 795ab31cb..8a20cf655 100755 --- a/ikiwiki-transition +++ b/ikiwiki-transition @@ -335,4 +335,14 @@ sub get_banned_users () { return @ret; } +# Used to be in IkiWiki, but only used here (to migrate admin prefs into the +# setup file) now. +sub pagespec_merge ($$) { + my $a=shift; + my $b=shift; + + return $a if $a eq $b; + return "($a) or ($b)"; +} + 1 diff --git a/t/pagespec_merge.t b/t/pagespec_merge.t deleted file mode 100755 index 9e38d5761..000000000 --- a/t/pagespec_merge.t +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/perl -use warnings; -use strict; -use Test::More tests => 25; - -BEGIN { use_ok("IkiWiki"); } - -sub same { - my $a=shift; - my $b=shift; - my $match=shift; - - my $imatch=(pagespec_match($match, $a) || - pagespec_match($match, $b)); - my $cmatch=pagespec_match($match, IkiWiki::pagespec_merge($a, $b)); - - return $imatch == $cmatch; -} - -ok(same("foo", "bar", "foo"), "basic match 1"); -ok(same("foo", "bar", "bar"), "basic match 2"); -ok(same("foo", "bar", "foobar"), "basic failed match"); -ok(same("foo", "!bar", "foo"), "basic match with inversion"); -ok(same("foo", "!bar", "bar"), "basic failed match with inversion"); -ok(same("!foo", "bar", "foo"), "basic failed match with inversion 2"); -ok(same("!foo", "bar", "bar"), "basic match with inversion 2"); -ok(same("!foo", "!bar", "foo"), "double inversion failed match"); -ok(same("!foo", "!bar", "bar"), "double inversion failed match 2"); -ok(same("*", "!bar", "foo"), "glob+inversion match"); -ok(same("*", "!bar", "bar"), "matching glob and matching inversion"); -ok(same("* and !foo", "!bar", "bar"), "matching glob and matching inversion"); -ok(same("* and !foo", "!bar", "foo"), "matching glob with matching inversion and non-matching inversion"); -ok(same("* and !foo", "!foo", "foo"), "matching glob with matching inversion and matching inversion"); -ok(same("b??", "!b??", "bar"), "matching glob and matching inverted glob"); -ok(same("f?? !f??", "!bar", "bar"), "matching glob and matching inverted glob"); -ok(same("b??", "!b?z", "bar"), "matching glob and non-matching inverted glob"); -ok(same("f?? !f?z", "!bar", "bar"), "matching glob and non-matching inverted glob"); -ok(same("!foo bar baz", "!bar", "bar"), "matching list and matching inversion"); -ok(pagespec_match("foo/Discussion", - IkiWiki::pagespec_merge("* and !*/Discussion", "*/Discussion")), "should match"); -ok(same("* and !*/Discussion", "*/Discussion", "foo/Discussion"), "Discussion merge 1"); -ok(same("*/Discussion", "* and !*/Discussion", "foo/Discussion"), "Discussion merge 2"); -ok(same("*/Discussion !*/bar", "*/bar !*/Discussion", "foo/Discussion"), "bidirectional merge 1"); -ok(same("*/Discussion !*/bar", "*/bar !*/Discussion", "foo/bar"), "bidirectional merge 2"); -- cgit v1.2.3 From 12bbc6c919ca5333a69715e5909263c4e9e4e514 Mon Sep 17 00:00:00 2001 From: Amitai Schlair Date: Wed, 9 Sep 2009 23:09:26 -0400 Subject: Add automated tests, modeled after svn's. Note the chdir() calls; perhaps cvs.pm should be doing pushd/popd in case the caller expects its working directory to be left alone. --- t/cvs.t | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100755 t/cvs.t (limited to 't') diff --git a/t/cvs.t b/t/cvs.t new file mode 100755 index 000000000..c3612ef8c --- /dev/null +++ b/t/cvs.t @@ -0,0 +1,76 @@ +#!/usr/bin/perl +use warnings; +use strict; +my $dir; +BEGIN { + $dir="/tmp/ikiwiki-test-cvs.$$"; + my $cvs=`which cvs`; + chomp $cvs; + if (! -x $cvs || ! mkdir($dir)) { + eval q{ + use Test::More skip_all => "cvs not available or could not make test dir" + } + } +} +use Test::More tests => 12; + +BEGIN { use_ok("IkiWiki"); } + +%config=IkiWiki::defaultconfig(); +$config{rcs} = "cvs"; +$config{srcdir} = "$dir/src"; +$config{cvsrepo} = "$dir/repo"; +$config{cvspath} = "ikiwiki"; +IkiWiki::loadplugins(); +IkiWiki::checkconfig(); + +my $cvsrepo = "$dir/repo"; + +system "cvs -d $cvsrepo init >/dev/null"; +system "mkdir $dir/ikiwiki >/dev/null"; +my $cwd = `pwd`; chomp $cwd; +system "cd $dir/ikiwiki && cvs -d $cvsrepo import -m import ikiwiki VENDOR RELEASE >/dev/null"; +chdir $cwd; +system "rm -rf $dir/ikiwiki >/dev/null"; +system "cvs -d $cvsrepo co -d $config{srcdir} ikiwiki >/dev/null"; + +# Web commit +chdir $cwd; +my $test1 = readfile("t/test1.mdwn"); +writefile('test1.mdwn', $config{srcdir}, $test1); +IkiWiki::rcs_add("test1.mdwn"); +IkiWiki::rcs_commit("test1.mdwn", "Added the first page", "moo"); + +my @changes; +@changes = IkiWiki::rcs_recentchanges(3); + +is($#changes, 0); +is($changes[0]{message}[0]{"line"}, "Added the first page"); +is($changes[0]{pages}[0]{"page"}, "test1"); + +# Manual commit +my $message = "Added the second page"; + +chdir $cwd; +my $test2 = readfile("t/test2.mdwn"); +writefile('test2.mdwn', $config{srcdir}, $test2); +chdir $config{srcdir}; +system "cvs add test2.mdwn >/dev/null 2>&1"; +system "cvs commit -m \"$message\" test2.mdwn >/dev/null"; + +@changes = IkiWiki::rcs_recentchanges(3); +is($#changes, 1); +is($changes[0]{message}[0]{"line"}, $message); +is($changes[0]{pages}[0]{"page"}, "test2"); +is($changes[1]{pages}[0]{"page"}, "test1"); + +# extra slashes in the path shouldn't break things +$config{cvspath} = "/ikiwiki//"; +IkiWiki::checkconfig(); +@changes = IkiWiki::rcs_recentchanges(3); +is($#changes, 1); +is($changes[0]{message}[0]{"line"}, $message); +is($changes[0]{pages}[0]{"page"}, "test2"); +is($changes[1]{pages}[0]{"page"}, "test1"); + +system "rm -rf $dir"; -- cgit v1.2.3 From 98553d15375a373bd769b82cea24b48ee9613662 Mon Sep 17 00:00:00 2001 From: Amitai Schlair Date: Thu, 10 Sep 2009 00:51:34 -0400 Subject: CVS operations generally need to be within CVS checkouts, so these chdir() calls are warranted. They shouldn't modify the caller's working directory, though. Use File::chdir to keep the scope of the changes subroutine-local. The tests now pass without resetting the working directory. --- IkiWiki/Plugin/cvs.pm | 12 +++++++----- t/cvs.t | 9 ++------- 2 files changed, 9 insertions(+), 12 deletions(-) (limited to 't') diff --git a/IkiWiki/Plugin/cvs.pm b/IkiWiki/Plugin/cvs.pm index ff9d578e2..e926425f2 100644 --- a/IkiWiki/Plugin/cvs.pm +++ b/IkiWiki/Plugin/cvs.pm @@ -5,6 +5,8 @@ use warnings; use strict; use IkiWiki; +use File::chdir; + sub import { hook(type => "wrapperargcheck", id => "cvs", call => \&wrapperargcheck); hook(type => "checkconfig", id => "cvs", call => \&checkconfig); @@ -104,7 +106,7 @@ sub cvs_info ($$) { my $field=shift; my $file=shift; - chdir $config{srcdir} || error("Cannot chdir to $config{srcdir}: $!"); + local $CWD = $config{srcdir}; my $info=`cvs status $file`; my ($ret)=$info=~/^\s*$field:\s*(\S+)/m; @@ -115,7 +117,7 @@ sub cvs_runcvs(@) { my @cmd = @_; unshift @cmd, 'cvs', '-Q'; - chdir $config{srcdir} || error("Cannot chdir to $config{srcdir}: $!"); + local $CWD = $config{srcdir}; open(my $savedout, ">&STDOUT"); open(STDOUT, ">", "/dev/null"); @@ -261,7 +263,7 @@ sub rcs_rename ($$) { return unless cvs_is_controlling; - chdir $config{srcdir} || error("Cannot chdir to $config{srcdir}: $!"); + local $CWD = $config{srcdir}; if (system("mv", "$src", "$dest") != 0) { warn("filesystem rename failed\n"); @@ -280,7 +282,7 @@ sub rcs_recentchanges($) { eval q{use Date::Parse}; error($@) if $@; - chdir $config{srcdir} || error("Cannot chdir to $config{srcdir}: $!"); + local $CWD = $config{srcdir}; # There's no cvsps option to get the last N changesets. # Write full output to a temp file and read backwards. @@ -400,7 +402,7 @@ sub rcs_recentchanges($) { sub rcs_diff ($) { my $rev=IkiWiki::possibly_foolish_untaint(int(shift)); - chdir $config{srcdir} || error("Cannot chdir to $config{srcdir}: $!"); + local $CWD = $config{srcdir}; # diff output is unavoidably preceded by the cvsps PatchSet entry my @cvsps = `env TZ=UTC cvsps -q --cvs-direct -z 30 -g -s $rev`; diff --git a/t/cvs.t b/t/cvs.t index c3612ef8c..9df62334d 100755 --- a/t/cvs.t +++ b/t/cvs.t @@ -28,14 +28,11 @@ my $cvsrepo = "$dir/repo"; system "cvs -d $cvsrepo init >/dev/null"; system "mkdir $dir/ikiwiki >/dev/null"; -my $cwd = `pwd`; chomp $cwd; system "cd $dir/ikiwiki && cvs -d $cvsrepo import -m import ikiwiki VENDOR RELEASE >/dev/null"; -chdir $cwd; system "rm -rf $dir/ikiwiki >/dev/null"; system "cvs -d $cvsrepo co -d $config{srcdir} ikiwiki >/dev/null"; # Web commit -chdir $cwd; my $test1 = readfile("t/test1.mdwn"); writefile('test1.mdwn', $config{srcdir}, $test1); IkiWiki::rcs_add("test1.mdwn"); @@ -51,12 +48,10 @@ is($changes[0]{pages}[0]{"page"}, "test1"); # Manual commit my $message = "Added the second page"; -chdir $cwd; my $test2 = readfile("t/test2.mdwn"); writefile('test2.mdwn', $config{srcdir}, $test2); -chdir $config{srcdir}; -system "cvs add test2.mdwn >/dev/null 2>&1"; -system "cvs commit -m \"$message\" test2.mdwn >/dev/null"; +system "cd $config{srcdir} && cvs add test2.mdwn >/dev/null 2>&1"; +system "cd $config{srcdir} && cvs commit -m \"$message\" test2.mdwn >/dev/null"; @changes = IkiWiki::rcs_recentchanges(3); is($#changes, 1); -- cgit v1.2.3 From e22af796e502fb881b7a8605b87e0746190d4899 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Thu, 10 Sep 2009 16:49:54 -0400 Subject: skip if cvsps is not available --- t/cvs.t | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 't') diff --git a/t/cvs.t b/t/cvs.t index 9df62334d..c9caa94eb 100755 --- a/t/cvs.t +++ b/t/cvs.t @@ -6,9 +6,11 @@ BEGIN { $dir="/tmp/ikiwiki-test-cvs.$$"; my $cvs=`which cvs`; chomp $cvs; - if (! -x $cvs || ! mkdir($dir)) { + my $cvsps=`which cvsps`; + chomp $cvsps; + if (! -x $cvs || ! -x $cvsps || ! mkdir($dir)) { eval q{ - use Test::More skip_all => "cvs not available or could not make test dir" + use Test::More skip_all => "cvs or cvsps not available or could not make test dir" } } } -- cgit v1.2.3 From b94c1ccd552f70b8ecce8f414563b4c6b6385e08 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 13 Sep 2009 19:40:42 -0400 Subject: fix CVS prune test The regexp only matches things inside the CVS directory, not just "CVS" by itself. I changed the test to not test that it will be pruned. --- t/file_pruned.t | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 't') diff --git a/t/file_pruned.t b/t/file_pruned.t index a05ad548f..00542d580 100755 --- a/t/file_pruned.t +++ b/t/file_pruned.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 27; +use Test::More tests => 26; BEGIN { use_ok("IkiWiki"); } @@ -9,8 +9,7 @@ BEGIN { use_ok("IkiWiki"); } ok(IkiWiki::file_pruned("src/.ikiwiki/", "src")); ok(IkiWiki::file_pruned("src/.ikiwiki/index", "src")); -ok(IkiWiki::file_pruned("src/CVS", "src")); -ok(IkiWiki::file_pruned("src/subdir/CVS", "src")); +ok(IkiWiki::file_pruned("src/CVS/foo", "src")); ok(IkiWiki::file_pruned("src/subdir/CVS/foo", "src")); ok(IkiWiki::file_pruned("src/.svn", "src")); ok(IkiWiki::file_pruned("src/subdir/.svn", "src")); -- cgit v1.2.3 From 36c605cadace789d14354a1c835457f5f23ee1c2 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 4 Oct 2009 23:27:00 -0400 Subject: add test suite for add_depends and found a bug in my bitmath.. --- IkiWiki.pm | 2 +- t/add_depends.t | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 t/add_depends.t (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 9591a8160..0bb0c1746 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -1786,7 +1786,7 @@ sub add_depends ($$;@) { $limited = $1 =~ /^(glob|internal|creation_month|creation_day|creation_year|created_before|created_after)$/; } - $deptype=$deptype & ~$DEPEND_CONTENT & $DEPEND_PRESENCE + $deptype=$deptype & ~$DEPEND_CONTENT | $DEPEND_PRESENCE if $params{presence} && $limited; } diff --git a/t/add_depends.t b/t/add_depends.t new file mode 100644 index 000000000..935a57944 --- /dev/null +++ b/t/add_depends.t @@ -0,0 +1,68 @@ +#!/usr/bin/perl +use warnings; +use strict; +use Test::More tests => 50; + +BEGIN { use_ok("IkiWiki"); } +%config=IkiWiki::defaultconfig(); +$config{srcdir}=$config{destdir}="/dev/null"; +IkiWiki::checkconfig(); + +# avoids adding an unparseable pagespec +ok(! add_depends("foo", "foo and (bar")); +ok(! add_depends("foo", "foo another")); + +# simple and not-so-simple dependencies split +ok(add_depends("foo", "*")); +ok(add_depends("foo", "bar")); +ok(add_depends("foo", "BAZ")); +ok(exists $IkiWiki::depends_simple{foo}{"bar"}); +ok(exists $IkiWiki::depends_simple{foo}{"baz"}); # lowercase +ok(! exists $IkiWiki::depends_simple{foo}{"*"}); +ok(! exists $IkiWiki::depends{foo}{"bar"}); +ok(! exists $IkiWiki::depends{foo}{"baz"}); + +# default dependencies are content dependencies +ok($IkiWiki::depends{foo}{"*"} & $IkiWiki::DEPEND_CONTENT); +ok(! ($IkiWiki::depends{foo}{"*"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); +ok($IkiWiki::depends_simple{foo}{"bar"} & $IkiWiki::DEPEND_CONTENT); +ok(! ($IkiWiki::depends_simple{foo}{"bar"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); + +# adding other dep types standalone +ok(add_depends("foo2", "*", presence => 1)); +ok(add_depends("foo2", "bar", links => 1)); +ok($IkiWiki::depends{foo2}{"*"} & $IkiWiki::DEPEND_PRESENCE); +ok(! ($IkiWiki::depends{foo2}{"*"} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); +ok($IkiWiki::depends_simple{foo2}{"bar"} & $IkiWiki::DEPEND_LINKS); +ok(! ($IkiWiki::depends_simple{foo2}{"bar"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_CONTENT))); + +# adding combined dep types +ok(add_depends("foo2", "baz", links => 1, presence => 1)); +ok($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_LINKS); +ok($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_PRESENCE); +ok(! ($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_CONTENT)); + +# adding a pagespec that requires page metadata should cause a fallback to +# a content dependency +foreach my $spec ("* and ! link(bar)", "* or link(bar)", "unknownspec()", + "title(hi)", + "* or backlink(yo)", # this one could actually be acceptably be + # detected to not need a content dep .. in + # theory! + ) { + ok(add_depends("foo3", $spec, presence => 1)); + ok($IkiWiki::depends{foo3}{$spec} & $IkiWiki::DEPEND_CONTENT); + ok(! ($IkiWiki::depends{foo3}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); +} + +# adding dep types to existing dependencies should merge the flags +ok(add_depends("foo2", "baz")); +ok($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_LINKS); +ok($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_PRESENCE); +ok(($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_CONTENT)); +ok(add_depends("foo2", "bar", presence => 1)); # had only links before +ok($IkiWiki::depends_simple{foo2}{"bar"} & ($IkiWiki::DEPEND_LINKS | $IkiWiki::DEPEND_PRESENCE)); +ok(! ($IkiWiki::depends_simple{foo2}{"bar"} & $IkiWiki::DEPEND_CONTENT)); +ok(add_depends("foo", "bar", links => 1)); # had only content before +ok($IkiWiki::depends{foo}{"*"} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS)); +ok(! ($IkiWiki::depends{foo}{"*"} & $IkiWiki::DEPEND_PRESENCE)); -- cgit v1.2.3 From 62cc2afcaa7d09f73cb00a7efb15502406743178 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Tue, 6 Oct 2009 18:44:34 -0400 Subject: update to test backlinks() pagespecs --- t/add_depends.t | 47 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 14 deletions(-) mode change 100644 => 100755 t/add_depends.t (limited to 't') diff --git a/t/add_depends.t b/t/add_depends.t old mode 100644 new mode 100755 index 935a57944..68429b24a --- a/t/add_depends.t +++ b/t/add_depends.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 50; +use Test::More tests => 85; BEGIN { use_ok("IkiWiki"); } %config=IkiWiki::defaultconfig(); @@ -42,19 +42,6 @@ ok($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_LINKS); ok($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_PRESENCE); ok(! ($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_CONTENT)); -# adding a pagespec that requires page metadata should cause a fallback to -# a content dependency -foreach my $spec ("* and ! link(bar)", "* or link(bar)", "unknownspec()", - "title(hi)", - "* or backlink(yo)", # this one could actually be acceptably be - # detected to not need a content dep .. in - # theory! - ) { - ok(add_depends("foo3", $spec, presence => 1)); - ok($IkiWiki::depends{foo3}{$spec} & $IkiWiki::DEPEND_CONTENT); - ok(! ($IkiWiki::depends{foo3}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); -} - # adding dep types to existing dependencies should merge the flags ok(add_depends("foo2", "baz")); ok($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_LINKS); @@ -66,3 +53,35 @@ ok(! ($IkiWiki::depends_simple{foo2}{"bar"} & $IkiWiki::DEPEND_CONTENT)); ok(add_depends("foo", "bar", links => 1)); # had only content before ok($IkiWiki::depends{foo}{"*"} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS)); ok(! ($IkiWiki::depends{foo}{"*"} & $IkiWiki::DEPEND_PRESENCE)); + +# adding a pagespec that requires page metadata should cause a fallback to +# a content dependency +foreach my $spec ("* and ! link(bar)", "* or link(bar)", "unknownspec()", + "title(hi)", + "* or unknown(yo)", # this one could actually be acceptably be + # detected to not need a content dep .. in + # theory! + ) { + ok(add_depends("foo3", $spec, presence => 1)); + ok($IkiWiki::depends{foo3}{$spec} & $IkiWiki::DEPEND_CONTENT); + ok(! ($IkiWiki::depends{foo3}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); + ok(add_depends("foo4", $spec, links => 1)); + ok($IkiWiki::depends{foo3}{$spec} & $IkiWiki::DEPEND_CONTENT); + ok(! ($IkiWiki::depends{foo3}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); +} + +# a pagespec with backlinks() in it is acceptable for a links dependency, +# but not a presence dependency +foreach my $spec ("index or (backlink(index) and !*.png)", "backlink(foo)") { + ok(add_depends("foo5", $spec, presence => 1)); + ok($IkiWiki::depends{foo5}{$spec} & $IkiWiki::DEPEND_CONTENT); + ok(! ($IkiWiki::depends{foo5}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); + ok(add_depends("foo6", $spec, links => 1)); + ok($IkiWiki::depends{foo6}{$spec} & $IkiWiki::DEPEND_LINKS); + ok(! ($IkiWiki::depends{foo6}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_CONTENT))); + # combining both ends up with a content+links dependency + ok(add_depends("foo7", $spec, presence => 1, links => 1)); + ok($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_CONTENT); + ok($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_LINKS); + ok(! ($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_PRESENCE)); +} -- cgit v1.2.3 From 7a8b492bcce45123cef45ebe6f97cd70b38b9db7 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Tue, 6 Oct 2009 19:07:52 -0400 Subject: add_depends should default to content dependencies if unknown type specified --- IkiWiki.pm | 4 +--- t/add_depends.t | 7 ++++++- 2 files changed, 7 insertions(+), 4 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 7348ea2f7..7b1d24c6a 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -1810,9 +1810,7 @@ sub add_depends ($$;@) { } } } - else { - $deptype=$DEPEND_CONTENT; - } + $deptype=$DEPEND_CONTENT unless $deptype; if ($simple) { $depends_simple{$page}{lc $pagespec} |= $deptype; diff --git a/t/add_depends.t b/t/add_depends.t index 68429b24a..2d686a17d 100755 --- a/t/add_depends.t +++ b/t/add_depends.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 85; +use Test::More tests => 88; BEGIN { use_ok("IkiWiki"); } %config=IkiWiki::defaultconfig(); @@ -85,3 +85,8 @@ foreach my $spec ("index or (backlink(index) and !*.png)", "backlink(foo)") { ok($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_LINKS); ok(! ($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_PRESENCE)); } + +# content is the default if unknown types are entered +ok(add_depends("foo8", "*", presenCe => 1)); +ok($IkiWiki::depends{foo8}{"*"} & $IkiWiki::DEPEND_CONTENT); +ok(! ($IkiWiki::depends{foo8}{"*"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); -- cgit v1.2.3 From c72fda7d695142cc29ac986125234140f6414d97 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 7 Oct 2009 19:40:44 -0400 Subject: make success and failreason objects carry an influences hash The hash will be used used to record a set of pages that influenced the result of a pagespec match. The influences are merged together when boolean and/or are encountered in a pagespec. That means using a non-short-circuiting OR operator. And so I use & and | when translating pagespecs, since those bitwise operators can be overloaded. ("and" and "or" cannot, apparently). --- IkiWiki.pm | 34 +++++++++++++++++++--------------- t/pagespec_match.t | 6 +++++- 2 files changed, 24 insertions(+), 16 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 7b1d24c6a..73d2a9763 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -1926,10 +1926,10 @@ sub pagespec_translate ($) { }gx) { my $word=$1; if (lc $word eq 'and') { - $code.=' &&'; + $code.=' &'; } elsif (lc $word eq 'or') { - $code.=' ||'; + $code.=' |'; } elsif ($word eq "(" || $word eq ")" || $word eq "!") { $code.=' '.$word; @@ -2015,36 +2015,40 @@ sub glob2re ($) { package IkiWiki::FailReason; use overload ( - '""' => sub { ${$_[0]} }, + '""' => sub { $_[0][0] }, '0+' => sub { 0 }, '!' => sub { bless $_[0], 'IkiWiki::SuccessReason'}, + '&' => sub { $_[0][1]={%{$_[0][1]}, %{$_[1][1]}}; $_[0] }, + '|' => sub { $_[1][1]={%{$_[0][1]}, %{$_[1][1]}}; $_[1] }, fallback => 1, ); -sub new { - my $class = shift; - my $value = shift; - return bless \$value, $class; -} - -package IkiWiki::ErrorReason; - -our @ISA = 'IkiWiki::FailReason'; +our @ISA = 'IkiWiki::SuccessReason'; package IkiWiki::SuccessReason; use overload ( - '""' => sub { ${$_[0]} }, + '""' => sub { $_[0][0] }, '0+' => sub { 1 }, '!' => sub { bless $_[0], 'IkiWiki::FailReason'}, + '&' => sub { $_[1][1]={%{$_[0][1]}, %{$_[1][1]}}; $_[1] }, + '|' => sub { $_[0][1]={%{$_[0][1]}, %{$_[1][1]}}; $_[0] }, fallback => 1, ); sub new { my $class = shift; my $value = shift; - return bless \$value, $class; -}; + return bless [$value, {@_}], $class; +} + +sub influences { + return keys %{$_[0][1]}; +} + +package IkiWiki::ErrorReason; + +our @ISA = 'IkiWiki::FailReason'; package IkiWiki::PageSpec; diff --git a/t/pagespec_match.t b/t/pagespec_match.t index 64408f489..a1fcba7c8 100755 --- a/t/pagespec_match.t +++ b/t/pagespec_match.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 54; +use Test::More tests => 56; BEGIN { use_ok("IkiWiki"); } @@ -88,3 +88,7 @@ ok(! pagespec_match("foo", "no_such_function(foo)"), "foo"); my $ret=pagespec_match("foo", "(invalid"); ok(! $ret, "syntax error"); ok($ret =~ /syntax error/, "error message"); + +my $ret=pagespec_match("foo", "bar or foo"); +ok($ret, "simple match"); +is($ret, "foo matches foo", "stringified return"); -- cgit v1.2.3 From 4002d7c1a4657e769b036c6e76106991ec5c3897 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 7 Oct 2009 20:31:13 -0400 Subject: add influence info to match_* Also update docs, test suite. --- IkiWiki.pm | 24 ++++++++++++------------ IkiWiki/Plugin/meta.pm | 8 ++++---- debian/changelog | 2 ++ doc/plugins/write.mdwn | 7 +++++++ t/pagespec_match.t | 15 +++++++++++++-- 5 files changed, 38 insertions(+), 18 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 73d2a9763..9c386e154 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -2039,7 +2039,7 @@ use overload ( sub new { my $class = shift; my $value = shift; - return bless [$value, {@_}], $class; + return bless [$value, {map { $_ => 1 } @_}], $class; } sub influences { @@ -2099,23 +2099,23 @@ sub match_link ($$;@) { my $from=exists $params{location} ? $params{location} : ''; my $links = $IkiWiki::links{$page}; - return IkiWiki::FailReason->new("$page has no links") unless $links && @{$links}; + return IkiWiki::FailReason->new("$page has no links", $link) unless $links && @{$links}; my $bestlink = IkiWiki::bestlink($from, $link); foreach my $p (@{$links}) { if (length $bestlink) { - return IkiWiki::SuccessReason->new("$page links to $link") + return IkiWiki::SuccessReason->new("$page links to $link", $page) if $bestlink eq IkiWiki::bestlink($page, $p); } else { - return IkiWiki::SuccessReason->new("$page links to page $p matching $link") + return IkiWiki::SuccessReason->new("$page links to page $p matching $link", $page) if match_glob($p, $link, %params); my ($p_rel)=$p=~/^\/?(.*)/; $link=~s/^\///; - return IkiWiki::SuccessReason->new("$page links to page $p_rel matching $link") + return IkiWiki::SuccessReason->new("$page links to page $p_rel matching $link", $page) if match_glob($p_rel, $link, %params); } } - return IkiWiki::FailReason->new("$page does not link to $link"); + return IkiWiki::FailReason->new("$page does not link to $link", $page); } sub match_backlink ($$;@) { @@ -2131,14 +2131,14 @@ sub match_created_before ($$;@) { if (exists $IkiWiki::pagectime{$testpage}) { if ($IkiWiki::pagectime{$page} < $IkiWiki::pagectime{$testpage}) { - return IkiWiki::SuccessReason->new("$page created before $testpage"); + return IkiWiki::SuccessReason->new("$page created before $testpage", $testpage); } else { - return IkiWiki::FailReason->new("$page not created before $testpage"); + return IkiWiki::FailReason->new("$page not created before $testpage", $testpage); } } else { - return IkiWiki::ErrorReason->new("$testpage does not exist"); + return IkiWiki::ErrorReason->new("$testpage does not exist", $testpage); } } @@ -2151,14 +2151,14 @@ sub match_created_after ($$;@) { if (exists $IkiWiki::pagectime{$testpage}) { if ($IkiWiki::pagectime{$page} > $IkiWiki::pagectime{$testpage}) { - return IkiWiki::SuccessReason->new("$page created after $testpage"); + return IkiWiki::SuccessReason->new("$page created after $testpage", $testpage); } else { - return IkiWiki::FailReason->new("$page not created after $testpage"); + return IkiWiki::FailReason->new("$page not created after $testpage", $testpage); } } else { - return IkiWiki::ErrorReason->new("$testpage does not exist"); + return IkiWiki::ErrorReason->new("$testpage does not exist", $testpage); } } diff --git a/IkiWiki/Plugin/meta.pm b/IkiWiki/Plugin/meta.pm index 9b041a748..a8ee5bc85 100644 --- a/IkiWiki/Plugin/meta.pm +++ b/IkiWiki/Plugin/meta.pm @@ -291,21 +291,21 @@ sub match { if (defined $val) { if ($val=~/^$re$/i) { - return IkiWiki::SuccessReason->new("$re matches $field of $page"); + return IkiWiki::SuccessReason->new("$re matches $field of $page", $page); } else { - return IkiWiki::FailReason->new("$re does not match $field of $page"); + return IkiWiki::FailReason->new("$re does not match $field of $page", $page); } } else { - return IkiWiki::FailReason->new("$page does not have a $field"); + return IkiWiki::FailReason->new("$page does not have a $field", $page); } } package IkiWiki::PageSpec; sub match_title ($$;@) { - IkiWiki::Plugin::meta::match("title", @_); + IkiWiki::Plugin::meta::match("title", @_); } sub match_author ($$;@) { diff --git a/debian/changelog b/debian/changelog index dc6ee0a81..565a0cffa 100644 --- a/debian/changelog +++ b/debian/changelog @@ -32,6 +32,8 @@ ikiwiki (3.14159266) UNRELEASED; urgency=low * Transitive dependencies are now correctly supported. * Rebuild wikis on upgrade to this version to get improved dependency info. + * Plugins providing PageSpec `match_*` functions should pass additional + influence information when creating result objects. -- Joey Hess Sun, 27 Sep 2009 17:40:03 -0400 diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn index 8e8c3311e..6b47033e5 100644 --- a/doc/plugins/write.mdwn +++ b/doc/plugins/write.mdwn @@ -982,6 +982,13 @@ an IkiWiki::FailReason object if the match fails. If the match cannot be attempted at all, for any page, it can instead return an IkiWiki::ErrorReason object explaining why. +When constructing these objects, you should also include a list of any +pages whose contents or other metadata influenced the result of the match. +For example, "backlink(foo)" is influenced by the contents of page foo; +"link(foo)" and "title(bar)" are influenced by the contents of any +page they match; "created_before(foo)" is influenced by the metadata of +foo; while "glob(*)" is not influenced by the contents of any page. + ### Setup plugins The ikiwiki setup file is loaded using a pluggable mechanism. If you look diff --git a/t/pagespec_match.t b/t/pagespec_match.t index a1fcba7c8..f73bfdfe1 100755 --- a/t/pagespec_match.t +++ b/t/pagespec_match.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 56; +use Test::More tests => 61; BEGIN { use_ok("IkiWiki"); } @@ -89,6 +89,17 @@ my $ret=pagespec_match("foo", "(invalid"); ok(! $ret, "syntax error"); ok($ret =~ /syntax error/, "error message"); -my $ret=pagespec_match("foo", "bar or foo"); +$ret=pagespec_match("foo", "bar or foo"); ok($ret, "simple match"); is($ret, "foo matches foo", "stringified return"); + +$ret=pagespec_match("foo", "link(bar)"); +is(join(",", $ret->influences), 'foo', "link is influenced by the page with the link"); +$ret=pagespec_match("bar", "backlink(foo)"); +is(join(",", $ret->influences), 'foo', "backlink is influenced by the page with the link"); +$ret=pagespec_match("bar", "backlink(foo)"); +is(join(",", $ret->influences), 'foo', "backlink is influenced by the page with the link"); +$ret=pagespec_match("bar", "created_before(foo)"); +is(join(",", $ret->influences), 'foo', "created_before is influenced by the comparison page"); +$ret=pagespec_match("bar", "created_after(foo)"); +is(join(",", $ret->influences), 'foo', "created_after is influenced by the comparison page"); -- cgit v1.2.3 From 54fb82a5a47078f6865ed5bf7d7c09db4bf34e22 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 7 Oct 2009 20:35:26 -0400 Subject: more influences tests --- t/pagespec_match.t | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 't') diff --git a/t/pagespec_match.t b/t/pagespec_match.t index f73bfdfe1..1a0db1cef 100755 --- a/t/pagespec_match.t +++ b/t/pagespec_match.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 61; +use Test::More tests => 64; BEGIN { use_ok("IkiWiki"); } @@ -103,3 +103,9 @@ $ret=pagespec_match("bar", "created_before(foo)"); is(join(",", $ret->influences), 'foo', "created_before is influenced by the comparison page"); $ret=pagespec_match("bar", "created_after(foo)"); is(join(",", $ret->influences), 'foo', "created_after is influenced by the comparison page"); +$ret=pagespec_match("bar", "link(quux) and created_after(foo)"); +is(join(",", sort $ret->influences), 'foo,quux', "influences add up over AND"); +$ret=pagespec_match("bar", "link(quux) and created_after(foo)"); +is(join(",", sort $ret->influences), 'foo,quux', "influences add up over OR"); +$ret=pagespec_match("bar", "!link(quux) and !created_after(foo)"); +is(join(",", sort $ret->influences), 'foo,quux', "influences unaffected by negation"); -- cgit v1.2.3 From bb389a5ae25461ed20e2d28b18ea8b08f5f36473 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 7 Oct 2009 21:13:10 -0400 Subject: convert add_depends to use influences No more horrible special-case pagespec parsing. OTOH, matching over all pages to determine influences is a lot of work. --- IkiWiki.pm | 52 +++++++++++----------------------- t/add_depends.t | 86 ++++++++++++++++++++++++++++++--------------------------- 2 files changed, 62 insertions(+), 76 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 9c386e154..7adc63139 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -1772,52 +1772,32 @@ sub add_depends ($$;@) { my $page=shift; my $pagespec=shift; - # Is the pagespec a simple page name? - my $simple=$pagespec =~ /$config{wiki_file_regexp}/ && - $pagespec !~ /[\s*?()!]/; - my $deptype=0; if (@_) { my %params=@_; - if ($params{presence}) { - # Is the pagespec limited to terms that will continue - # to match pages as long as those pages exist? - my $presence_limited=1; - while ($presence_limited && $pagespec=~m/(\w+)\([^\)]*\)/g) { - $presence_limited = $1 =~ /^(glob|internal|creation_month|creation_day|creation_year|created_before|created_after)$/; - } - if ($presence_limited) { - $deptype=$deptype | $DEPEND_PRESENCE; - } - else { - $deptype=$deptype | $DEPEND_CONTENT; - } - } - if ($params{links}) { - # Is the pagespec limited to terms that will continue - # to match pages as long as those pages exist and - # link to the same places? - my $links_limited=1; - while ($links_limited && $pagespec=~m/(\w+)\([^\)]*\)/g) { - $links_limited = $1 =~ /^(glob|internal|creation_month|creation_day|creation_year|created_before|created_after|backlink)$/; - } - if ($links_limited) { - $deptype=$deptype | $DEPEND_LINKS; - } - else { - $deptype=$deptype | $DEPEND_CONTENT; - } - } + $deptype=$deptype | $DEPEND_PRESENCE if $params{presence}; + $deptype=$deptype | $DEPEND_LINKS if $params{links}; } $deptype=$DEPEND_CONTENT unless $deptype; - if ($simple) { + # Is the pagespec a simple page name? + if ($pagespec =~ /$config{wiki_file_regexp}/ && + $pagespec !~ /[\s*?()!]/) { $depends_simple{$page}{lc $pagespec} |= $deptype; return 1; } - return unless pagespec_valid($pagespec); + # Analyse the pagespec, and match it against all pages + # to get a list of influences, and add explicit + # content dependencies for those. + my $sub=pagespec_translate($pagespec); + return if $@; + foreach my $p (keys %pagesources) { + my $r=$sub->($p, location => $page ); + map { $depends_simple{$page}{lc $_} |= $DEPEND_CONTENT } $r->influences + if $r; + } $depends{$page}{$pagespec} |= $deptype; return 1; @@ -2099,7 +2079,7 @@ sub match_link ($$;@) { my $from=exists $params{location} ? $params{location} : ''; my $links = $IkiWiki::links{$page}; - return IkiWiki::FailReason->new("$page has no links", $link) unless $links && @{$links}; + return IkiWiki::FailReason->new("$page has no links", $page) unless $links && @{$links}; my $bestlink = IkiWiki::bestlink($from, $link); foreach my $p (@{$links}) { if (length $bestlink) { diff --git a/t/add_depends.t b/t/add_depends.t index 2d686a17d..d49aa74ce 100755 --- a/t/add_depends.t +++ b/t/add_depends.t @@ -8,25 +8,27 @@ BEGIN { use_ok("IkiWiki"); } $config{srcdir}=$config{destdir}="/dev/null"; IkiWiki::checkconfig(); +$pagesources{"foo$_"}="foo$_.mdwn" for 0..9; + # avoids adding an unparseable pagespec -ok(! add_depends("foo", "foo and (bar")); -ok(! add_depends("foo", "foo another")); +ok(! add_depends("foo0", "foo and (bar")); +ok(! add_depends("foo0", "foo another")); # simple and not-so-simple dependencies split -ok(add_depends("foo", "*")); -ok(add_depends("foo", "bar")); -ok(add_depends("foo", "BAZ")); -ok(exists $IkiWiki::depends_simple{foo}{"bar"}); -ok(exists $IkiWiki::depends_simple{foo}{"baz"}); # lowercase -ok(! exists $IkiWiki::depends_simple{foo}{"*"}); -ok(! exists $IkiWiki::depends{foo}{"bar"}); -ok(! exists $IkiWiki::depends{foo}{"baz"}); +ok(add_depends("foo0", "*")); +ok(add_depends("foo0", "bar")); +ok(add_depends("foo0", "BAZ")); +ok(exists $IkiWiki::depends_simple{foo0}{"bar"}); +ok(exists $IkiWiki::depends_simple{foo0}{"baz"}); # lowercase +ok(! exists $IkiWiki::depends_simple{foo0}{"*"}); +ok(! exists $IkiWiki::depends{foo0}{"bar"}); +ok(! exists $IkiWiki::depends{foo0}{"baz"}); # default dependencies are content dependencies -ok($IkiWiki::depends{foo}{"*"} & $IkiWiki::DEPEND_CONTENT); -ok(! ($IkiWiki::depends{foo}{"*"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); -ok($IkiWiki::depends_simple{foo}{"bar"} & $IkiWiki::DEPEND_CONTENT); -ok(! ($IkiWiki::depends_simple{foo}{"bar"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); +ok($IkiWiki::depends{foo0}{"*"} & $IkiWiki::DEPEND_CONTENT); +ok(! ($IkiWiki::depends{foo0}{"*"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); +ok($IkiWiki::depends_simple{foo0}{"bar"} & $IkiWiki::DEPEND_CONTENT); +ok(! ($IkiWiki::depends_simple{foo0}{"bar"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); # adding other dep types standalone ok(add_depends("foo2", "*", presence => 1)); @@ -50,43 +52,47 @@ ok(($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_CONTENT)); ok(add_depends("foo2", "bar", presence => 1)); # had only links before ok($IkiWiki::depends_simple{foo2}{"bar"} & ($IkiWiki::DEPEND_LINKS | $IkiWiki::DEPEND_PRESENCE)); ok(! ($IkiWiki::depends_simple{foo2}{"bar"} & $IkiWiki::DEPEND_CONTENT)); -ok(add_depends("foo", "bar", links => 1)); # had only content before -ok($IkiWiki::depends{foo}{"*"} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS)); -ok(! ($IkiWiki::depends{foo}{"*"} & $IkiWiki::DEPEND_PRESENCE)); +ok(add_depends("foo0", "bar", links => 1)); # had only content before +ok($IkiWiki::depends{foo0}{"*"} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS)); +ok(! ($IkiWiki::depends{foo0}{"*"} & $IkiWiki::DEPEND_PRESENCE)); -# adding a pagespec that requires page metadata should cause a fallback to -# a content dependency -foreach my $spec ("* and ! link(bar)", "* or link(bar)", "unknownspec()", - "title(hi)", - "* or unknown(yo)", # this one could actually be acceptably be - # detected to not need a content dep .. in - # theory! - ) { +# Adding a pagespec that requires page metadata should add the influence +# as an explicit content dependency. +$links{foo0}=$links{foo9}=[qw{bar baz}]; +foreach my $spec ("* and ! link(bar)", "* or link(bar)") { ok(add_depends("foo3", $spec, presence => 1)); - ok($IkiWiki::depends{foo3}{$spec} & $IkiWiki::DEPEND_CONTENT); - ok(! ($IkiWiki::depends{foo3}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); + ok($IkiWiki::depends{foo3}{$spec} & $IkiWiki::DEPEND_PRESENCE); + ok(! ($IkiWiki::depends{foo3}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); + ok($IkiWiki::depends_simple{foo3}{foo3} == $IkiWiki::DEPEND_CONTENT); ok(add_depends("foo4", $spec, links => 1)); - ok($IkiWiki::depends{foo3}{$spec} & $IkiWiki::DEPEND_CONTENT); - ok(! ($IkiWiki::depends{foo3}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); + ok($IkiWiki::depends{foo4}{$spec} & $IkiWiki::DEPEND_LINKS); + ok(! ($IkiWiki::depends{foo4}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_PRESENCE))); + ok($IkiWiki::depends_simple{foo4}{foo4} == $IkiWiki::DEPEND_CONTENT); } -# a pagespec with backlinks() in it is acceptable for a links dependency, -# but not a presence dependency -foreach my $spec ("index or (backlink(index) and !*.png)", "backlink(foo)") { +# a pagespec with backlinks() will add as an influence the page with the links +$links{foo0}=[qw{foo5 foo7}]; +foreach my $spec ("bugs or (backlink(foo0) and !*.png)", "backlink(foo)") { ok(add_depends("foo5", $spec, presence => 1)); - ok($IkiWiki::depends{foo5}{$spec} & $IkiWiki::DEPEND_CONTENT); - ok(! ($IkiWiki::depends{foo5}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); + ok($IkiWiki::depends{foo5}{$spec} & $IkiWiki::DEPEND_PRESENCE); + ok(! ($IkiWiki::depends{foo5}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); + ok($IkiWiki::depends_simple{foo5}{foo0} == $IkiWiki::DEPEND_CONTENT); ok(add_depends("foo6", $spec, links => 1)); ok($IkiWiki::depends{foo6}{$spec} & $IkiWiki::DEPEND_LINKS); ok(! ($IkiWiki::depends{foo6}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_CONTENT))); - # combining both ends up with a content+links dependency + ok($IkiWiki::depends_simple{foo5}{foo0} == $IkiWiki::DEPEND_CONTENT); ok(add_depends("foo7", $spec, presence => 1, links => 1)); - ok($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_CONTENT); + ok($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_PRESENCE); ok($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_LINKS); - ok(! ($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_PRESENCE)); + ok(! ($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_CONTENT)); + ok($IkiWiki::depends_simple{foo7}{foo0} == $IkiWiki::DEPEND_CONTENT); + ok(add_depends("foo8", $spec)); + ok($IkiWiki::depends{foo8}{$spec} & $IkiWiki::DEPEND_CONTENT); + ok(! ($IkiWiki::depends{foo8}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); + ok($IkiWiki::depends_simple{foo8}{foo0} == $IkiWiki::DEPEND_CONTENT); } # content is the default if unknown types are entered -ok(add_depends("foo8", "*", presenCe => 1)); -ok($IkiWiki::depends{foo8}{"*"} & $IkiWiki::DEPEND_CONTENT); -ok(! ($IkiWiki::depends{foo8}{"*"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); +ok(add_depends("foo9", "*", presenCe => 1)); +ok($IkiWiki::depends{foo9}{"*"} & $IkiWiki::DEPEND_CONTENT); +ok(! ($IkiWiki::depends{foo9}{"*"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); -- cgit v1.2.3 From 5f9860e65c65aa769f11e550e63cc164b1519710 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 7 Oct 2009 21:48:03 -0400 Subject: add type info to influence information --- IkiWiki.pm | 49 ++++++++++++++++++++++++++++++------------------- IkiWiki/Plugin/meta.pm | 6 +++--- doc/plugins/write.mdwn | 6 +++--- docwiki.setup | 2 +- t/add_depends.t | 14 +++++++------- t/pagespec_match.t | 32 ++++++++++++++++---------------- 6 files changed, 60 insertions(+), 49 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 7adc63139..39a43ddbe 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -1795,8 +1795,10 @@ sub add_depends ($$;@) { return if $@; foreach my $p (keys %pagesources) { my $r=$sub->($p, location => $page ); - map { $depends_simple{$page}{lc $_} |= $DEPEND_CONTENT } $r->influences - if $r; + my %i=$r->influences; + foreach my $i (keys %i) { + $depends_simple{$page}{lc $i} |= $i{$i}; + } } $depends{$page}{$pagespec} |= $deptype; @@ -1998,8 +2000,8 @@ use overload ( '""' => sub { $_[0][0] }, '0+' => sub { 0 }, '!' => sub { bless $_[0], 'IkiWiki::SuccessReason'}, - '&' => sub { $_[0][1]={%{$_[0][1]}, %{$_[1][1]}}; $_[0] }, - '|' => sub { $_[1][1]={%{$_[0][1]}, %{$_[1][1]}}; $_[1] }, + '&' => sub { $_[0]->merge_influences($_[1]); $_[0] }, + '|' => sub { $_[1]->merge_influences($_[0]); $_[1] }, fallback => 1, ); @@ -2011,19 +2013,27 @@ use overload ( '""' => sub { $_[0][0] }, '0+' => sub { 1 }, '!' => sub { bless $_[0], 'IkiWiki::FailReason'}, - '&' => sub { $_[1][1]={%{$_[0][1]}, %{$_[1][1]}}; $_[1] }, - '|' => sub { $_[0][1]={%{$_[0][1]}, %{$_[1][1]}}; $_[0] }, + '&' => sub { $_[1]->merge_influences($_[0]); $_[1] }, + '|' => sub { $_[0]->merge_influences($_[1]); $_[0] }, fallback => 1, ); sub new { my $class = shift; my $value = shift; - return bless [$value, {map { $_ => 1 } @_}], $class; + return bless [$value, {@_}], $class; } sub influences { - return keys %{$_[0][1]}; + return %{$_[0][1]}; +} + +sub merge_influences { + my $this=shift; + my $other=shift; + foreach my $influence (keys %{$other->[1]}) { + $this->[1]{$influence} |= $other->[1]{$influence}; + } } package IkiWiki::ErrorReason; @@ -2079,23 +2089,24 @@ sub match_link ($$;@) { my $from=exists $params{location} ? $params{location} : ''; my $links = $IkiWiki::links{$page}; - return IkiWiki::FailReason->new("$page has no links", $page) unless $links && @{$links}; + return IkiWiki::FailReason->new("$page has no links", $page => $IkiWiki::DEPEND_LINKS) + unless $links && @{$links}; my $bestlink = IkiWiki::bestlink($from, $link); foreach my $p (@{$links}) { if (length $bestlink) { - return IkiWiki::SuccessReason->new("$page links to $link", $page) + return IkiWiki::SuccessReason->new("$page links to $link", $page => $IkiWiki::DEPEND_LINKS) if $bestlink eq IkiWiki::bestlink($page, $p); } else { - return IkiWiki::SuccessReason->new("$page links to page $p matching $link", $page) + return IkiWiki::SuccessReason->new("$page links to page $p matching $link", $page => $IkiWiki::DEPEND_LINKS) if match_glob($p, $link, %params); my ($p_rel)=$p=~/^\/?(.*)/; $link=~s/^\///; - return IkiWiki::SuccessReason->new("$page links to page $p_rel matching $link", $page) + return IkiWiki::SuccessReason->new("$page links to page $p_rel matching $link", $page => $IkiWiki::DEPEND_LINKS) if match_glob($p_rel, $link, %params); } } - return IkiWiki::FailReason->new("$page does not link to $link", $page); + return IkiWiki::FailReason->new("$page does not link to $link", $page => $IkiWiki::DEPEND_LINKS); } sub match_backlink ($$;@) { @@ -2111,14 +2122,14 @@ sub match_created_before ($$;@) { if (exists $IkiWiki::pagectime{$testpage}) { if ($IkiWiki::pagectime{$page} < $IkiWiki::pagectime{$testpage}) { - return IkiWiki::SuccessReason->new("$page created before $testpage", $testpage); + return IkiWiki::SuccessReason->new("$page created before $testpage", $testpage => $IkiWiki::DEPEND_PRESENCE); } else { - return IkiWiki::FailReason->new("$page not created before $testpage", $testpage); + return IkiWiki::FailReason->new("$page not created before $testpage", $testpage => $IkiWiki::DEPEND_PRESENCE); } } else { - return IkiWiki::ErrorReason->new("$testpage does not exist", $testpage); + return IkiWiki::ErrorReason->new("$testpage does not exist", $testpage => $IkiWiki::DEPEND_PRESENCE); } } @@ -2131,14 +2142,14 @@ sub match_created_after ($$;@) { if (exists $IkiWiki::pagectime{$testpage}) { if ($IkiWiki::pagectime{$page} > $IkiWiki::pagectime{$testpage}) { - return IkiWiki::SuccessReason->new("$page created after $testpage", $testpage); + return IkiWiki::SuccessReason->new("$page created after $testpage", $testpage => $IkiWiki::DEPEND_PRESENCE); } else { - return IkiWiki::FailReason->new("$page not created after $testpage", $testpage); + return IkiWiki::FailReason->new("$page not created after $testpage", $testpage => $IkiWiki::DEPEND_PRESENCE); } } else { - return IkiWiki::ErrorReason->new("$testpage does not exist", $testpage); + return IkiWiki::ErrorReason->new("$testpage does not exist", $testpage => $IkiWiki::DEPEND_PRESENCE); } } diff --git a/IkiWiki/Plugin/meta.pm b/IkiWiki/Plugin/meta.pm index a8ee5bc85..c160e7eba 100644 --- a/IkiWiki/Plugin/meta.pm +++ b/IkiWiki/Plugin/meta.pm @@ -291,14 +291,14 @@ sub match { if (defined $val) { if ($val=~/^$re$/i) { - return IkiWiki::SuccessReason->new("$re matches $field of $page", $page); + return IkiWiki::SuccessReason->new("$re matches $field of $page", $page => $IkiWiki::DEPEND_CONTENT); } else { - return IkiWiki::FailReason->new("$re does not match $field of $page", $page); + return IkiWiki::FailReason->new("$re does not match $field of $page", $page => $IkiWiki::DEPEND_CONTENT); } } else { - return IkiWiki::FailReason->new("$page does not have a $field", $page); + return IkiWiki::FailReason->new("$page does not have a $field", $page => $IkiWiki::DEPEND_CONTENT); } } diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn index 6b47033e5..232430079 100644 --- a/doc/plugins/write.mdwn +++ b/doc/plugins/write.mdwn @@ -982,9 +982,9 @@ an IkiWiki::FailReason object if the match fails. If the match cannot be attempted at all, for any page, it can instead return an IkiWiki::ErrorReason object explaining why. -When constructing these objects, you should also include a list of any -pages whose contents or other metadata influenced the result of the match. -For example, "backlink(foo)" is influenced by the contents of page foo; +When constructing these objects, you should also include information about +of any pages whose contents or other metadata influenced the result of the +match. For example, "backlink(foo)" is influenced by the contents of page foo; "link(foo)" and "title(bar)" are influenced by the contents of any page they match; "created_before(foo)" is influenced by the metadata of foo; while "glob(*)" is not influenced by the contents of any page. diff --git a/docwiki.setup b/docwiki.setup index 52421e501..41c07f024 100644 --- a/docwiki.setup +++ b/docwiki.setup @@ -16,5 +16,5 @@ use IkiWiki::Setup::Standard { userdir => "users", usedirs => 0, prefix_directives => 1, - add_plugins => [qw{goodstuff version haiku polygen fortune}], + add_plugins => [qw{linkmap goodstuff version haiku polygen fortune}], } diff --git a/t/add_depends.t b/t/add_depends.t index d49aa74ce..9f426187b 100755 --- a/t/add_depends.t +++ b/t/add_depends.t @@ -57,17 +57,17 @@ ok($IkiWiki::depends{foo0}{"*"} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_L ok(! ($IkiWiki::depends{foo0}{"*"} & $IkiWiki::DEPEND_PRESENCE)); # Adding a pagespec that requires page metadata should add the influence -# as an explicit content dependency. +# as an explicit dependency. In the case of a link, a links dependency. $links{foo0}=$links{foo9}=[qw{bar baz}]; foreach my $spec ("* and ! link(bar)", "* or link(bar)") { ok(add_depends("foo3", $spec, presence => 1)); ok($IkiWiki::depends{foo3}{$spec} & $IkiWiki::DEPEND_PRESENCE); ok(! ($IkiWiki::depends{foo3}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); - ok($IkiWiki::depends_simple{foo3}{foo3} == $IkiWiki::DEPEND_CONTENT); + ok($IkiWiki::depends_simple{foo3}{foo3} == $IkiWiki::DEPEND_LINKS); ok(add_depends("foo4", $spec, links => 1)); ok($IkiWiki::depends{foo4}{$spec} & $IkiWiki::DEPEND_LINKS); ok(! ($IkiWiki::depends{foo4}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_PRESENCE))); - ok($IkiWiki::depends_simple{foo4}{foo4} == $IkiWiki::DEPEND_CONTENT); + ok($IkiWiki::depends_simple{foo4}{foo4} == $IkiWiki::DEPEND_LINKS); } # a pagespec with backlinks() will add as an influence the page with the links @@ -76,20 +76,20 @@ foreach my $spec ("bugs or (backlink(foo0) and !*.png)", "backlink(foo)") { ok(add_depends("foo5", $spec, presence => 1)); ok($IkiWiki::depends{foo5}{$spec} & $IkiWiki::DEPEND_PRESENCE); ok(! ($IkiWiki::depends{foo5}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); - ok($IkiWiki::depends_simple{foo5}{foo0} == $IkiWiki::DEPEND_CONTENT); + ok($IkiWiki::depends_simple{foo5}{foo0} == $IkiWiki::DEPEND_LINKS); ok(add_depends("foo6", $spec, links => 1)); ok($IkiWiki::depends{foo6}{$spec} & $IkiWiki::DEPEND_LINKS); ok(! ($IkiWiki::depends{foo6}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_CONTENT))); - ok($IkiWiki::depends_simple{foo5}{foo0} == $IkiWiki::DEPEND_CONTENT); + ok($IkiWiki::depends_simple{foo5}{foo0} == $IkiWiki::DEPEND_LINKS); ok(add_depends("foo7", $spec, presence => 1, links => 1)); ok($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_PRESENCE); ok($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_LINKS); ok(! ($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_CONTENT)); - ok($IkiWiki::depends_simple{foo7}{foo0} == $IkiWiki::DEPEND_CONTENT); + ok($IkiWiki::depends_simple{foo7}{foo0} == $IkiWiki::DEPEND_LINKS); ok(add_depends("foo8", $spec)); ok($IkiWiki::depends{foo8}{$spec} & $IkiWiki::DEPEND_CONTENT); ok(! ($IkiWiki::depends{foo8}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); - ok($IkiWiki::depends_simple{foo8}{foo0} == $IkiWiki::DEPEND_CONTENT); + ok($IkiWiki::depends_simple{foo8}{foo0} == $IkiWiki::DEPEND_LINKS); } # content is the default if unknown types are entered diff --git a/t/pagespec_match.t b/t/pagespec_match.t index 1a0db1cef..36fa04370 100755 --- a/t/pagespec_match.t +++ b/t/pagespec_match.t @@ -93,19 +93,19 @@ $ret=pagespec_match("foo", "bar or foo"); ok($ret, "simple match"); is($ret, "foo matches foo", "stringified return"); -$ret=pagespec_match("foo", "link(bar)"); -is(join(",", $ret->influences), 'foo', "link is influenced by the page with the link"); -$ret=pagespec_match("bar", "backlink(foo)"); -is(join(",", $ret->influences), 'foo', "backlink is influenced by the page with the link"); -$ret=pagespec_match("bar", "backlink(foo)"); -is(join(",", $ret->influences), 'foo', "backlink is influenced by the page with the link"); -$ret=pagespec_match("bar", "created_before(foo)"); -is(join(",", $ret->influences), 'foo', "created_before is influenced by the comparison page"); -$ret=pagespec_match("bar", "created_after(foo)"); -is(join(",", $ret->influences), 'foo', "created_after is influenced by the comparison page"); -$ret=pagespec_match("bar", "link(quux) and created_after(foo)"); -is(join(",", sort $ret->influences), 'foo,quux', "influences add up over AND"); -$ret=pagespec_match("bar", "link(quux) and created_after(foo)"); -is(join(",", sort $ret->influences), 'foo,quux', "influences add up over OR"); -$ret=pagespec_match("bar", "!link(quux) and !created_after(foo)"); -is(join(",", sort $ret->influences), 'foo,quux', "influences unaffected by negation"); +my %i=pagespec_match("foo", "link(bar)")->influences; +is(join(",", keys %i), 'foo', "link is influenced by the page with the link"); +%i=pagespec_match("bar", "backlink(foo)")->influences; +is(join(",", keys %i), 'foo', "backlink is influenced by the page with the link"); +%i=pagespec_match("bar", "backlink(foo)")->influences; +is(join(",", keys %i), 'foo', "backlink is influenced by the page with the link"); +%i=pagespec_match("bar", "created_before(foo)")->influences; +is(join(",", keys %i), 'foo', "created_before is influenced by the comparison page"); +%i=pagespec_match("bar", "created_after(foo)")->influences; +is(join(",", keys %i), 'foo', "created_after is influenced by the comparison page"); +%i=pagespec_match("bar", "link(quux) and created_after(foo)")->influences; +is(join(",", sort keys %i), 'bar,foo', "influences add up over AND"); +%i=pagespec_match("bar", "link(quux) and created_after(foo)")->influences; +is(join(",", sort keys %i), 'bar,foo', "influences add up over OR"); +%i=pagespec_match("bar", "!link(quux) and !created_after(foo)")->influences; +is(join(",", sort keys %i), 'bar,foo', "influences unaffected by negation"); -- cgit v1.2.3 From 5e236f5d25b68f5fb4a421b24470419c6042cb1c Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Thu, 8 Oct 2009 16:49:53 -0400 Subject: add use_pagespec and deptype functions --- IkiWiki.pm | 135 +++++++++++++++++++++++++++++++++++++++++-------- debian/changelog | 4 +- doc/plugins/write.mdwn | 61 +++++++++++++++++----- t/use_pagespec.t | 30 +++++++++++ 4 files changed, 194 insertions(+), 36 deletions(-) create mode 100755 t/use_pagespec.t (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 2064c881a..c787612e1 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -17,11 +17,12 @@ use vars qw{%config %links %oldlinks %pagemtime %pagectime %pagecase %forcerebuild %loaded_plugins}; use Exporter q{import}; -our @EXPORT = qw(hook debug error template htmlpage add_depends pagespec_match - pagespec_match_list bestlink htmllink readfile writefile - pagetype srcfile pagename displaytime will_render gettext urlto - targetpage add_underlay pagetitle titlepage linkpage - newpagefile inject add_link +our @EXPORT = qw(hook debug error template htmlpage deptype use_pagespec + add_depends pagespec_match pagespec_match_list bestlink + htmllink readfile writefile pagetype srcfile pagename + displaytime will_render gettext urlto targetpage + add_underlay pagetitle titlepage linkpage newpagefile + inject add_link %config %links %pagestate %wikistate %renderedfiles %pagesources %destsources); our $VERSION = 3.00; # plugin interface version, next is ikiwiki version @@ -1768,18 +1769,10 @@ sub rcs_receive () { $hooks{rcs}{rcs_receive}{call}->(); } -sub add_depends ($$;@) { +sub add_depends ($$;$) { my $page=shift; my $pagespec=shift; - - my $deptype=0; - if (@_) { - my %params=@_; - - $deptype=$deptype | $DEPEND_PRESENCE if $params{presence}; - $deptype=$deptype | $DEPEND_LINKS if $params{links}; - } - $deptype=$DEPEND_CONTENT unless $deptype; + my $deptype=shift || $DEPEND_CONTENT; # Is the pagespec a simple page name? if ($pagespec =~ /$config{wiki_file_regexp}/ && @@ -1791,18 +1784,118 @@ sub add_depends ($$;@) { # Analyse the pagespec, and match it against all pages # to get a list of influences, and add explicit dependencies # for those. + #my $sub=pagespec_translate($pagespec); + #return if $@; + #foreach my $p (keys %pagesources) { + # my $r=$sub->($p, location => $page ); + # my %i=$r->influences; + # foreach my $i (keys %i) { + # $depends_simple{$page}{lc $i} |= $i{$i}; + # } + #} + print STDERR "warning: use of add_depends; influences not tracked\n"; + + $depends{$page}{$pagespec} |= $deptype; + return 1; +} + +sub use_pagespec ($$;@) { + my $page=shift; + my $pagespec=shift; + my %params=@_; + my $sub=pagespec_translate($pagespec); - return if $@; - foreach my $p (keys %pagesources) { - my $r=$sub->($p, location => $page ); - my %i=$r->influences; + error "syntax error in pagespec \"$pagespec\"" + if $@ || ! defined $sub; + + my @candidates; + if (exists $params{limit}) { + @candidates=grep { $params{limit}->($_) } keys %pagesources; + } + else { + @candidates=keys %pagesources; + } + + if (defined $params{sort}) { + my $f; + if ($params{sort} eq 'title') { + $f=sub { pagetitle(basename($a)) cmp pagetitle(basename($b)) }; + } + elsif ($params{sort} eq 'title_natural') { + eval q{use Sort::Naturally}; + if ($@) { + error(gettext("Sort::Naturally needed for title_natural sort")); + } + $f=sub { Sort::Naturally::ncmp(pagetitle(basename($a)), pagetitle(basename($b))) }; + } + elsif ($params{sort} eq 'mtime') { + $f=sub { $pagemtime{$b} <=> $pagemtime{$a} }; + } + elsif ($params{sort} eq 'age') { + $f=sub { $pagectime{$b} <=> $pagectime{$a} }; + } + else { + error sprintf(gettext("unknown sort type %s"), $params{sort}); + } + @candidates = sort { &$f } @candidates; + } + + @candidates=reverse(@candidates) if $params{reverse}; + + my @matches; + my $firstfail; + my $count=0; + foreach my $p (@candidates) { + my $r=$sub->($p, location => $page); + if ($r) { + push @matches, [$p, $r]; + last if defined $params{num} && ++$count == $params{num}; + } + elsif (! defined $firstfail) { + $firstfail=$r; + } + } + + $depends{$page}{$pagespec} |= ($params{deptype} || $DEPEND_CONTENT); + + my @ret; + if (@matches) { + # Add all influences from successful matches. + foreach my $m (@matches) { + push @ret, $m->[0]; + my %i=$m->[1]->influences; + foreach my $i (keys %i) { + $depends_simple{$page}{lc $i} |= $i{$i}; + } + } + } + elsif (defined $firstfail) { + # Add influences from one failure. (Which one should not + # matter; all should have the same influences.) + my %i=$firstfail->influences; foreach my $i (keys %i) { $depends_simple{$page}{lc $i} |= $i{$i}; } + error(sprintf(gettext("cannot match pages: %s"), $firstfail)); } - $depends{$page}{$pagespec} |= $deptype; - return 1; + return @ret; +} + +sub deptype (@) { + my $deptype=0; + foreach my $type (@_) { + if ($type eq 'presence') { + $deptype |= $DEPEND_PRESENCE; + } + elsif ($type eq 'links') { + $deptype |= $DEPEND_LINKS; + } + elsif ($type eq 'content') { + $deptype |= $DEPEND_CONTENT; + } + } + return $deptype; } sub file_pruned ($$) { diff --git a/debian/changelog b/debian/changelog index 565a0cffa..12ddebac9 100644 --- a/debian/changelog +++ b/debian/changelog @@ -13,7 +13,6 @@ ikiwiki (3.14159266) UNRELEASED; urgency=low * Added support framework for multiple types of dependencies. * Allow declaring that a dependency is only affected by page presence or changes to its links. - (By passing presence => 1 or links => 1 to add_depends.) * pagecount, calendar, postsparkline, progress: Use a presence dependency, which makes these directives much less expensive to use, since page edits will no longer trigger an unnecessary update. @@ -34,6 +33,9 @@ ikiwiki (3.14159266) UNRELEASED; urgency=low info. * Plugins providing PageSpec `match_*` functions should pass additional influence information when creating result objects. + * Added `use_pagespec` function, that plugins can use to find a list + of matching pages and add dependencies and influences, all at once, + and efficiently. -- Joey Hess Sun, 27 Sep 2009 17:40:03 -0400 diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn index 232430079..3d5650758 100644 --- a/doc/plugins/write.mdwn +++ b/doc/plugins/write.mdwn @@ -609,21 +609,52 @@ page created from it. (Ie, it appends ".html".) Use this when constructing the filename of a html file. Use `urlto` when generating a link to a page. -#### `add_depends($$;@)` +### `deptype(@)` + +Use this function to generate ikiwiki's internal representation of a +dependency type from one or more of these keywords: + +* `content` is the default. Any change to the content + of a page triggers the dependency. +* `presence` is only triggered by a change to the presence + of a page. +* `links` is only triggered by a change to the links of a page. + This includes when a link is added, removed, or changes what + it points to due to other changes. It does not include the + addition or removal of a duplicate link. + +If multiple types are specified, they are combined. + +#### `use_pagespec($$;@)` + +Passed a page name, and [[ikiwiki/PageSpec]], returns a list of pages +in the wiki that match the [[ikiwiki/PageSpec]]. + +The page will automatically be made to depend on the specified +[[ikiwiki/PageSpec]], so `add_depends` does not need to be called. This +is significantly more efficient than calling `add_depends` +followed by `pagespec_match_list`. You should use this anytime a plugin +needs to match a set of pages and generate something based on that list. + +Additional named parameters can be specified: + +* `deptype` optionally specifies the type of dependency to add. Use the + `deptype` function to generate a dependency type. +* `limit` is a reference to a function, that is called and passed a page, + and must return true for the page to be included. +* `sort` specifies a sort order for the list. See + [[ikiwiki/PageSpec/sorting]] for the avilable sort methods. +* `reverse` if true, sorts in reverse. +* `num` if nonzero, specifies the maximum number of matching pages that + will be returned. + +#### `add_depends($$;$)` Makes the specified page depend on the specified [[ikiwiki/PageSpec]]. By default, dependencies are full content dependencies, meaning that the page will be updated whenever anything matching the PageSpec is modified. -This default can be overridden by additional named parameters, which can be -used to indicate weaker types of dependencies: - -* `presence` if set to true, only the presence of a matching page triggers - the dependency. -* `links` if set to true, any change to links on a matching page - triggers the dependency. This includes when a link is added, removed, - or changes what it points to due to other changes. It does not include - the addition or removal of a duplicate link. +This can be overridden by passing a `deptype` value as the third parameter. #### `pagespec_match($$;@)` @@ -984,10 +1015,12 @@ IkiWiki::ErrorReason object explaining why. When constructing these objects, you should also include information about of any pages whose contents or other metadata influenced the result of the -match. For example, "backlink(foo)" is influenced by the contents of page foo; -"link(foo)" and "title(bar)" are influenced by the contents of any -page they match; "created_before(foo)" is influenced by the metadata of -foo; while "glob(*)" is not influenced by the contents of any page. +match. Do this by passing a list of pages, followed by `deptype` values. + +For example, "backlink(foo)" is influenced by the contents of page foo; +"link(foo)" and "title(bar)" are influenced by the contents of any page +they match; "created_before(foo)" is influenced by the metadata of foo; +while "glob(*)" is not influenced by the contents of any page. ### Setup plugins diff --git a/t/use_pagespec.t b/t/use_pagespec.t new file mode 100755 index 000000000..7b904075e --- /dev/null +++ b/t/use_pagespec.t @@ -0,0 +1,30 @@ +#!/usr/bin/perl +use warnings; +use strict; +use Test::More tests => 64; + +BEGIN { use_ok("IkiWiki"); } + +%pagesources=( + foo => "foo.mdwn", + bar => "bar.mdwn", + "post/1" => "post/1.mdwn", + "post/2" => "post/2.mdwn", + "post/3" => "post/3.mdwn", +); + +is_deeply([use_pagespec("foo", "bar")], ["bar"]); +is_deeply([sort(use_pagespec("foo", "post/*"))], ["post/1", "post/2", "post/3"]); +is_deeply([use_pagespec("foo", "post/*", sort => "title", reverse => 1)], + ["post/3", "post/2", "post/1"]); +is_deeply([use_pagespec("foo", "post/*", sort => "title", num => 2)], + ["post/1", "post/2"]); +is_deeply([use_pagespec("foo", "post/*", sort => "title", num => 50)], + ["post/1", "post/2", "post/3"]); +is_deeply([use_pagespec("foo", "post/*", sort => "title", + limit => sub { $_[0] !~ /3/}) ], + ["post/1", "post/2"]); +eval { use_pagespec("foo", "beep") }; +ok($@, "fails with error when unable to match anything"); +eval { use_pagespec("foo", "this is not a legal pagespec!") }; +ok($@, "fails with error when pagespec bad"); -- cgit v1.2.3 From d5f014449511460d4da22591379b30bd5de86145 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Thu, 8 Oct 2009 18:47:48 -0400 Subject: use_pagespec: do not fail with error when unable to match, unless it fails with an ErrorReason --- IkiWiki.pm | 4 +++- t/use_pagespec.t | 7 ++++--- 2 files changed, 7 insertions(+), 4 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index c787612e1..c735b26c8 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -1876,7 +1876,9 @@ sub use_pagespec ($$;@) { foreach my $i (keys %i) { $depends_simple{$page}{lc $i} |= $i{$i}; } - error(sprintf(gettext("cannot match pages: %s"), $firstfail)); + + error(sprintf(gettext("cannot match pages: %s"), $firstfail)) + if $firstfail->isa("IkiWiki::ErrorReason"); } return @ret; diff --git a/t/use_pagespec.t b/t/use_pagespec.t index 7b904075e..92d7977cf 100755 --- a/t/use_pagespec.t +++ b/t/use_pagespec.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 64; +use Test::More tests => 10; BEGIN { use_ok("IkiWiki"); } @@ -24,7 +24,8 @@ is_deeply([use_pagespec("foo", "post/*", sort => "title", num => 50)], is_deeply([use_pagespec("foo", "post/*", sort => "title", limit => sub { $_[0] !~ /3/}) ], ["post/1", "post/2"]); -eval { use_pagespec("foo", "beep") }; -ok($@, "fails with error when unable to match anything"); +my $r=eval { use_pagespec("foo", "beep") }; +ok(eval { use_pagespec("foo", "beep") } == 0); +ok(! $@, "does not fail with error when unable to match anything"); eval { use_pagespec("foo", "this is not a legal pagespec!") }; ok($@, "fails with error when pagespec bad"); -- cgit v1.2.3 From 955bcea2a7bacb98cb62a38faa78b05e0f430aac Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Thu, 8 Oct 2009 23:48:08 -0400 Subject: fix test broken by change to failing link() influence --- t/pagespec_match.t | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 't') diff --git a/t/pagespec_match.t b/t/pagespec_match.t index 36fa04370..ab3fcdd4b 100755 --- a/t/pagespec_match.t +++ b/t/pagespec_match.t @@ -103,9 +103,9 @@ is(join(",", keys %i), 'foo', "backlink is influenced by the page with the link" is(join(",", keys %i), 'foo', "created_before is influenced by the comparison page"); %i=pagespec_match("bar", "created_after(foo)")->influences; is(join(",", keys %i), 'foo', "created_after is influenced by the comparison page"); -%i=pagespec_match("bar", "link(quux) and created_after(foo)")->influences; +%i=pagespec_match("foo", "link(baz) and created_after(bar)")->influences; is(join(",", sort keys %i), 'bar,foo', "influences add up over AND"); -%i=pagespec_match("bar", "link(quux) and created_after(foo)")->influences; +%i=pagespec_match("foo", "link(baz) and created_after(bar)")->influences; is(join(",", sort keys %i), 'bar,foo', "influences add up over OR"); -%i=pagespec_match("bar", "!link(quux) and !created_after(foo)")->influences; +%i=pagespec_match("foo", "!link(baz) and !created_after(bar)")->influences; is(join(",", sort keys %i), 'bar,foo', "influences unaffected by negation"); -- cgit v1.2.3 From 5e7b2dea84a35163b599b88efc02cd7ef3e0ad46 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Thu, 8 Oct 2009 23:51:06 -0400 Subject: rename use_pagespec to pagespec_match_list To avoid breaking plugins, also support the old pagespec_match_list calling convention, with a deprecation warning. --- IkiWiki.pm | 183 ++++++++++++++++++---------------------- IkiWiki/Plugin/calendar.pm | 2 +- IkiWiki/Plugin/inline.pm | 8 +- IkiWiki/Plugin/map.pm | 3 +- IkiWiki/Plugin/orphans.pm | 2 +- IkiWiki/Plugin/pagecount.pm | 2 +- IkiWiki/Plugin/pagestats.pm | 9 +- IkiWiki/Plugin/postsparkline.pm | 2 +- debian/changelog | 6 +- doc/plugins/write.mdwn | 24 ++---- t/pagespec_match_list.t | 31 +++++++ t/use_pagespec.t | 31 ------- 12 files changed, 140 insertions(+), 163 deletions(-) create mode 100755 t/pagespec_match_list.t delete mode 100755 t/use_pagespec.t (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index daa71059b..fd7e23524 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -17,7 +17,7 @@ use vars qw{%config %links %oldlinks %pagemtime %pagectime %pagecase %forcerebuild %loaded_plugins}; use Exporter q{import}; -our @EXPORT = qw(hook debug error template htmlpage deptype use_pagespec +our @EXPORT = qw(hook debug error template htmlpage deptype add_depends pagespec_match pagespec_match_list bestlink htmllink readfile writefile pagetype srcfile pagename displaytime will_render gettext urlto targetpage @@ -1798,91 +1798,6 @@ sub add_depends ($$;$) { return 1; } -sub use_pagespec ($$;@) { - my $page=shift; - my $pagespec=shift; - my %params=@_; - - my $sub=pagespec_translate($pagespec); - error "syntax error in pagespec \"$pagespec\"" - if $@ || ! defined $sub; - - my @candidates; - if (exists $params{limit}) { - @candidates=grep { $params{limit}->($_) } keys %pagesources; - } - else { - @candidates=keys %pagesources; - } - - if (defined $params{sort}) { - my $f; - if ($params{sort} eq 'title') { - $f=sub { pagetitle(basename($a)) cmp pagetitle(basename($b)) }; - } - elsif ($params{sort} eq 'title_natural') { - eval q{use Sort::Naturally}; - if ($@) { - error(gettext("Sort::Naturally needed for title_natural sort")); - } - $f=sub { Sort::Naturally::ncmp(pagetitle(basename($a)), pagetitle(basename($b))) }; - } - elsif ($params{sort} eq 'mtime') { - $f=sub { $pagemtime{$b} <=> $pagemtime{$a} }; - } - elsif ($params{sort} eq 'age') { - $f=sub { $pagectime{$b} <=> $pagectime{$a} }; - } - else { - error sprintf(gettext("unknown sort type %s"), $params{sort}); - } - @candidates = sort { &$f } @candidates; - } - - @candidates=reverse(@candidates) if $params{reverse}; - - my @matches; - my $firstfail; - my $count=0; - foreach my $p (@candidates) { - my $r=$sub->($p, location => $page); - if ($r) { - push @matches, [$p, $r]; - last if defined $params{num} && ++$count == $params{num}; - } - elsif (! defined $firstfail) { - $firstfail=$r; - } - } - - $depends{$page}{$pagespec} |= ($params{deptype} || $DEPEND_CONTENT); - - my @ret; - if (@matches) { - # Add all influences from successful matches. - foreach my $m (@matches) { - push @ret, $m->[0]; - my %i=$m->[1]->influences; - foreach my $i (keys %i) { - $depends_simple{$page}{lc $i} |= $i{$i}; - } - } - } - elsif (defined $firstfail) { - # Add influences from one failure. (Which one should not - # matter; all should have the same influences.) - my %i=$firstfail->influences; - foreach my $i (keys %i) { - $depends_simple{$page}{lc $i} |= $i{$i}; - } - - error(sprintf(gettext("cannot match pages: %s"), $firstfail)) - if $firstfail->isa("IkiWiki::ErrorReason"); - } - - return @ret; -} - sub deptype (@) { my $deptype=0; foreach my $type (@_) { @@ -2055,27 +1970,95 @@ sub pagespec_match ($$;@) { } sub pagespec_match_list ($$;@) { - my $pages=shift; - my $spec=shift; - my @params=@_; + my $page=shift; + my $pagespec=shift; + my %params=@_; - my $sub=pagespec_translate($spec); - error "syntax error in pagespec \"$spec\"" + # Backwards compatability with old calling convention. + if (ref $page) { + print STDERR "warning: a plugin (".caller().") is using pagespec_match_list in an obsolete way, and needs to be updated\n"; + $params{list}=$page; + $page=$params{location}; # ugh! + } + + my $sub=pagespec_translate($pagespec); + error "syntax error in pagespec \"$pagespec\"" if $@ || ! defined $sub; + + my @candidates; + if (exists $params{limit}) { + @candidates=grep { $params{limit}->($_) } keys %pagesources; + } + else { + @candidates=keys %pagesources; + } + + if (defined $params{sort}) { + my $f; + if ($params{sort} eq 'title') { + $f=sub { pagetitle(basename($a)) cmp pagetitle(basename($b)) }; + } + elsif ($params{sort} eq 'title_natural') { + eval q{use Sort::Naturally}; + if ($@) { + error(gettext("Sort::Naturally needed for title_natural sort")); + } + $f=sub { Sort::Naturally::ncmp(pagetitle(basename($a)), pagetitle(basename($b))) }; + } + elsif ($params{sort} eq 'mtime') { + $f=sub { $pagemtime{$b} <=> $pagemtime{$a} }; + } + elsif ($params{sort} eq 'age') { + $f=sub { $pagectime{$b} <=> $pagectime{$a} }; + } + else { + error sprintf(gettext("unknown sort type %s"), $params{sort}); + } + @candidates = sort { &$f } @candidates; + } + + @candidates=reverse(@candidates) if $params{reverse}; - my @ret; - my $r; - foreach my $page (@$pages) { - $r=$sub->($page, @params); - push @ret, $page if $r; + my @matches; + my $firstfail; + my $count=0; + foreach my $p (@candidates) { + my $r=$sub->($p, location => $page); + if ($r) { + push @matches, [$p, $r]; + last if defined $params{num} && ++$count == $params{num}; + } + elsif (! defined $firstfail) { + $firstfail=$r; + } } + + $depends{$page}{$pagespec} |= ($params{deptype} || $DEPEND_CONTENT); - if (! @ret && defined $r && $r->isa("IkiWiki::ErrorReason")) { - error(sprintf(gettext("cannot match pages: %s"), $r)); + my @ret; + if (@matches) { + # Add all influences from successful matches. + foreach my $m (@matches) { + push @ret, $m->[0]; + my %i=$m->[1]->influences; + foreach my $i (keys %i) { + $depends_simple{$page}{lc $i} |= $i{$i}; + } + } } - else { - return @ret; + elsif (defined $firstfail) { + # Add influences from one failure. (Which one should not + # matter; all should have the same influences.) + my %i=$firstfail->influences; + foreach my $i (keys %i) { + $depends_simple{$page}{lc $i} |= $i{$i}; + } + + error(sprintf(gettext("cannot match pages: %s"), $firstfail)) + if $firstfail->isa("IkiWiki::ErrorReason"); } + + return @ret; } sub pagespec_valid ($) { diff --git a/IkiWiki/Plugin/calendar.pm b/IkiWiki/Plugin/calendar.pm index a89175cfb..c50d038df 100644 --- a/IkiWiki/Plugin/calendar.pm +++ b/IkiWiki/Plugin/calendar.pm @@ -74,7 +74,7 @@ sub format_month (@) { my $nyear = $params{nyear}; my %linkcache; - foreach my $p (use_pagespec($params{page}, $params{pagespec}, + foreach my $p (pagespec_match_list($params{page}, $params{pagespec}, # add presence dependencies to update # month calendar when pages are added/removed deptype => deptype("presence"))) { diff --git a/IkiWiki/Plugin/inline.pm b/IkiWiki/Plugin/inline.pm index c02137aed..815a37838 100644 --- a/IkiWiki/Plugin/inline.pm +++ b/IkiWiki/Plugin/inline.pm @@ -216,7 +216,7 @@ sub preprocess_inline (@) { $num+=$params{skip}; } - @list = use_pagespec($params{page}, $params{pages}, + @list = pagespec_match_list($params{page}, $params{pages}, deptype => deptype($quick ? "presence" : "content"), limit => sub { $_[0] ne $params{page} }, sort => exists $params{sort} ? $params{sort} : "age", @@ -245,9 +245,11 @@ sub preprocess_inline (@) { } if ($feeds && exists $params{feedpages}) { - @feedlist = use_pagespec($params{page}, "($params{pages}) and ($params{feedpages})", + @feedlist = pagespec_match_list( + $params{page}, "($params{pages}) and ($params{feedpages})", deptype => deptype($quick ? "presence" : "content"), - list => \@feedlist); + list => \@feedlist, + ); } my ($feedbase, $feednum); diff --git a/IkiWiki/Plugin/map.pm b/IkiWiki/Plugin/map.pm index 634b0e4d6..788b96827 100644 --- a/IkiWiki/Plugin/map.pm +++ b/IkiWiki/Plugin/map.pm @@ -36,7 +36,8 @@ sub preprocess (@) { # Get all the items to map. my %mapitems; - foreach my $page (use_pagespec($params{page}, $params{pages}, deptype => $deptype)) { + foreach my $page (pagespec_match_list($params{page}, $params{pages}, + deptype => $deptype)) { if (exists $params{show} && exists $pagestate{$page} && exists $pagestate{$page}{meta}{$params{show}}) { diff --git a/IkiWiki/Plugin/orphans.pm b/IkiWiki/Plugin/orphans.pm index 607239500..b1ebd1b9d 100644 --- a/IkiWiki/Plugin/orphans.pm +++ b/IkiWiki/Plugin/orphans.pm @@ -28,7 +28,7 @@ sub preprocess (@) { # considering as orphans. add_depends($params{page}, "*", deptype("links")); - my @orphans=use_pagespec($params{page}, $params{pages}, + my @orphans=pagespec_match_list($params{page}, $params{pages}, # update when orphans are added/removed deptype => deptype("presence"), limit => sub { diff --git a/IkiWiki/Plugin/pagecount.pm b/IkiWiki/Plugin/pagecount.pm index 40474b2a1..8d36f057e 100644 --- a/IkiWiki/Plugin/pagecount.pm +++ b/IkiWiki/Plugin/pagecount.pm @@ -32,7 +32,7 @@ sub preprocess (@) { return scalar keys %pagesources; } - return scalar use_pagespec($params{page}, $pages, + return scalar pagespec_match_list($params{page}, $pages, deptype => deptype("presence")); } diff --git a/IkiWiki/Plugin/pagestats.pm b/IkiWiki/Plugin/pagestats.pm index e64f7d9c3..47638210a 100644 --- a/IkiWiki/Plugin/pagestats.pm +++ b/IkiWiki/Plugin/pagestats.pm @@ -37,16 +37,17 @@ sub preprocess (@) { my %counts; my $max = 0; - foreach my $page (use_pagespec($params{page}, $params{pages}, - # update when a displayed page is added or removed - deptype => deptype("presence"))) { + foreach my $page (pagespec_match_list($params{page}, $params{pages}, + # update when a displayed page is added/removed + deptype => deptype("presence"))) { use IkiWiki::Render; my @backlinks = IkiWiki::backlink_pages($page); if (exists $params{among}) { # only consider backlinks from the amoung pages - @backlinks = use_pagespec($params{page}, $params{among}, + @backlinks = pagespec_match_list( + $params{page}, $params{among}, # update whenever links on those pages change deptype => deptype("links"), list => \@backlinks diff --git a/IkiWiki/Plugin/postsparkline.pm b/IkiWiki/Plugin/postsparkline.pm index f51e309c8..1d4532366 100644 --- a/IkiWiki/Plugin/postsparkline.pm +++ b/IkiWiki/Plugin/postsparkline.pm @@ -54,7 +54,7 @@ sub preprocess (@) { } my @list=sort { $params{timehash}->{$b} <=> $params{timehash}->{$a} } - use_pagespec($params{page}, $params{pages}, + pagespec_match_list($params{page}, $params{pages}, deptype => $deptype, limit => sub { $_[0] ne $params{page} }, ); diff --git a/debian/changelog b/debian/changelog index 3a6fdf77d..49809e6cf 100644 --- a/debian/changelog +++ b/debian/changelog @@ -33,9 +33,9 @@ ikiwiki (3.14159266) UNRELEASED; urgency=low info. * Plugins providing PageSpec `match_*` functions should pass additional influence information when creating result objects. - * Added `use_pagespec` function, that plugins can use to find a list - of matching pages and add dependencies and influences, all at once, - and efficiently. + * API change: `pagespec_match_list` has completly changed its interface. + The old interface will be removed soon, and a warning will be printed + if any plugins try to use it. * Optimize away most expensive file prune calls, when refreshing, by only checking new files. diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn index 62bebbeed..f6ea76c36 100644 --- a/doc/plugins/write.mdwn +++ b/doc/plugins/write.mdwn @@ -625,16 +625,16 @@ dependency type from one or more of these keywords: If multiple types are specified, they are combined. -#### `use_pagespec($$;@)` +#### `pagespec_match_list($$;@)` Passed a page name, and [[ikiwiki/PageSpec]], returns a list of pages in the wiki that match the [[ikiwiki/PageSpec]]. The page will automatically be made to depend on the specified [[ikiwiki/PageSpec]], so `add_depends` does not need to be called. This -is significantly more efficient than calling `add_depends` -followed by `pagespec_match_list`. You should use this anytime a plugin -needs to match a set of pages and generate something based on that list. +is significantly more efficient than calling `add_depends` and +`pagespec_match` in a loop. You should use this anytime a plugin +needs to match a set of pages and do something based on that list. Additional named parameters can be specified: @@ -650,6 +650,9 @@ Additional named parameters can be specified: * `list` makes it only match amoung the specified list of pages. Default is to match amoung all pages in the wiki. +Unlike pagespec_match, this may throw an error if there is an error in +the pagespec. + #### `add_depends($$;$)` Makes the specified page depend on the specified [[ikiwiki/PageSpec]]. @@ -672,19 +675,6 @@ The most often used is "location", which specifies the location the PageSpec should match against. If not passed, relative PageSpecs will match relative to the top of the wiki. -#### `pagespec_match_list($$;@)` - -Passed a reference to a list of page names, and [[ikiwiki/PageSpec]], -returns the set of pages that match the [[ikiwiki/PageSpec]]. - -Additional named parameters can be passed, to further limit the match. -The most often used is "location", which specifies the location the -PageSpec should match against. If not passed, relative PageSpecs will match -relative to the top of the wiki. - -Unlike pagespec_match, this may throw an error if there is an error in -the pagespec. - #### `bestlink($$)` Given a page and the text of a link on the page, determine which diff --git a/t/pagespec_match_list.t b/t/pagespec_match_list.t new file mode 100755 index 000000000..85a30b542 --- /dev/null +++ b/t/pagespec_match_list.t @@ -0,0 +1,31 @@ +#!/usr/bin/perl +use warnings; +use strict; +use Test::More tests => 10; + +BEGIN { use_ok("IkiWiki"); } + +%pagesources=( + foo => "foo.mdwn", + bar => "bar.mdwn", + "post/1" => "post/1.mdwn", + "post/2" => "post/2.mdwn", + "post/3" => "post/3.mdwn", +); + +is_deeply([pagespec_match_list("foo", "bar")], ["bar"]); +is_deeply([sort(pagespec_match_list("foo", "post/*"))], ["post/1", "post/2", "post/3"]); +is_deeply([pagespec_match_list("foo", "post/*", sort => "title", reverse => 1)], + ["post/3", "post/2", "post/1"]); +is_deeply([pagespec_match_list("foo", "post/*", sort => "title", num => 2)], + ["post/1", "post/2"]); +is_deeply([pagespec_match_list("foo", "post/*", sort => "title", num => 50)], + ["post/1", "post/2", "post/3"]); +is_deeply([pagespec_match_list("foo", "post/*", sort => "title", + limit => sub { $_[0] !~ /3/}) ], + ["post/1", "post/2"]); +my $r=eval { pagespec_match_list("foo", "beep") }; +ok(eval { pagespec_match_list("foo", "beep") } == 0); +ok(! $@, "does not fail with error when unable to match anything"); +eval { pagespec_match_list("foo", "this is not a legal pagespec!") }; +ok($@, "fails with error when pagespec bad"); diff --git a/t/use_pagespec.t b/t/use_pagespec.t deleted file mode 100755 index 92d7977cf..000000000 --- a/t/use_pagespec.t +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/perl -use warnings; -use strict; -use Test::More tests => 10; - -BEGIN { use_ok("IkiWiki"); } - -%pagesources=( - foo => "foo.mdwn", - bar => "bar.mdwn", - "post/1" => "post/1.mdwn", - "post/2" => "post/2.mdwn", - "post/3" => "post/3.mdwn", -); - -is_deeply([use_pagespec("foo", "bar")], ["bar"]); -is_deeply([sort(use_pagespec("foo", "post/*"))], ["post/1", "post/2", "post/3"]); -is_deeply([use_pagespec("foo", "post/*", sort => "title", reverse => 1)], - ["post/3", "post/2", "post/1"]); -is_deeply([use_pagespec("foo", "post/*", sort => "title", num => 2)], - ["post/1", "post/2"]); -is_deeply([use_pagespec("foo", "post/*", sort => "title", num => 50)], - ["post/1", "post/2", "post/3"]); -is_deeply([use_pagespec("foo", "post/*", sort => "title", - limit => sub { $_[0] !~ /3/}) ], - ["post/1", "post/2"]); -my $r=eval { use_pagespec("foo", "beep") }; -ok(eval { use_pagespec("foo", "beep") } == 0); -ok(! $@, "does not fail with error when unable to match anything"); -eval { use_pagespec("foo", "this is not a legal pagespec!") }; -ok($@, "fails with error when pagespec bad"); -- cgit v1.2.3 From 34b213235529300c8be19290cefb1e5a01e2da3a Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Fri, 9 Oct 2009 00:06:58 -0400 Subject: fix test to use new calling convention This test still fails several cases, since add_depends influence testing is currently commented out. --- t/add_depends.t | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 't') diff --git a/t/add_depends.t b/t/add_depends.t index 9f426187b..fce7a76c6 100755 --- a/t/add_depends.t +++ b/t/add_depends.t @@ -31,15 +31,15 @@ ok($IkiWiki::depends_simple{foo0}{"bar"} & $IkiWiki::DEPEND_CONTENT); ok(! ($IkiWiki::depends_simple{foo0}{"bar"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); # adding other dep types standalone -ok(add_depends("foo2", "*", presence => 1)); -ok(add_depends("foo2", "bar", links => 1)); +ok(add_depends("foo2", "*", deptype("presence"))); +ok(add_depends("foo2", "bar", deptype("links"))); ok($IkiWiki::depends{foo2}{"*"} & $IkiWiki::DEPEND_PRESENCE); ok(! ($IkiWiki::depends{foo2}{"*"} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); ok($IkiWiki::depends_simple{foo2}{"bar"} & $IkiWiki::DEPEND_LINKS); ok(! ($IkiWiki::depends_simple{foo2}{"bar"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_CONTENT))); # adding combined dep types -ok(add_depends("foo2", "baz", links => 1, presence => 1)); +ok(add_depends("foo2", "baz", deptype("links". "presence"))); ok($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_LINKS); ok($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_PRESENCE); ok(! ($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_CONTENT)); @@ -49,10 +49,10 @@ ok(add_depends("foo2", "baz")); ok($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_LINKS); ok($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_PRESENCE); ok(($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_CONTENT)); -ok(add_depends("foo2", "bar", presence => 1)); # had only links before +ok(add_depends("foo2", "bar", deptype("presence"))); # had only links before ok($IkiWiki::depends_simple{foo2}{"bar"} & ($IkiWiki::DEPEND_LINKS | $IkiWiki::DEPEND_PRESENCE)); ok(! ($IkiWiki::depends_simple{foo2}{"bar"} & $IkiWiki::DEPEND_CONTENT)); -ok(add_depends("foo0", "bar", links => 1)); # had only content before +ok(add_depends("foo0", "bar", deptype("links"))); # had only content before ok($IkiWiki::depends{foo0}{"*"} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS)); ok(! ($IkiWiki::depends{foo0}{"*"} & $IkiWiki::DEPEND_PRESENCE)); @@ -60,11 +60,11 @@ ok(! ($IkiWiki::depends{foo0}{"*"} & $IkiWiki::DEPEND_PRESENCE)); # as an explicit dependency. In the case of a link, a links dependency. $links{foo0}=$links{foo9}=[qw{bar baz}]; foreach my $spec ("* and ! link(bar)", "* or link(bar)") { - ok(add_depends("foo3", $spec, presence => 1)); + ok(add_depends("foo3", $spec, deptype("presence"))); ok($IkiWiki::depends{foo3}{$spec} & $IkiWiki::DEPEND_PRESENCE); ok(! ($IkiWiki::depends{foo3}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); ok($IkiWiki::depends_simple{foo3}{foo3} == $IkiWiki::DEPEND_LINKS); - ok(add_depends("foo4", $spec, links => 1)); + ok(add_depends("foo4", $spec, deptype("links"))); ok($IkiWiki::depends{foo4}{$spec} & $IkiWiki::DEPEND_LINKS); ok(! ($IkiWiki::depends{foo4}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_PRESENCE))); ok($IkiWiki::depends_simple{foo4}{foo4} == $IkiWiki::DEPEND_LINKS); @@ -73,15 +73,15 @@ foreach my $spec ("* and ! link(bar)", "* or link(bar)") { # a pagespec with backlinks() will add as an influence the page with the links $links{foo0}=[qw{foo5 foo7}]; foreach my $spec ("bugs or (backlink(foo0) and !*.png)", "backlink(foo)") { - ok(add_depends("foo5", $spec, presence => 1)); + ok(add_depends("foo5", $spec, deptype("presence"))); ok($IkiWiki::depends{foo5}{$spec} & $IkiWiki::DEPEND_PRESENCE); ok(! ($IkiWiki::depends{foo5}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); ok($IkiWiki::depends_simple{foo5}{foo0} == $IkiWiki::DEPEND_LINKS); - ok(add_depends("foo6", $spec, links => 1)); + ok(add_depends("foo6", $spec, deptype("links"))); ok($IkiWiki::depends{foo6}{$spec} & $IkiWiki::DEPEND_LINKS); ok(! ($IkiWiki::depends{foo6}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_CONTENT))); ok($IkiWiki::depends_simple{foo5}{foo0} == $IkiWiki::DEPEND_LINKS); - ok(add_depends("foo7", $spec, presence => 1, links => 1)); + ok(add_depends("foo7", $spec, deptype("presence", "links"))); ok($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_PRESENCE); ok($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_LINKS); ok(! ($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_CONTENT)); @@ -93,6 +93,6 @@ foreach my $spec ("bugs or (backlink(foo0) and !*.png)", "backlink(foo)") { } # content is the default if unknown types are entered -ok(add_depends("foo9", "*", presenCe => 1)); +ok(add_depends("foo9", "*", deptype("monkey"))); ok($IkiWiki::depends{foo9}{"*"} & $IkiWiki::DEPEND_CONTENT); ok(! ($IkiWiki::depends{foo9}{"*"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); -- cgit v1.2.3 From b7351daacd0d4a041a51b43d99b7bf589de54f53 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Fri, 9 Oct 2009 12:54:13 -0400 Subject: conditional: use yesno --- IkiWiki.pm | 2 +- IkiWiki/Plugin/conditional.pm | 2 +- t/yesno.t | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index fd7e23524..475e278c3 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -1792,7 +1792,7 @@ sub add_depends ($$;$) { # $depends_simple{$page}{lc $i} |= $i{$i}; # } #} - print STDERR "warning: use of add_depends; influences not tracked\n"; + print STDERR "warning: use of add_depends by ".caller()."; influences not tracked\n"; $depends{$page}{$pagespec} |= $deptype; return 1; diff --git a/IkiWiki/Plugin/conditional.pm b/IkiWiki/Plugin/conditional.pm index 7445dbdad..7ead701f2 100644 --- a/IkiWiki/Plugin/conditional.pm +++ b/IkiWiki/Plugin/conditional.pm @@ -29,7 +29,7 @@ sub preprocess_if (@) { } my $result=0; - if ((exists $params{all} && lc $params{all} eq "no") || + if (! IkiWiki::yesno($params{all}) || # An optimisation to avoid needless looping over every page # and adding of dependencies for simple uses of some of the # tests. diff --git a/t/yesno.t b/t/yesno.t index 60a8c071d..8770390a1 100755 --- a/t/yesno.t +++ b/t/yesno.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 10; +use Test::More tests => 11; BEGIN { use_ok("IkiWiki"); } @@ -19,3 +19,5 @@ ok(IkiWiki::yesno("NO") == 0); ok(IkiWiki::yesno("1") == 1); ok(IkiWiki::yesno("0") == 0); ok(IkiWiki::yesno("mooooooooooo") == 0); + +ok(IkiWiki::yesno(undef) == 0); -- cgit v1.2.3 From 9eb229bfc73066e5d12d5a199c77f63d5881dfb7 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Fri, 9 Oct 2009 14:05:22 -0400 Subject: move influence tests to pagespec_match_list.t --- t/add_depends.t | 36 ------------------------------ t/pagespec_match_list.t | 58 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 38 deletions(-) (limited to 't') diff --git a/t/add_depends.t b/t/add_depends.t index fce7a76c6..e3f0b6603 100755 --- a/t/add_depends.t +++ b/t/add_depends.t @@ -56,42 +56,6 @@ ok(add_depends("foo0", "bar", deptype("links"))); # had only content before ok($IkiWiki::depends{foo0}{"*"} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS)); ok(! ($IkiWiki::depends{foo0}{"*"} & $IkiWiki::DEPEND_PRESENCE)); -# Adding a pagespec that requires page metadata should add the influence -# as an explicit dependency. In the case of a link, a links dependency. -$links{foo0}=$links{foo9}=[qw{bar baz}]; -foreach my $spec ("* and ! link(bar)", "* or link(bar)") { - ok(add_depends("foo3", $spec, deptype("presence"))); - ok($IkiWiki::depends{foo3}{$spec} & $IkiWiki::DEPEND_PRESENCE); - ok(! ($IkiWiki::depends{foo3}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); - ok($IkiWiki::depends_simple{foo3}{foo3} == $IkiWiki::DEPEND_LINKS); - ok(add_depends("foo4", $spec, deptype("links"))); - ok($IkiWiki::depends{foo4}{$spec} & $IkiWiki::DEPEND_LINKS); - ok(! ($IkiWiki::depends{foo4}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_PRESENCE))); - ok($IkiWiki::depends_simple{foo4}{foo4} == $IkiWiki::DEPEND_LINKS); -} - -# a pagespec with backlinks() will add as an influence the page with the links -$links{foo0}=[qw{foo5 foo7}]; -foreach my $spec ("bugs or (backlink(foo0) and !*.png)", "backlink(foo)") { - ok(add_depends("foo5", $spec, deptype("presence"))); - ok($IkiWiki::depends{foo5}{$spec} & $IkiWiki::DEPEND_PRESENCE); - ok(! ($IkiWiki::depends{foo5}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); - ok($IkiWiki::depends_simple{foo5}{foo0} == $IkiWiki::DEPEND_LINKS); - ok(add_depends("foo6", $spec, deptype("links"))); - ok($IkiWiki::depends{foo6}{$spec} & $IkiWiki::DEPEND_LINKS); - ok(! ($IkiWiki::depends{foo6}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_CONTENT))); - ok($IkiWiki::depends_simple{foo5}{foo0} == $IkiWiki::DEPEND_LINKS); - ok(add_depends("foo7", $spec, deptype("presence", "links"))); - ok($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_PRESENCE); - ok($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_LINKS); - ok(! ($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_CONTENT)); - ok($IkiWiki::depends_simple{foo7}{foo0} == $IkiWiki::DEPEND_LINKS); - ok(add_depends("foo8", $spec)); - ok($IkiWiki::depends{foo8}{$spec} & $IkiWiki::DEPEND_CONTENT); - ok(! ($IkiWiki::depends{foo8}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); - ok($IkiWiki::depends_simple{foo8}{foo0} == $IkiWiki::DEPEND_LINKS); -} - # content is the default if unknown types are entered ok(add_depends("foo9", "*", deptype("monkey"))); ok($IkiWiki::depends{foo9}{"*"} & $IkiWiki::DEPEND_CONTENT); diff --git a/t/pagespec_match_list.t b/t/pagespec_match_list.t index 85a30b542..301197cc1 100755 --- a/t/pagespec_match_list.t +++ b/t/pagespec_match_list.t @@ -1,19 +1,29 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 10; +use Test::More tests => 49; BEGIN { use_ok("IkiWiki"); } +%config=IkiWiki::defaultconfig(); +$config{srcdir}=$config{destdir}="/dev/null"; +IkiWiki::checkconfig(); + %pagesources=( foo => "foo.mdwn", + foo2 => "foo2.mdwn", + foo3 => "foo3.mdwn", bar => "bar.mdwn", "post/1" => "post/1.mdwn", "post/2" => "post/2.mdwn", "post/3" => "post/3.mdwn", ); +$links{foo}=[qw{post/1 post/2}]; +$links{foo2}=[qw{bar}]; +$links{foo3}=[qw{bar}]; is_deeply([pagespec_match_list("foo", "bar")], ["bar"]); +is_deeply([sort(pagespec_match_list("foo", "* and !post/*"))], ["bar", "foo", "foo2", "foo3"]); is_deeply([sort(pagespec_match_list("foo", "post/*"))], ["post/1", "post/2", "post/3"]); is_deeply([pagespec_match_list("foo", "post/*", sort => "title", reverse => 1)], ["post/3", "post/2", "post/1"]); @@ -22,10 +32,54 @@ is_deeply([pagespec_match_list("foo", "post/*", sort => "title", num => 2)], is_deeply([pagespec_match_list("foo", "post/*", sort => "title", num => 50)], ["post/1", "post/2", "post/3"]); is_deeply([pagespec_match_list("foo", "post/*", sort => "title", - limit => sub { $_[0] !~ /3/}) ], + filter => sub { $_[0] =~ /3/}) ], ["post/1", "post/2"]); my $r=eval { pagespec_match_list("foo", "beep") }; ok(eval { pagespec_match_list("foo", "beep") } == 0); ok(! $@, "does not fail with error when unable to match anything"); eval { pagespec_match_list("foo", "this is not a legal pagespec!") }; ok($@, "fails with error when pagespec bad"); + +# A pagespec that requires page metadata should add influences +# as an explicit dependency. In the case of a link, a links dependency. +foreach my $spec ("* and link(bar)", "* or link(bar)") { + pagespec_match_list("foo2", $spec, deptype => deptype("presence")); + ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_PRESENCE); + ok(! ($IkiWiki::depends{foo2}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); + ok($IkiWiki::depends_simple{foo2}{foo2} == $IkiWiki::DEPEND_LINKS); + %IkiWiki::depends_simple=(); + %IkiWiki::depends=(); + pagespec_match_list("foo3", $spec, deptype => deptype("links")); + ok($IkiWiki::depends{foo3}{$spec} & $IkiWiki::DEPEND_LINKS); + ok(! ($IkiWiki::depends{foo3}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_PRESENCE))); + ok($IkiWiki::depends_simple{foo3}{foo3} == $IkiWiki::DEPEND_LINKS); + %IkiWiki::depends_simple=(); + %IkiWiki::depends=(); +} + +# a pagespec with backlinks() will add as an influence the page with the links +foreach my $spec ("bar or (backlink(foo) and !*.png)", "backlink(foo)") { + pagespec_match_list("foo2", $spec, deptype => deptype("presence")); + ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_PRESENCE); + ok(! ($IkiWiki::depends{foo2}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); + ok($IkiWiki::depends_simple{foo2}{foo} == $IkiWiki::DEPEND_LINKS); + %IkiWiki::depends_simple=(); + %IkiWiki::depends=(); + pagespec_match_list("foo2", $spec, deptype => deptype("links")); + ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_LINKS); + ok(! ($IkiWiki::depends{foo2}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_CONTENT))); + ok($IkiWiki::depends_simple{foo2}{foo} == $IkiWiki::DEPEND_LINKS); + %IkiWiki::depends_simple=(); + %IkiWiki::depends=(); + pagespec_match_list("foo2", $spec, deptype => deptype("presence", "links")); + ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_PRESENCE); + ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_LINKS); + ok(! ($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_CONTENT)); + ok($IkiWiki::depends_simple{foo2}{foo} == $IkiWiki::DEPEND_LINKS); + %IkiWiki::depends_simple=(); + %IkiWiki::depends=(); + pagespec_match_list("foo2", $spec); + ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_CONTENT); + ok(! ($IkiWiki::depends{foo2}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); + ok($IkiWiki::depends_simple{foo2}{foo} == $IkiWiki::DEPEND_LINKS); +} -- cgit v1.2.3 From 6d6fff8d87ae304427e222d20e4e49e671e3b0f3 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Fri, 9 Oct 2009 21:12:14 -0400 Subject: added tests of SuccessReason/FailReason objects --- t/pagespec_match_result.t | 54 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100755 t/pagespec_match_result.t (limited to 't') diff --git a/t/pagespec_match_result.t b/t/pagespec_match_result.t new file mode 100755 index 000000000..2319d8733 --- /dev/null +++ b/t/pagespec_match_result.t @@ -0,0 +1,54 @@ +#!/usr/bin/perl +use warnings; +use strict; +use Test::More tests => 67; + +BEGIN { use_ok("IkiWiki"); } + +# Note that new objects have to be constructed freshly for each test, since +# object states are mutated as they are combined. +sub S { IkiWiki::SuccessReason->new("match") } +sub F { IkiWiki::FailReason->new("no match") } +sub E { IkiWiki::ErrorReason->new("error in matching") } + +ok(S() eq "match"); +ok(F() eq "no match"); +ok(E() eq "error in matching"); + +ok(S()); +ok(! F()); +ok(! E()); + +ok(!(! S())); +ok(!(!(! F))); +ok(!(!(! E))); + +ok(S() | F()); +ok(F() | S()); +ok(!(F() | E())); +ok(!(!S() | F() | E())); + +ok(S() & S() & S()); +ok(!(S() & E())); +ok(!(S() & F())); +ok(!(S() & F() & E())); +ok(S() & (F() | F() | S())); + +# influences are always merged, no matter the operation performed, +# as long as the two items are always both present +foreach my $op ('$s | $f', '$s & $f', '$s & $f & E()', '$s | E() | $f', + '! $s | ! $f', '!(!(!$s)) | $f') { + my $s=S(); + $s->influences(foo => 1, bar => 1); + is($s->influences->{foo}, 1); + is($s->influences->{bar}, 1); + my $f=F(); + $f->influences(bar => 2, baz => 1); + is($f->influences->{bar}, 2); + is($f->influences->{baz}, 1); + my $c = eval $op; + ok(ref $c); + is($c->influences->{foo}, 1, "foo ($op)"); + is($c->influences->{bar}, (1 | 2), "bar ($op)"); + is($c->influences->{baz}, 1, "baz ($op)"); +} -- cgit v1.2.3 From ba00fb319c6332977ac0cc78937151add5b91335 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Fri, 9 Oct 2009 21:23:39 -0400 Subject: update --- t/pagespec_match_result.t | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) (limited to 't') diff --git a/t/pagespec_match_result.t b/t/pagespec_match_result.t index 2319d8733..d9c31d6f0 100755 --- a/t/pagespec_match_result.t +++ b/t/pagespec_match_result.t @@ -1,15 +1,15 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 67; +use Test::More tests => 71; BEGIN { use_ok("IkiWiki"); } # Note that new objects have to be constructed freshly for each test, since # object states are mutated as they are combined. -sub S { IkiWiki::SuccessReason->new("match") } -sub F { IkiWiki::FailReason->new("no match") } -sub E { IkiWiki::ErrorReason->new("error in matching") } +sub S { IkiWiki::SuccessReason->new("match", @_) } +sub F { IkiWiki::FailReason->new("no match", @_) } +sub E { IkiWiki::ErrorReason->new("error in matching", @_) } ok(S() eq "match"); ok(F() eq "no match"); @@ -38,12 +38,10 @@ ok(S() & (F() | F() | S())); # as long as the two items are always both present foreach my $op ('$s | $f', '$s & $f', '$s & $f & E()', '$s | E() | $f', '! $s | ! $f', '!(!(!$s)) | $f') { - my $s=S(); - $s->influences(foo => 1, bar => 1); + my $s=S(foo => 1, bar => 1); is($s->influences->{foo}, 1); is($s->influences->{bar}, 1); - my $f=F(); - $f->influences(bar => 2, baz => 1); + my $f=F(bar => 2, baz => 1); is($f->influences->{bar}, 2); is($f->influences->{baz}, 1); my $c = eval $op; @@ -52,3 +50,10 @@ foreach my $op ('$s | $f', '$s & $f', '$s & $f & E()', '$s | E() | $f', is($c->influences->{bar}, (1 | 2), "bar ($op)"); is($c->influences->{baz}, 1, "baz ($op)"); } + +my $s=S(foo => 0, bar => 1); +$s->influences(baz => 1); +ok(! $s->influences->{foo}, "removed 0 influence"); +ok(! $s->influences->{bar}, "removed 1 influence"); +ok($s->influences->{baz}, "set influence"); +ok($s->influences_static); -- cgit v1.2.3 From 5dba91cdc88c01ac1d314f24b12eb2cda651e206 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 11 Oct 2009 13:51:23 -0400 Subject: typo --- doc/plugins/calendar.mdwn | 2 +- t/pagespec_match_result.t | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) (limited to 't') diff --git a/doc/plugins/calendar.mdwn b/doc/plugins/calendar.mdwn index d695762b7..8ade70004 100644 --- a/doc/plugins/calendar.mdwn +++ b/doc/plugins/calendar.mdwn @@ -6,7 +6,7 @@ The directive displays a calendar, similar to the typical calendars shown on some blogs. Since ikiwiki is a wiki compiler, to keep the calendar up-to-date, -wikis that include it need to be periodically refreshes, typically by cron +wikis that include it need to be periodically refreshed, typically by cron at midnight. Example crontab: 0 0 * * * ikiwiki -setup ~/ikiwiki.setup -refresh diff --git a/t/pagespec_match_result.t b/t/pagespec_match_result.t index d9c31d6f0..c2112bf14 100755 --- a/t/pagespec_match_result.t +++ b/t/pagespec_match_result.t @@ -57,3 +57,21 @@ ok(! $s->influences->{foo}, "removed 0 influence"); ok(! $s->influences->{bar}, "removed 1 influence"); ok($s->influences->{baz}, "set influence"); ok($s->influences_static); + +# influence blocking +my $r=F()->block & S(foo => 1); +ok(! $r->influences->{foo}, "failed blocker & influence -> does not pass"); +$r=F()->block | S(foo => 1); +ok($r->influences->{foo}, "failed blocker | influence -> does pass"); +$r=S(foo => 1) & F()->block; +ok(! $r->influences->{foo}, "influence & failed blocker -> does not pass"); +$r=S(foo => 1) | F()->block; +ok($r->influences->{foo}, "influence | failed blocker -> does pass"); +$r=S(foo => 1) & F()->block & S(foo => 2); +ok(! $r->influences->{foo}, "influence & failed blocker & influence -> does not pass"); +$r=S(foo => 1) | F()->block | S(foo => 2); +ok($r->influences->{foo}, "influence | failed blocker | influence -> does pass"); +$r=S()->block & S(foo => 1); +ok($r->influences->{foo}, "successful blocker -> does pass"); +$r=(! S()->block) & S(foo => 1); +ok(! $r->influences->{foo}, "! successful blocker -> failed blocker"); -- cgit v1.2.3 From 04d0a72c09528a4117b2ba641f5fdedbbe2a2171 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 11 Oct 2009 19:05:37 -0400 Subject: typo --- t/add_depends.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 't') diff --git a/t/add_depends.t b/t/add_depends.t index e3f0b6603..9b074818c 100755 --- a/t/add_depends.t +++ b/t/add_depends.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 88; +use Test::More tests => 38; BEGIN { use_ok("IkiWiki"); } %config=IkiWiki::defaultconfig(); @@ -39,7 +39,7 @@ ok($IkiWiki::depends_simple{foo2}{"bar"} & $IkiWiki::DEPEND_LINKS); ok(! ($IkiWiki::depends_simple{foo2}{"bar"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_CONTENT))); # adding combined dep types -ok(add_depends("foo2", "baz", deptype("links". "presence"))); +ok(add_depends("foo2", "baz", deptype("links", "presence"))); ok($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_LINKS); ok($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_PRESENCE); ok(! ($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_CONTENT)); -- cgit v1.2.3 From 51d889951528a1de4f631435c2deccd1bd326ec5 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 11 Oct 2009 19:13:40 -0400 Subject: influences returns hash ref now --- t/pagespec_match.t | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 't') diff --git a/t/pagespec_match.t b/t/pagespec_match.t index ab3fcdd4b..d529106f7 100755 --- a/t/pagespec_match.t +++ b/t/pagespec_match.t @@ -93,19 +93,19 @@ $ret=pagespec_match("foo", "bar or foo"); ok($ret, "simple match"); is($ret, "foo matches foo", "stringified return"); -my %i=pagespec_match("foo", "link(bar)")->influences; -is(join(",", keys %i), 'foo', "link is influenced by the page with the link"); -%i=pagespec_match("bar", "backlink(foo)")->influences; -is(join(",", keys %i), 'foo', "backlink is influenced by the page with the link"); -%i=pagespec_match("bar", "backlink(foo)")->influences; -is(join(",", keys %i), 'foo', "backlink is influenced by the page with the link"); -%i=pagespec_match("bar", "created_before(foo)")->influences; -is(join(",", keys %i), 'foo', "created_before is influenced by the comparison page"); -%i=pagespec_match("bar", "created_after(foo)")->influences; -is(join(",", keys %i), 'foo', "created_after is influenced by the comparison page"); -%i=pagespec_match("foo", "link(baz) and created_after(bar)")->influences; -is(join(",", sort keys %i), 'bar,foo', "influences add up over AND"); -%i=pagespec_match("foo", "link(baz) and created_after(bar)")->influences; -is(join(",", sort keys %i), 'bar,foo', "influences add up over OR"); -%i=pagespec_match("foo", "!link(baz) and !created_after(bar)")->influences; -is(join(",", sort keys %i), 'bar,foo', "influences unaffected by negation"); +my $i=pagespec_match("foo", "link(bar)")->influences; +is(join(",", keys %$i), 'foo', "link is influenced by the page with the link"); +$i=pagespec_match("bar", "backlink(foo)")->influences; +is(join(",", keys %$i), 'foo', "backlink is influenced by the page with the link"); +$i=pagespec_match("bar", "backlink(foo)")->influences; +is(join(",", keys %$i), 'foo', "backlink is influenced by the page with the link"); +$i=pagespec_match("bar", "created_before(foo)")->influences; +is(join(",", keys %$i), 'foo', "created_before is influenced by the comparison page"); +$i=pagespec_match("bar", "created_after(foo)")->influences; +is(join(",", keys %$i), 'foo', "created_after is influenced by the comparison page"); +$i=pagespec_match("foo", "link(baz) and created_after(bar)")->influences; +is(join(",", sort keys %$i), 'bar,foo', "influences add up over AND"); +$i=pagespec_match("foo", "link(baz) and created_after(bar)")->influences; +is(join(",", sort keys %$i), 'bar,foo', "influences add up over OR"); +$i=pagespec_match("foo", "!link(baz) and !created_after(bar)")->influences; +is(join(",", sort keys %$i), 'bar,foo', "influences unaffected by negation"); -- cgit v1.2.3 From 139085b3f294f3a8e3fd1663aa232a9e901ca3e1 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 11 Oct 2009 23:53:21 -0400 Subject: add some TODO tests for influence blocking --- t/pagespec_match_list.t | 31 ++++++++++++++++++++++++++++++- t/pagespec_match_result.t | 18 ------------------ 2 files changed, 30 insertions(+), 19 deletions(-) (limited to 't') diff --git a/t/pagespec_match_list.t b/t/pagespec_match_list.t index 301197cc1..51145a973 100755 --- a/t/pagespec_match_list.t +++ b/t/pagespec_match_list.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 49; +use Test::More tests => 61; BEGIN { use_ok("IkiWiki"); } @@ -82,4 +82,33 @@ foreach my $spec ("bar or (backlink(foo) and !*.png)", "backlink(foo)") { ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_CONTENT); ok(! ($IkiWiki::depends{foo2}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); ok($IkiWiki::depends_simple{foo2}{foo} == $IkiWiki::DEPEND_LINKS); + %IkiWiki::depends_simple=(); + %IkiWiki::depends=(); +} + +TODO: { + local $TODO = "optimisation not yet written"; + +# a pagespec that hard fails due to a glob, etc, will not set influences +# for other terms that normally would. +foreach my $spec ("nosuchpage and link(bar)", "link(bar) and */Discussion") { + pagespec_match_list("foo2", $spec, deptype => deptype("presence")); + ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_PRESENCE); + ok(! ($IkiWiki::depends{foo2}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); + ok(! exists $IkiWiki::depends_simple{foo2}{foo2}); + %IkiWiki::depends_simple=(); + %IkiWiki::depends=(); +} + +# a pagespec containing a hard failure that is ored with another term will +# get influences from the other term +foreach my $spec ("nosuchpage or link(bar)", "link(bar) or */Discussion") { + pagespec_match_list("foo2", $spec, deptype => deptype("presence")); + ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_PRESENCE); + ok(! ($IkiWiki::depends{foo2}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); + ok($IkiWiki::depends_simple{foo2}{foo2} == $IkiWiki::DEPEND_LINKS); + %IkiWiki::depends_simple=(); + %IkiWiki::depends=(); +} + } diff --git a/t/pagespec_match_result.t b/t/pagespec_match_result.t index c2112bf14..d9c31d6f0 100755 --- a/t/pagespec_match_result.t +++ b/t/pagespec_match_result.t @@ -57,21 +57,3 @@ ok(! $s->influences->{foo}, "removed 0 influence"); ok(! $s->influences->{bar}, "removed 1 influence"); ok($s->influences->{baz}, "set influence"); ok($s->influences_static); - -# influence blocking -my $r=F()->block & S(foo => 1); -ok(! $r->influences->{foo}, "failed blocker & influence -> does not pass"); -$r=F()->block | S(foo => 1); -ok($r->influences->{foo}, "failed blocker | influence -> does pass"); -$r=S(foo => 1) & F()->block; -ok(! $r->influences->{foo}, "influence & failed blocker -> does not pass"); -$r=S(foo => 1) | F()->block; -ok($r->influences->{foo}, "influence | failed blocker -> does pass"); -$r=S(foo => 1) & F()->block & S(foo => 2); -ok(! $r->influences->{foo}, "influence & failed blocker & influence -> does not pass"); -$r=S(foo => 1) | F()->block | S(foo => 2); -ok($r->influences->{foo}, "influence | failed blocker | influence -> does pass"); -$r=S()->block & S(foo => 1); -ok($r->influences->{foo}, "successful blocker -> does pass"); -$r=(! S()->block) & S(foo => 1); -ok(! $r->influences->{foo}, "! successful blocker -> failed blocker"); -- cgit v1.2.3 From 2f5beb59bff17ace9e33743d646a95204a9be5bc Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Tue, 13 Oct 2009 14:46:38 -0400 Subject: tests for influence blocking --- t/pagespec_match_list.t | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) (limited to 't') diff --git a/t/pagespec_match_list.t b/t/pagespec_match_list.t index 51145a973..dd5dcc5b0 100755 --- a/t/pagespec_match_list.t +++ b/t/pagespec_match_list.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 61; +use Test::More tests => 88; BEGIN { use_ok("IkiWiki"); } @@ -86,23 +86,23 @@ foreach my $spec ("bar or (backlink(foo) and !*.png)", "backlink(foo)") { %IkiWiki::depends=(); } -TODO: { - local $TODO = "optimisation not yet written"; - -# a pagespec that hard fails due to a glob, etc, will not set influences -# for other terms that normally would. -foreach my $spec ("nosuchpage and link(bar)", "link(bar) and */Discussion") { +# Hard fails due to a glob, etc, will block influences of other anded terms. +foreach my $spec ("nosuchpage and link(bar)", "link(bar) and nosuchpage", + "link(bar) and */Discussion", "*/Discussion and link(bar)", + "!foo2 and link(bar)", "link(bar) and !foo2") { pagespec_match_list("foo2", $spec, deptype => deptype("presence")); ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_PRESENCE); ok(! ($IkiWiki::depends{foo2}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); - ok(! exists $IkiWiki::depends_simple{foo2}{foo2}); + ok(! exists $IkiWiki::depends_simple{foo2}{foo2}, "no influence from $spec"); %IkiWiki::depends_simple=(); %IkiWiki::depends=(); } -# a pagespec containing a hard failure that is ored with another term will -# get influences from the other term -foreach my $spec ("nosuchpage or link(bar)", "link(bar) or */Discussion") { +# A hard fail will not block influences of other ored terms. +foreach my $spec ("nosuchpage or link(bar)", "link(bar) or nosuchpage", + "link(bar) or */Discussion", "*/Discussion or link(bar)", + "!foo2 or link(bar)", "link(bar) or !foo2", + "link(bar) or (!foo2 and !foo1)") { pagespec_match_list("foo2", $spec, deptype => deptype("presence")); ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_PRESENCE); ok(! ($IkiWiki::depends{foo2}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); @@ -110,5 +110,3 @@ foreach my $spec ("nosuchpage or link(bar)", "link(bar) or */Discussion") { %IkiWiki::depends_simple=(); %IkiWiki::depends=(); } - -} -- cgit v1.2.3 From c916dcd035ef35e545119c91953a135600ecf0c3 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Tue, 13 Oct 2009 14:58:22 -0400 Subject: fix some broken influence blocking testing, add more tests --- t/pagespec_match_result.t | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) (limited to 't') diff --git a/t/pagespec_match_result.t b/t/pagespec_match_result.t index d9c31d6f0..13fcdcad0 100755 --- a/t/pagespec_match_result.t +++ b/t/pagespec_match_result.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 71; +use Test::More tests => 138; BEGIN { use_ok("IkiWiki"); } @@ -34,10 +34,27 @@ ok(!(S() & F())); ok(!(S() & F() & E())); ok(S() & (F() | F() | S())); -# influences are always merged, no matter the operation performed, -# as long as the two items are always both present -foreach my $op ('$s | $f', '$s & $f', '$s & $f & E()', '$s | E() | $f', - '! $s | ! $f', '!(!(!$s)) | $f') { +# influence merging tests +foreach my $test ( + ['$s | $f' => 1], # OR merges + ['! $s | ! $f' => 1], # OR merges with negated terms too + ['!(!(!$s)) | $f' => 1],# OR merges with multiple negation too + ['$s | $f | E()' => 1], # OR merges, even though E() has no influences + ['$s | E() | $f' => 1], # ditto + ['E() | $s | $f' => 1], # ditto + ['!$s | !$f | E()' => 1],# negated terms also do not block merges + ['!$s | E() | $f' => 1],# ditto + ['E() | $s | !$f' => 1],# ditto + ['$s & $f' => 1], # AND merges if both items have influences + ['!$s & $f' => 1], # AND merges negated terms too + ['$s & !$f' => 1], # AND merges negated terms too + ['$s & $f & E()' => 0], # AND fails to merge since E() has no influences + ['$s & E() & $f' => 0], # ditto + ['E() & $s & $f' => 0], # ditto + ) { + my $op=$test->[0]; + my $influence=$test->[1]; + my $s=S(foo => 1, bar => 1); is($s->influences->{foo}, 1); is($s->influences->{bar}, 1); @@ -46,9 +63,14 @@ foreach my $op ('$s | $f', '$s & $f', '$s & $f & E()', '$s | E() | $f', is($f->influences->{baz}, 1); my $c = eval $op; ok(ref $c); - is($c->influences->{foo}, 1, "foo ($op)"); - is($c->influences->{bar}, (1 | 2), "bar ($op)"); - is($c->influences->{baz}, 1, "baz ($op)"); + if ($influence) { + is($c->influences->{foo}, 1, "foo ($op)"); + is($c->influences->{bar}, (1 | 2), "bar ($op)"); + is($c->influences->{baz}, 1, "baz ($op)"); + } + else { + ok(! %{$c->influences}, "no influence for ($op)"); + } } my $s=S(foo => 0, bar => 1); @@ -57,3 +79,6 @@ ok(! $s->influences->{foo}, "removed 0 influence"); ok(! $s->influences->{bar}, "removed 1 influence"); ok($s->influences->{baz}, "set influence"); ok($s->influences_static); +$s=S(foo => 0, bar => 1); +$s->influences(baz => 1, "" => 1); +ok(! $s->influences_static); -- cgit v1.2.3 From 57a135c92ac33f792d138fcdbeb72e8ad5f37670 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 14 Oct 2009 13:15:12 -0400 Subject: abort test if cvs prereq perl modules are not available --- t/cvs.t | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 't') diff --git a/t/cvs.t b/t/cvs.t index c9caa94eb..2808973be 100755 --- a/t/cvs.t +++ b/t/cvs.t @@ -13,6 +13,14 @@ BEGIN { use Test::More skip_all => "cvs or cvsps not available or could not make test dir" } } + foreach my $module ('File::ReadBackwards', 'File::MimeInfo') { + eval qq{use $module}; + if ($@) { + eval qq{ + use Test::More skip_all => "$module not available" + } + } + } } use Test::More tests => 12; -- cgit v1.2.3 From 7ba4fa760a238a135b2fc2074044454b5b9a177e Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 14 Oct 2009 13:19:44 -0400 Subject: build wiki in C locale, to ensure that the "no broken links" message is not translated --- t/basewiki_brokenlinks.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 't') diff --git a/t/basewiki_brokenlinks.t b/t/basewiki_brokenlinks.t index d74f64e36..2dd950832 100755 --- a/t/basewiki_brokenlinks.t +++ b/t/basewiki_brokenlinks.t @@ -8,7 +8,7 @@ ok(! system("make -s ikiwiki.out")); ok(! system("make underlay_install DESTDIR=`pwd`/t/tmp/install PREFIX=/usr >/dev/null")); foreach my $plugin ("", "listdirectives") { - ok(! system("perl -I. ./ikiwiki.out -rebuild -plugin brokenlinks ". + ok(! system("LANG=C perl -I. ./ikiwiki.out -rebuild -plugin brokenlinks ". # always enabled because pages link to it conditionally, # which brokenlinks cannot handle properly "-plugin smiley ". -- cgit v1.2.3 From 96729cafc3c178b54da271c9ba5cbb51a69cccab Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Thu, 15 Oct 2009 14:26:06 -0400 Subject: override LC_ALL, not LANG --- t/basewiki_brokenlinks.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 't') diff --git a/t/basewiki_brokenlinks.t b/t/basewiki_brokenlinks.t index 2dd950832..74ddc61c5 100755 --- a/t/basewiki_brokenlinks.t +++ b/t/basewiki_brokenlinks.t @@ -8,7 +8,7 @@ ok(! system("make -s ikiwiki.out")); ok(! system("make underlay_install DESTDIR=`pwd`/t/tmp/install PREFIX=/usr >/dev/null")); foreach my $plugin ("", "listdirectives") { - ok(! system("LANG=C perl -I. ./ikiwiki.out -rebuild -plugin brokenlinks ". + ok(! system("LC_ALL=C perl -I. ./ikiwiki.out -rebuild -plugin brokenlinks ". # always enabled because pages link to it conditionally, # which brokenlinks cannot handle properly "-plugin smiley ". -- cgit v1.2.3 From 01c0f533de4f6504df8e762125ec06f8458b1024 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Mon, 30 Nov 2009 18:05:47 -0500 Subject: fix test suite after bestlink change --- t/bestlink.t | 4 ++-- t/pagespec_match.t | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 't') diff --git a/t/bestlink.t b/t/bestlink.t index 033b80d74..0020a05e2 100755 --- a/t/bestlink.t +++ b/t/bestlink.t @@ -11,11 +11,11 @@ sub test ($$$) { my @existing_pages=@{shift()}; %IkiWiki::pagecase=(); - %links=(); + %pagesources=(); $IkiWiki::config{userdir}="foouserdir"; foreach my $page (@existing_pages) { $IkiWiki::pagecase{lc $page}=$page; - $links{$page}=[]; + $pagesources{$page}="$page.mdwn"; } return bestlink($page, $link); diff --git a/t/pagespec_match.t b/t/pagespec_match.t index d529106f7..b96947407 100755 --- a/t/pagespec_match.t +++ b/t/pagespec_match.t @@ -49,9 +49,13 @@ $links{"bugs/foo"}=[qw{bugs/done}]; $links{"bugs/done"}=[]; $links{"bugs/bar"}=[qw{done}]; $links{"done"}=[]; +$links{"done"}=[]; $links{"examples/softwaresite/bugs/fails_to_frobnicate"}=[qw{done}]; $links{"examples/softwaresite/bugs/done"}=[]; $links{"ook"}=[qw{/blog/tags/foo}]; +foreach my $p (keys %links) { + $pagesources{$p}="$p.mdwn"; +} ok(pagespec_match("foo", "link(bar)"), "link"); ok(pagespec_match("foo", "link(ba?)"), "glob link"); -- cgit v1.2.3 From c923e0ba3377f85107ccea1933a042aaec675c77 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Thu, 11 Feb 2010 20:39:10 -0500 Subject: Allow globs to be used in user() pagespecs. --- IkiWiki.pm | 4 +++- debian/changelog | 1 + doc/ikiwiki/pagespec.mdwn | 3 ++- t/pagespec_match.t | 9 ++++++++- 4 files changed, 14 insertions(+), 3 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index de7dbfc79..a96ff1236 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -2266,11 +2266,13 @@ sub match_user ($$;@) { my $user=shift; my %params=@_; + my $regexp=IkiWiki::glob2re($user); + if (! exists $params{user}) { return IkiWiki::ErrorReason->new("no user specified"); } - if (defined $params{user} && lc $params{user} eq lc $user) { + if (defined $params{user} && $params{user}=~/^$regexp$/i) { return IkiWiki::SuccessReason->new("user is $user"); } elsif (! defined $params{user}) { diff --git a/debian/changelog b/debian/changelog index 14be7ec69..d74abd0f9 100644 --- a/debian/changelog +++ b/debian/changelog @@ -21,6 +21,7 @@ ikiwiki (3.20100123) UNRELEASED; urgency=low a button on the login form to use it. * httpauth: Add httpauth_pagespec setting that can be used to limit pages to only being edited via users authed with httpauth. + * Allow globs to be used in user() pagespecs. -- Joey Hess Tue, 26 Jan 2010 22:25:33 -0500 diff --git a/doc/ikiwiki/pagespec.mdwn b/doc/ikiwiki/pagespec.mdwn index 5f0f44e2e..8d8b1a507 100644 --- a/doc/ikiwiki/pagespec.mdwn +++ b/doc/ikiwiki/pagespec.mdwn @@ -44,7 +44,8 @@ Some more elaborate limits can be added to what matches using these functions: metadata, matching the specified glob. * "`user(username)`" - tests whether a modification is being made by a user with the specified username. If openid is enabled, an openid can also - be put here. + be put here. Glob patterns can be used in the username. For example, + to match all openid users, use `user(*://.*)` * "`admin()`" - tests whether a modification is being made by one of the wiki admins. * "`ip(address)`" - tests whether a modification is being made from the diff --git a/t/pagespec_match.t b/t/pagespec_match.t index b96947407..197ff818b 100755 --- a/t/pagespec_match.t +++ b/t/pagespec_match.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 64; +use Test::More tests => 70; BEGIN { use_ok("IkiWiki"); } @@ -40,6 +40,13 @@ ok(! pagespec_match("foo", "foo and bar"), "foo and bar"); ok(pagespec_match("{f}oo", "{*}*"), "curly match"); ok(! pagespec_match("foo", "{*}*"), "curly !match"); +ok(pagespec_match("somepage", "user(frodo)", user => "frodo")); +ok(pagespec_match("somepage", "user(frodo)", user => "Frodo")); +ok(! pagespec_match("somepage", "user(frodo)", user => "Sam")); +ok(pagespec_match("somepage", "user(*o)", user => "Bilbo")); +ok(pagespec_match("somepage", "user(*o)", user => "frodo")); +ok(! pagespec_match("somepage", "user(*o)", user => "Sam")); + # The link and backlink stuff needs this. $config{userdir}=""; $links{foo}=[qw{bar baz}]; -- cgit v1.2.3 From 6f3641f16c0043f42212976ccba93b3cf7e4e36f Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Thu, 11 Feb 2010 22:42:24 -0500 Subject: add some openid matching tests --- t/pagespec_match.t | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 't') diff --git a/t/pagespec_match.t b/t/pagespec_match.t index 197ff818b..8b0be4e8a 100755 --- a/t/pagespec_match.t +++ b/t/pagespec_match.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 70; +use Test::More tests => 72; BEGIN { use_ok("IkiWiki"); } @@ -46,6 +46,8 @@ ok(! pagespec_match("somepage", "user(frodo)", user => "Sam")); ok(pagespec_match("somepage", "user(*o)", user => "Bilbo")); ok(pagespec_match("somepage", "user(*o)", user => "frodo")); ok(! pagespec_match("somepage", "user(*o)", user => "Sam")); +ok(pagespec_match("somepage", "user(http://*.myopenid.com/)", user => "http://foo.myopenid.com/")); +ok(pagespec_match("somepage", "user(*://*)", user => "http://foo.myopenid.com/")); # The link and backlink stuff needs this. $config{userdir}=""; -- cgit v1.2.3 From b3c3c42b269c102123dcd80979cb9c49ce13bca7 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Fri, 26 Feb 2010 11:49:51 -0500 Subject: Loosen regexp, to allow empty quoted parameters in directives. --- IkiWiki.pm | 6 +++--- debian/changelog | 1 + t/preprocess.t | 12 +++++++++++- 3 files changed, 15 insertions(+), 4 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index b9a419d1d..9df6c90d6 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -1220,7 +1220,7 @@ sub preprocess ($$$;$$) { (?: """(.*?)""" # 2: triple-quoted value | - "([^"]+)" # 3: single-quoted value + "([^"]*?)" # 3: single-quoted value | (\S+) # 4: unquoted value ) @@ -1306,7 +1306,7 @@ sub preprocess ($$$;$$) { (?: """.*?""" # triple-quoted value | - "[^"]+" # single-quoted value + "[^"]*?" # single-quoted value | [^"\s\]]+ # unquoted value ) @@ -1329,7 +1329,7 @@ sub preprocess ($$$;$$) { (?: """.*?""" # triple-quoted value | - "[^"]+" # single-quoted value + "[^"]*?" # single-quoted value | [^"\s\]]+ # unquoted value ) diff --git a/debian/changelog b/debian/changelog index e3ec89eed..1960a1226 100644 --- a/debian/changelog +++ b/debian/changelog @@ -3,6 +3,7 @@ ikiwiki (3.20100213) UNRELEASED; urgency=low * comments: Display number of comments in comment action link. * Rebuild wikis on upgrade to this version to get the comment counts added to existing pages. + * Loosen regexp, to allow empty quoted parameters in directives. -- Joey Hess Sun, 14 Feb 2010 17:02:10 -0500 diff --git a/t/preprocess.t b/t/preprocess.t index e5026ed64..7bb9878d0 100755 --- a/t/preprocess.t +++ b/t/preprocess.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 21; +use Test::More tests => 31; BEGIN { use_ok("IkiWiki"); } @@ -26,6 +26,16 @@ is(IkiWiki::preprocess("foo", "foo", "[[foo ]]", 0, 0), "foo()", "simple"); is(IkiWiki::preprocess("foo", "foo", "[[!foo ]]", 0, 0), "foo()", "prefixed"); is(IkiWiki::preprocess("foo", "foo", "[[!foo]]", 0, 0), "[[!foo]]", "prefixed, no space"); is(IkiWiki::preprocess("foo", "foo", "[[foo a=1]]", 0, 0), "foo(a => 1)"); +is(IkiWiki::preprocess("foo", "foo", q{[[foo a="1"]]}, 0, 0), "foo(a => 1)"); +is(IkiWiki::preprocess("foo", "foo", q{[[foo a="""1"""]]}, 0, 0), "foo(a => 1)"); +is(IkiWiki::preprocess("foo", "foo", q{[[foo a=""]]}, 0, 0), "foo(a)"); +is(IkiWiki::preprocess("foo", "foo", q{[[foo a="" b="1"]]}, 0, 0), "foo(a, b => 1)"); +is(IkiWiki::preprocess("foo", "foo", q{[[foo a=""""""]]}, 0, 0), "foo(a)"); +is(IkiWiki::preprocess("foo", "foo", q{[[foo a="""""" b="1"]]}, 0, 0), "foo(a, b => 1)"); +is(IkiWiki::preprocess("foo", "foo", q{[[foo a="""""" b="""1"""]]}, 0, 0), "foo(a, b => 1)"); +is(IkiWiki::preprocess("foo", "foo", q{[[foo a="""""" b=""""""]]}, 0, 0), "foo(a, b)"); +is(IkiWiki::preprocess("foo", "foo", q{[[foo a="" b=""""""]]}, 0, 0), "foo(a, b)"); +is(IkiWiki::preprocess("foo", "foo", q{[[foo a="" b="""1"""]]}, 0, 0), "foo(a, b => 1)"); is(IkiWiki::preprocess("foo", "foo", "[[foo a=\"1 2 3 4\"]]", 0, 0), "foo(a => 1 2 3 4)"); is(IkiWiki::preprocess("foo", "foo", "[[foo ]] then [[foo a=2]]", 0, 0), "foo() then foo(a => 2)"); -- cgit v1.2.3 From c0ad4929deb455804ddfc6451820c6c67a298ea1 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sat, 13 Mar 2010 20:10:50 -0500 Subject: Improve openid url munging; do not display anchors and cgi parameters, as used by yahoo and google urls. --- IkiWiki.pm | 2 +- debian/changelog | 2 ++ doc/forum/google_openid_broken__63__.mdwn | 6 ++++++ t/openiduser.t | 7 ++++++- 4 files changed, 15 insertions(+), 2 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 00eadfd98..251ed8cc8 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -1131,7 +1131,7 @@ sub openiduser ($) { # Convert "http://somehost.com/user" to "user [somehost.com]". # (also "https://somehost.com/user/") if ($display !~ /\[/) { - $display=~s/^https?:\/\/(.+)\/([^\/]+)\/?$/$2 [$1]/; + $display=~s/^https?:\/\/(.+)\/([^\/#?]+)\/?(?:[#?].*)?$/$2 [$1]/; } $display=~s!^https?://!!; # make sure this is removed eval q{use CGI 'escapeHTML'}; diff --git a/debian/changelog b/debian/changelog index 854d83130..0a9679618 100644 --- a/debian/changelog +++ b/debian/changelog @@ -4,6 +4,8 @@ ikiwiki (3.20100313) UNRELEASED; urgency=low as unsafe. * openid: Use Openid Simple Registration or OpenID Attribute Exchange to get the user's email address and username. + * Improve openid url munging; do not display anchors and cgi parameters, + as used by yahoo and google urls. -- Joey Hess Sat, 13 Mar 2010 14:48:10 -0500 diff --git a/doc/forum/google_openid_broken__63__.mdwn b/doc/forum/google_openid_broken__63__.mdwn index 4ca5cac93..96ba2d791 100644 --- a/doc/forum/google_openid_broken__63__.mdwn +++ b/doc/forum/google_openid_broken__63__.mdwn @@ -59,6 +59,12 @@ points to a fairly useless xml document, rather than a web page. --[[Joey]] > Using the Google profile page as the OpenID is really orthogonal to the above. --[[kaol]] +>> First, I don't accept that the openid google returns from their +>> generic signin url *has* to be so freaking ugly. For contrast, +>> look at the openid you log in as if you use the yahoo url. +>> . Nice and clean, now +>> munged by ikiwiki to "joeyhess [me.yahoo.com]". +>> >> Displaying email addresses is not really an option, because ikiwiki >> can't leak user email addresses like that. Displaying nicknames or >> usernames is, see [[todo/Separate_OpenIDs_and_usernames]]. diff --git a/t/openiduser.t b/t/openiduser.t index 52d879484..caabbcefc 100755 --- a/t/openiduser.t +++ b/t/openiduser.t @@ -10,7 +10,7 @@ BEGIN { eval q{use Test::More skip_all => "Net::OpenID::VerifiedIdentity not available"}; } else { - eval q{use Test::More tests => 9}; + eval q{use Test::More tests => 11}; } use_ok("IkiWiki::Plugin::openid"); } @@ -28,6 +28,11 @@ $^W=1; is(IkiWiki::openiduser('http://yam655.livejournal.com/'), 'yam655 [livejournal.com]'); is(IkiWiki::openiduser('http://id.mayfirst.org/jamie/'), 'jamie [id.mayfirst.org]'); +# yahoo has an anchor in the url +is(IkiWiki::openiduser('https://me.yahoo.com/joeyhess#35f22'), 'joeyhess [me.yahoo.com]'); +# google urls are horrendous, but the worst bit is after a ?, so can be dropped +is(IkiWiki::openiduser('https://www.google.com/accounts/o8/id?id=AItOawm-ebiIfxbKD3KNa-Cu9LvvD9edMLW7BAo'), 'id [www.google.com/accounts/o8]'); + # and some less typical ones taken from the ikiwiki commit history is(IkiWiki::openiduser('http://thm.id.fedoraproject.org/'), 'thm [id.fedoraproject.org]'); -- cgit v1.2.3 From 87a36bbb467c22ebb68a6e9999f0ded2b017e5df Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 14 Mar 2010 15:22:45 -0400 Subject: update --- t/file_pruned.t | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 't') diff --git a/t/file_pruned.t b/t/file_pruned.t index 00542d580..f9c1c257e 100755 --- a/t/file_pruned.t +++ b/t/file_pruned.t @@ -1,12 +1,13 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 26; +use Test::More tests => 27; BEGIN { use_ok("IkiWiki"); } %config=IkiWiki::defaultconfig(); +ok(IkiWiki::file_pruned("src/.htaccess", "src")); ok(IkiWiki::file_pruned("src/.ikiwiki/", "src")); ok(IkiWiki::file_pruned("src/.ikiwiki/index", "src")); ok(IkiWiki::file_pruned("src/CVS/foo", "src")); -- cgit v1.2.3 From e67a9382f67e745af3be7d367fe7a0d36c1777e6 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Wed, 24 Mar 2010 00:29:10 +0000 Subject: Allow hooks to add sorting functions to pagespec_match_list --- IkiWiki.pm | 6 +++++- doc/ikiwiki/pagespec/sorting.mdwn | 2 ++ doc/plugins/write.mdwn | 15 +++++++++++++++ t/pagespec_match_list.t | 6 +++++- 4 files changed, 27 insertions(+), 2 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 022bfe3bd..1a4dc47dd 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -2035,7 +2035,11 @@ sub pagespec_match_list ($$;@) { if (defined $params{sort}) { my $f; - if ($params{sort} eq 'title') { + + if (exists $hooks{sort}{$params{sort}}{call}) { + $f = sub { $hooks{sort}{$params{sort}}{call}($a, $b) }; + } + elsif ($params{sort} eq 'title') { $f=sub { pagetitle(basename($a)) cmp pagetitle(basename($b)) }; } elsif ($params{sort} eq 'title_natural') { diff --git a/doc/ikiwiki/pagespec/sorting.mdwn b/doc/ikiwiki/pagespec/sorting.mdwn index 697818a2a..9007c23bf 100644 --- a/doc/ikiwiki/pagespec/sorting.mdwn +++ b/doc/ikiwiki/pagespec/sorting.mdwn @@ -10,4 +10,6 @@ orders can be specified. installed. Orders by title, but numbers in the title are treated as such, ("1 2 9 10 20" instead of "1 10 2 20 9") +Plugins can add additional sort orders. + [[!meta robots="noindex, follow"]] diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn index 96a2aa16d..bfa6617bd 100644 --- a/doc/plugins/write.mdwn +++ b/doc/plugins/write.mdwn @@ -588,6 +588,21 @@ describes the plugin as a whole. For example: This hook is used to inject C code (which it returns) into the `main` function of the ikiwiki wrapper when it is being generated. +### sort + + hook(type => "sort", id => "foo", call => \&sort_by_foo); + +This hook adds an additional [[ikiwiki/pagespec/sorting]] order or overrides +an existing one. The callback is given two page names as arguments, and +returns negative, zero or positive if the first page should come before, +close to (i.e. undefined order), or after the second page. + +For instance, the built-in `title` sort order could be reimplemented as + + sub sort_by_title { + pagetitle(basename($_[0])) cmp pagetitle(basename($_[1])); + } + ## Exported variables Several variables are exported to your plugin when you `use IkiWiki;` diff --git a/t/pagespec_match_list.t b/t/pagespec_match_list.t index dd5dcc5b0..b34ee769f 100755 --- a/t/pagespec_match_list.t +++ b/t/pagespec_match_list.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 88; +use Test::More tests => 89; BEGIN { use_ok("IkiWiki"); } @@ -9,6 +9,8 @@ BEGIN { use_ok("IkiWiki"); } $config{srcdir}=$config{destdir}="/dev/null"; IkiWiki::checkconfig(); +hook(type => "sort", id => "path", call => sub { $_[0] cmp $_[1] }); + %pagesources=( foo => "foo.mdwn", foo2 => "foo2.mdwn", @@ -34,6 +36,8 @@ is_deeply([pagespec_match_list("foo", "post/*", sort => "title", num => 50)], is_deeply([pagespec_match_list("foo", "post/*", sort => "title", filter => sub { $_[0] =~ /3/}) ], ["post/1", "post/2"]); +is_deeply([pagespec_match_list("foo", "*", sort => "path", num => 2)], + ["bar", "foo"]); my $r=eval { pagespec_match_list("foo", "beep") }; ok(eval { pagespec_match_list("foo", "beep") } == 0); ok(! $@, "does not fail with error when unable to match anything"); -- cgit v1.2.3 From 60edd2dc3157f756f4f7a213ee15836fe7bbb769 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Wed, 24 Mar 2010 23:51:48 +0000 Subject: Allow sorting to be combined and/or reversed --- IkiWiki.pm | 84 ++++++++++++++++++++++++++++----------- doc/ikiwiki/pagespec/sorting.mdwn | 4 ++ doc/plugins/write.mdwn | 17 +++++++- t/pagespec_match_list.t | 12 +++++- 4 files changed, 91 insertions(+), 26 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 1a4dc47dd..ce8fdd454 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -2005,6 +2005,64 @@ sub pagespec_match ($$;@) { return $sub->($page, @params); } +sub get_sort_function { + my $method = $_[0]; + + if ($method =~ m/\s/) { + my @methods = map { get_sort_function($_) } split(' ', $method); + + return sub { + foreach my $method (@methods) { + my $answer = $method->($_[0], $_[1]); + return $answer if $answer; + } + + return 0; + }; + } + + my $sense = 1; + + if ($method =~ s/^-//) { + $sense = -1; + } + + my $token = $method; + my $parameter = undef; + + if ($method =~ m/^(\w+)\((.*)\)$/) { + $token = $1; + $parameter = $2; + } + + if (exists $hooks{sort}{$token}{call}) { + my $callback = $hooks{sort}{$token}{call}; + return sub { $sense * $callback->($_[0], $_[1], $parameter) }; + } + + if ($method eq 'title') { + return sub { $sense * (pagetitle(basename($_[0])) cmp pagetitle(basename($_[1]))) }; + } + + if ($method eq 'title_natural') { + eval q{use Sort::Naturally}; + if ($@) { + error(gettext("Sort::Naturally needed for title_natural sort")); + } + return sub { $sense * Sort::Naturally::ncmp(pagetitle(basename($_[0])), pagetitle(basename($_[1]))) }; + } + + if ($method eq 'mtime') { + return sub { $sense * ($pagemtime{$_[1]} <=> $pagemtime{$_[0]}) }; + } + + if ($method eq 'age') { + return sub { $sense * ($pagectime{$_[1]} <=> $pagectime{$_[0]}) }; + } + + error sprintf(gettext("unknown sort type %s"), $method); +} + sub pagespec_match_list ($$;@) { my $page=shift; my $pagespec=shift; @@ -2034,31 +2092,9 @@ sub pagespec_match_list ($$;@) { } if (defined $params{sort}) { - my $f; + my $f = get_sort_function($params{sort}); - if (exists $hooks{sort}{$params{sort}}{call}) { - $f = sub { $hooks{sort}{$params{sort}}{call}($a, $b) }; - } - elsif ($params{sort} eq 'title') { - $f=sub { pagetitle(basename($a)) cmp pagetitle(basename($b)) }; - } - elsif ($params{sort} eq 'title_natural') { - eval q{use Sort::Naturally}; - if ($@) { - error(gettext("Sort::Naturally needed for title_natural sort")); - } - $f=sub { Sort::Naturally::ncmp(pagetitle(basename($a)), pagetitle(basename($b))) }; - } - elsif ($params{sort} eq 'mtime') { - $f=sub { $pagemtime{$b} <=> $pagemtime{$a} }; - } - elsif ($params{sort} eq 'age') { - $f=sub { $pagectime{$b} <=> $pagectime{$a} }; - } - else { - error sprintf(gettext("unknown sort type %s"), $params{sort}); - } - @candidates = sort { &$f } @candidates; + @candidates = sort { $f->($a, $b) } @candidates; } @candidates=reverse(@candidates) if $params{reverse}; diff --git a/doc/ikiwiki/pagespec/sorting.mdwn b/doc/ikiwiki/pagespec/sorting.mdwn index 61516bec5..f27972d4e 100644 --- a/doc/ikiwiki/pagespec/sorting.mdwn +++ b/doc/ikiwiki/pagespec/sorting.mdwn @@ -15,6 +15,10 @@ orders can be specified. full title was set. """]] +In addition, you can combine several sort orders and/or reverse the order of +sorting, with a string like `age -title` (which would sort by age, then by +title in reverse order if two pages have the same age). + Plugins can add additional sort orders, so more might be available on this wiki. diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn index bfa6617bd..1010e76e4 100644 --- a/doc/plugins/write.mdwn +++ b/doc/plugins/write.mdwn @@ -593,7 +593,9 @@ function of the ikiwiki wrapper when it is being generated. hook(type => "sort", id => "foo", call => \&sort_by_foo); This hook adds an additional [[ikiwiki/pagespec/sorting]] order or overrides -an existing one. The callback is given two page names as arguments, and +an existing one. + +The callback is given two page names followed by the parameter as arguments, and returns negative, zero or positive if the first page should come before, close to (i.e. undefined order), or after the second page. @@ -603,6 +605,19 @@ For instance, the built-in `title` sort order could be reimplemented as pagetitle(basename($_[0])) cmp pagetitle(basename($_[1])); } +and to sort by an arbitrary `meta` value, you could use: + + # usage: sort="meta(description)" + sub sort_by_meta { + my $param = $_[2]; + error "sort=meta requires a parameter" unless defined $param; + my $left = $pagestate{$_[0]}{meta}{$param}; + $left = "" unless defined $left; + my $right = $pagestate{$_[1]}{meta}{$param}; + $right = "" unless defined $right; + return $left cmp $right; + } + ## Exported variables Several variables are exported to your plugin when you `use IkiWiki;` diff --git a/t/pagespec_match_list.t b/t/pagespec_match_list.t index b34ee769f..309961f1c 100755 --- a/t/pagespec_match_list.t +++ b/t/pagespec_match_list.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 89; +use Test::More tests => 90; BEGIN { use_ok("IkiWiki"); } @@ -20,6 +20,13 @@ hook(type => "sort", id => "path", call => sub { $_[0] cmp $_[1] }); "post/2" => "post/2.mdwn", "post/3" => "post/3.mdwn", ); +$IkiWiki::pagectime{foo} = 2; +$IkiWiki::pagectime{foo2} = 2; +$IkiWiki::pagectime{foo3} = 1; +$IkiWiki::pagectime{bar} = 3; +$IkiWiki::pagectime{"post/1"} = 6; +$IkiWiki::pagectime{"post/2"} = 6; +$IkiWiki::pagectime{"post/3"} = 6; $links{foo}=[qw{post/1 post/2}]; $links{foo2}=[qw{bar}]; $links{foo3}=[qw{bar}]; @@ -38,6 +45,9 @@ is_deeply([pagespec_match_list("foo", "post/*", sort => "title", ["post/1", "post/2"]); is_deeply([pagespec_match_list("foo", "*", sort => "path", num => 2)], ["bar", "foo"]); +is_deeply([pagespec_match_list("foo", "foo* or bar*", + sort => "-age title")], # oldest first, break ties by title + ["foo3", "foo", "foo2", "bar"]); my $r=eval { pagespec_match_list("foo", "beep") }; ok(eval { pagespec_match_list("foo", "beep") } == 0); ok(! $@, "does not fail with error when unable to match anything"); -- cgit v1.2.3 From b86276ffed7ee001b35cd610e5d56e5afb4088cf Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Thu, 25 Mar 2010 23:31:53 +0000 Subject: Reimplement extensible sorting mechanisms, in the same way as pagespecs --- IkiWiki.pm | 145 ++++++++++++++++++++++++++++-------------------- IkiWiki/Plugin/meta.pm | 11 ++-- doc/plugins/write.mdwn | 53 ++++++++---------- t/pagespec_match_list.t | 6 +- 4 files changed, 120 insertions(+), 95 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index ce8fdd454..a89c14058 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -37,6 +37,7 @@ our $DEPEND_LINKS=4; # Optimisation. use Memoize; memoize("abs2rel"); +memoize("cmpspec_translate"); memoize("pagespec_translate"); memoize("template_file"); @@ -1934,6 +1935,70 @@ sub add_link ($$) { unless grep { $_ eq $link } @{$links{$page}}; } +sub cmpspec_translate ($) { + my $spec = shift; + + my $code = ""; + my @data; + while ($spec =~ m{ + \s* + (-?) # group 1: perhaps negated + \s* + ( # group 2: a word + \w+\([^\)]*\) # command(params) + | + [^\s]+ # or anything else + ) + \s* + }gx) { + my $negated = $1; + my $word = $2; + my $params = undef; + + if ($word =~ m/^(\w+)\((.*)\)$/) { + # command with parameters + $params = $2; + $word = $1; + } + elsif ($word !~ m/^\w+$/) { + error(sprintf(gettext("invalid sort type %s"), $word)); + } + + if (length $code) { + $code .= " || "; + } + + if ($negated) { + $code .= "-"; + } + + if (exists $IkiWiki::PageSpec::{"cmp_$word"}) { + if (exists $IkiWiki::PageSpec::{"check_cmp_$word"}) { + $IkiWiki::PageSpec::{"check_cmp_$word"}->($params); + } + + if (defined $params) { + push @data, $params; + $code .= "IkiWiki::PageSpec::cmp_$word(\@_, \$data[$#data])"; + } + else { + $code .= "IkiWiki::PageSpec::cmp_$word(\@_, undef)"; + } + } + else { + error(sprintf(gettext("unknown sort type %s"), $word)); + } + } + + if (! length $code) { + # undefined sorting method... sort arbitrarily + return sub { 0 }; + } + + no warnings; + return eval 'sub { '.$code.' }'; +} + sub pagespec_translate ($) { my $spec=shift; @@ -2005,64 +2070,6 @@ sub pagespec_match ($$;@) { return $sub->($page, @params); } -sub get_sort_function { - my $method = $_[0]; - - if ($method =~ m/\s/) { - my @methods = map { get_sort_function($_) } split(' ', $method); - - return sub { - foreach my $method (@methods) { - my $answer = $method->($_[0], $_[1]); - return $answer if $answer; - } - - return 0; - }; - } - - my $sense = 1; - - if ($method =~ s/^-//) { - $sense = -1; - } - - my $token = $method; - my $parameter = undef; - - if ($method =~ m/^(\w+)\((.*)\)$/) { - $token = $1; - $parameter = $2; - } - - if (exists $hooks{sort}{$token}{call}) { - my $callback = $hooks{sort}{$token}{call}; - return sub { $sense * $callback->($_[0], $_[1], $parameter) }; - } - - if ($method eq 'title') { - return sub { $sense * (pagetitle(basename($_[0])) cmp pagetitle(basename($_[1]))) }; - } - - if ($method eq 'title_natural') { - eval q{use Sort::Naturally}; - if ($@) { - error(gettext("Sort::Naturally needed for title_natural sort")); - } - return sub { $sense * Sort::Naturally::ncmp(pagetitle(basename($_[0])), pagetitle(basename($_[1]))) }; - } - - if ($method eq 'mtime') { - return sub { $sense * ($pagemtime{$_[1]} <=> $pagemtime{$_[0]}) }; - } - - if ($method eq 'age') { - return sub { $sense * ($pagectime{$_[1]} <=> $pagectime{$_[0]}) }; - } - - error sprintf(gettext("unknown sort type %s"), $method); -} - sub pagespec_match_list ($$;@) { my $page=shift; my $pagespec=shift; @@ -2092,7 +2099,7 @@ sub pagespec_match_list ($$;@) { } if (defined $params{sort}) { - my $f = get_sort_function($params{sort}); + my $f = cmpspec_translate($params{sort}); @candidates = sort { $f->($a, $b) } @candidates; } @@ -2407,4 +2414,24 @@ sub match_ip ($$;@) { } } +sub cmp_title { + IkiWiki::pagetitle(IkiWiki::basename($_[0])) + cmp + IkiWiki::pagetitle(IkiWiki::basename($_[1])) +} + +sub cmp_mtime { $IkiWiki::pagemtime{$_[1]} <=> $IkiWiki::pagemtime{$_[0]} } +sub cmp_age { $IkiWiki::pagectime{$_[1]} <=> $IkiWiki::pagectime{$_[0]} } + +sub check_cmp_title_natural { + eval q{use Sort::Naturally}; + if ($@) { + error(gettext("Sort::Naturally needed for title_natural sort")); + } +} +sub cmp_title_natural { + Sort::Naturally::ncmp(IkiWiki::pagetitle(IkiWiki::basename($_[0])), + IkiWiki::pagetitle(IkiWiki::basename($_[1]))) +} + 1 diff --git a/IkiWiki/Plugin/meta.pm b/IkiWiki/Plugin/meta.pm index a470041c9..e8cc1e392 100644 --- a/IkiWiki/Plugin/meta.pm +++ b/IkiWiki/Plugin/meta.pm @@ -13,7 +13,6 @@ sub import { hook(type => "needsbuild", id => "meta", call => \&needsbuild); hook(type => "preprocess", id => "meta", call => \&preprocess, scan => 1); hook(type => "pagetemplate", id => "meta", call => \&pagetemplate); - hook(type => "sort", id => "meta_title", call => \&sort_meta_title); } sub getsetup () { @@ -299,10 +298,6 @@ sub titlesort { return pagetitle(IkiWiki::basename($_[0])); } -sub sort_meta_title { - return titlesort($_[0]) cmp titlesort($_[1]); -} - sub match { my $field=shift; my $page=shift; @@ -353,4 +348,10 @@ sub match_copyright ($$;@) { IkiWiki::Plugin::meta::match("copyright", @_); } +sub cmp_meta_title { + IkiWiki::Plugin::meta::titlesort($_[0]) + cmp + IkiWiki::Plugin::meta::titlesort($_[1]) +} + 1 diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn index 1010e76e4..de2b47015 100644 --- a/doc/plugins/write.mdwn +++ b/doc/plugins/write.mdwn @@ -588,36 +588,6 @@ describes the plugin as a whole. For example: This hook is used to inject C code (which it returns) into the `main` function of the ikiwiki wrapper when it is being generated. -### sort - - hook(type => "sort", id => "foo", call => \&sort_by_foo); - -This hook adds an additional [[ikiwiki/pagespec/sorting]] order or overrides -an existing one. - -The callback is given two page names followed by the parameter as arguments, and -returns negative, zero or positive if the first page should come before, -close to (i.e. undefined order), or after the second page. - -For instance, the built-in `title` sort order could be reimplemented as - - sub sort_by_title { - pagetitle(basename($_[0])) cmp pagetitle(basename($_[1])); - } - -and to sort by an arbitrary `meta` value, you could use: - - # usage: sort="meta(description)" - sub sort_by_meta { - my $param = $_[2]; - error "sort=meta requires a parameter" unless defined $param; - my $left = $pagestate{$_[0]}{meta}{$param}; - $left = "" unless defined $left; - my $right = $pagestate{$_[1]}{meta}{$param}; - $right = "" unless defined $right; - return $left cmp $right; - } - ## Exported variables Several variables are exported to your plugin when you `use IkiWiki;` @@ -1140,6 +1110,29 @@ For example, "backlink(foo)" is influenced by the contents of page foo; they match; "created_before(foo)" is influenced by the metadata of foo; while "glob(*)" is not influenced by the contents of any page. +### Sorting plugins + +Similarly, it's possible to write plugins that add new functions as +[[ikiwiki/pagespec/sorting]] methods. To achieve this, add a function to +the IkiWiki::PageSpec package named `cmp_foo`, which will be used when sorting +by `foo` or `foo(...)` is requested. + +The function will be passed three or more parameters. The first two are +page names, and the third is `undef` if invoked as `foo`, or the parameter +`"bar"` if invoked as `foo(bar)`. It may also be passed additional, named +parameters. + +It should return the same thing as Perl's `cmp` and `<=>` operators: negative +if the first argument is less than the second, positive if the first argument +is greater, or zero if they are considered equal. It may also raise an +error using `error`, for instance if it needs a parameter but one isn't +provided. + +You can also define a function called `check_cmp_foo` in the same package. +If you do, it will be called while preparing to sort by `foo` or `foo(bar)`, +with argument `undef` or `"bar"` respectively; it may raise an error using +`error`, if sorting like that isn't going to work. + ### Setup plugins The ikiwiki setup file is loaded using a pluggable mechanism. If you look diff --git a/t/pagespec_match_list.t b/t/pagespec_match_list.t index 309961f1c..743ae4637 100755 --- a/t/pagespec_match_list.t +++ b/t/pagespec_match_list.t @@ -9,7 +9,11 @@ BEGIN { use_ok("IkiWiki"); } $config{srcdir}=$config{destdir}="/dev/null"; IkiWiki::checkconfig(); -hook(type => "sort", id => "path", call => sub { $_[0] cmp $_[1] }); +{ + package IkiWiki::PageSpec; + + sub cmp_path { $_[0] cmp $_[1] } +} %pagesources=( foo => "foo.mdwn", -- cgit v1.2.3 From 0d524ad672333fd0bafa64e81e261fd297c25580 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Fri, 26 Mar 2010 01:38:53 -0400 Subject: Fix incorrect influence info returned by a failing link() pagespec, that could lead to bad dependency handling in certian situations. --- IkiWiki.pm | 4 ++-- debian/changelog | 2 ++ doc/bugs/depends_simple_mixup.mdwn | 5 +++++ t/pagespec_match.t | 8 +++++++- 4 files changed, 16 insertions(+), 3 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 022bfe3bd..927d62940 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -2215,7 +2215,7 @@ sub match_link ($$;@) { my $from=exists $params{location} ? $params{location} : ''; my $links = $IkiWiki::links{$page}; - return IkiWiki::FailReason->new("$page has no links", "" => 1) + return IkiWiki::FailReason->new("$page has no links", $page => $IkiWiki::DEPEND_LINKS, "" => 1) unless $links && @{$links}; my $bestlink = IkiWiki::bestlink($from, $link); foreach my $p (@{$links}) { @@ -2232,7 +2232,7 @@ sub match_link ($$;@) { if match_glob($p_rel, $link, %params); } } - return IkiWiki::FailReason->new("$page does not link to $link", "" => 1); + return IkiWiki::FailReason->new("$page does not link to $link", $page => $IkiWiki::DEPEND_LINKS, "" => 1); } sub match_backlink ($$;@) { diff --git a/debian/changelog b/debian/changelog index da1ab890e..b9a105552 100644 --- a/debian/changelog +++ b/debian/changelog @@ -17,6 +17,8 @@ ikiwiki (3.20100324) UNRELEASED; urgency=low * Add --set-yaml switch for setting more complex config file options. * filecheck: Fix bugs that prevented the pagespecs from matching when not called by attachment plugin. + * Fix incorrect influence info returned by a failing link() pagespec, + that could lead to bad dependency handling in certian situations. -- Joey Hess Sat, 13 Mar 2010 14:48:10 -0500 diff --git a/doc/bugs/depends_simple_mixup.mdwn b/doc/bugs/depends_simple_mixup.mdwn index c2845240d..2603ff04c 100644 --- a/doc/bugs/depends_simple_mixup.mdwn +++ b/doc/bugs/depends_simple_mixup.mdwn @@ -15,4 +15,9 @@ dependency. But, it seems to me it should still be listed in Then re-add the done link, and the dependency calc code breaks down, not noticing that bugs dependeded on the page and needs to be updated. + +Ok.. Turns out this was not a problem with the actual influences +calculation or dependency calculation code. Whew! `match_link` +just didn't set the influence correctly when failing. [[fixed|done]] + --[[Joey]] diff --git a/t/pagespec_match.t b/t/pagespec_match.t index 8b0be4e8a..ade9bca5a 100755 --- a/t/pagespec_match.t +++ b/t/pagespec_match.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 72; +use Test::More tests => 75; BEGIN { use_ok("IkiWiki"); } @@ -54,6 +54,7 @@ $config{userdir}=""; $links{foo}=[qw{bar baz}]; $links{bar}=[]; $links{baz}=[]; +$links{meh}=[]; $links{"bugs/foo"}=[qw{bugs/done}]; $links{"bugs/done"}=[]; $links{"bugs/bar"}=[qw{done}]; @@ -82,6 +83,7 @@ ok(! pagespec_match("bar", ""), "empty pagespec should match nothing"); ok(! pagespec_match("bar", " "), "blank pagespec should match nothing"); ok(pagespec_match("ook", "link(blog/tags/foo)"), "link internal absolute success"); ok(pagespec_match("ook", "link(/blog/tags/foo)"), "link explicit absolute success"); +ok(pagespec_match("meh", "!link(done)"), "negated failing match is a success"); $IkiWiki::pagectime{foo}=1154532692; # Wed Aug 2 11:26 EDT 2006 $IkiWiki::pagectime{bar}=1154532695; # after @@ -122,3 +124,7 @@ $i=pagespec_match("foo", "link(baz) and created_after(bar)")->influences; is(join(",", sort keys %$i), 'bar,foo', "influences add up over OR"); $i=pagespec_match("foo", "!link(baz) and !created_after(bar)")->influences; is(join(",", sort keys %$i), 'bar,foo', "influences unaffected by negation"); +$i=pagespec_match("foo", "!link(baz) and !created_after(bar)")->influences; +is(join(",", sort keys %$i), 'bar,foo', "influences unaffected by negation"); +$i=pagespec_match("meh", "!link(done)")->influences; +is(join(",", sort keys %$i), 'meh', "a negated, failing link test is successful, so the page is a link influence"); -- cgit v1.2.3 From 04a59b3c65e8e60805b6ed6d11d448b1d5babe64 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Sat, 3 Apr 2010 13:57:38 +0100 Subject: Move sort hooks to the IkiWiki::SortSpec namespace Also rename cmpspec_translate (internal function) to sortspec_translate for consistency. --- IkiWiki.pm | 14 ++++++++------ IkiWiki/Plugin/meta.pm | 2 ++ IkiWiki/Plugin/sortnaturally.pm | 2 +- doc/plugins/write.mdwn | 2 +- t/pagespec_match_list.t | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 7547f1751..d716e8b39 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -37,7 +37,7 @@ our $DEPEND_LINKS=4; # Optimisation. use Memoize; memoize("abs2rel"); -memoize("cmpspec_translate"); +memoize("sortspec_translate"); memoize("pagespec_translate"); memoize("template_file"); @@ -1935,7 +1935,7 @@ sub add_link ($$) { unless grep { $_ eq $link } @{$links{$page}}; } -sub cmpspec_translate ($) { +sub sortspec_translate ($) { my $spec = shift; my $code = ""; @@ -1972,13 +1972,13 @@ sub cmpspec_translate ($) { $code .= "-"; } - if (exists $IkiWiki::PageSpec::{"cmp_$word"}) { + if (exists $IkiWiki::SortSpec::{"cmp_$word"}) { if (defined $params) { push @data, $params; - $code .= "IkiWiki::PageSpec::cmp_$word(\@_, \$data[$#data])"; + $code .= "IkiWiki::SortSpec::cmp_$word(\@_, \$data[$#data])"; } else { - $code .= "IkiWiki::PageSpec::cmp_$word(\@_, undef)"; + $code .= "IkiWiki::SortSpec::cmp_$word(\@_, undef)"; } } else { @@ -2095,7 +2095,7 @@ sub pagespec_match_list ($$;@) { } if (defined $params{sort}) { - my $f = cmpspec_translate($params{sort}); + my $f = sortspec_translate($params{sort}); @candidates = sort { $f->($a, $b) } @candidates; } @@ -2410,6 +2410,8 @@ sub match_ip ($$;@) { } } +package IkiWiki::SortSpec; + sub cmp_title { IkiWiki::pagetitle(IkiWiki::basename($_[0])) cmp diff --git a/IkiWiki/Plugin/meta.pm b/IkiWiki/Plugin/meta.pm index e8cc1e392..cd7d0d127 100644 --- a/IkiWiki/Plugin/meta.pm +++ b/IkiWiki/Plugin/meta.pm @@ -348,6 +348,8 @@ sub match_copyright ($$;@) { IkiWiki::Plugin::meta::match("copyright", @_); } +package IkiWiki::SortSpec; + sub cmp_meta_title { IkiWiki::Plugin::meta::titlesort($_[0]) cmp diff --git a/IkiWiki/Plugin/sortnaturally.pm b/IkiWiki/Plugin/sortnaturally.pm index 0023f31f9..f498820a5 100644 --- a/IkiWiki/Plugin/sortnaturally.pm +++ b/IkiWiki/Plugin/sortnaturally.pm @@ -22,7 +22,7 @@ sub checkconfig () { error $@ if $@; } -package IkiWiki::PageSpec; +package IkiWiki::SortSpec; sub cmp_title_natural { Sort::Naturally::ncmp(IkiWiki::pagetitle(IkiWiki::basename($_[0])), diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn index 06c8f8e44..b67142230 100644 --- a/doc/plugins/write.mdwn +++ b/doc/plugins/write.mdwn @@ -1114,7 +1114,7 @@ while "glob(*)" is not influenced by the contents of any page. Similarly, it's possible to write plugins that add new functions as [[ikiwiki/pagespec/sorting]] methods. To achieve this, add a function to -the IkiWiki::PageSpec package named `cmp_foo`, which will be used when sorting +the IkiWiki::SortSpec package named `cmp_foo`, which will be used when sorting by `foo` or `foo(...)` is requested. The function will be passed three or more parameters. The first two are diff --git a/t/pagespec_match_list.t b/t/pagespec_match_list.t index 743ae4637..68112f5c0 100755 --- a/t/pagespec_match_list.t +++ b/t/pagespec_match_list.t @@ -10,7 +10,7 @@ $config{srcdir}=$config{destdir}="/dev/null"; IkiWiki::checkconfig(); { - package IkiWiki::PageSpec; + package IkiWiki::SortSpec; sub cmp_path { $_[0] cmp $_[1] } } -- cgit v1.2.3 From c1a42e76bc6667bfb2882a12d53c25d9f952ca82 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Fri, 2 Apr 2010 00:28:02 +0100 Subject: implement typed links; add tagged_is_strict config option --- IkiWiki.pm | 58 ++++++++++++++++++---- IkiWiki/Plugin/tag.pm | 36 +++++++++----- IkiWiki/Render.pm | 33 ++++++++++++ .../tagged__40____41___matching_wikilinks.mdwn | 3 ++ doc/ikiwiki/pagespec.mdwn | 3 ++ doc/plugins/tag.mdwn | 5 ++ doc/plugins/write.mdwn | 21 +++++++- t/index.t | 17 +++++-- t/tag.t | 45 +++++++++++++++++ 9 files changed, 193 insertions(+), 28 deletions(-) create mode 100755 t/tag.t (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 6739ba56c..25e9247b2 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -14,7 +14,7 @@ use open qw{:utf8 :std}; use vars qw{%config %links %oldlinks %pagemtime %pagectime %pagecase %pagestate %wikistate %renderedfiles %oldrenderedfiles %pagesources %destsources %depends %depends_simple %hooks - %forcerebuild %loaded_plugins}; + %forcerebuild %loaded_plugins %typedlinks %oldtypedlinks}; use Exporter q{import}; 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 inject add_link %config %links %pagestate %wikistate %renderedfiles - %pagesources %destsources); + %pagesources %destsources %typedlinks); our $VERSION = 3.00; # plugin interface version, next is ikiwiki version our $version='unknown'; # VERSION_AUTOREPLACE done by Makefile, DNE our $installdir='/usr'; # INSTALLDIR_AUTOREPLACE done by Makefile, DNE @@ -1503,7 +1503,7 @@ sub loadindex () { if (! $config{rebuild}) { %pagesources=%pagemtime=%oldlinks=%links=%depends= %destsources=%renderedfiles=%pagecase=%pagestate= - %depends_simple=(); + %depends_simple=%typedlinks=%oldtypedlinks=(); } my $in; if (! open ($in, "<", "$config{wikistatedir}/indexdb")) { @@ -1569,6 +1569,14 @@ sub loadindex () { if (exists $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}}]; } @@ -1617,6 +1625,10 @@ sub saveindex () { $index{page}{$src}{depends_simple} = $depends_simple{$page}; } + if (exists $typedlinks{$page} && %{$typedlinks{$page}}) { + $index{page}{$src}{typedlinks} = $typedlinks{$page}; + } + if (exists $pagestate{$page}) { foreach my $id (@hookids) { foreach my $key (keys %{$pagestate{$page}{$id}}) { @@ -1926,12 +1938,17 @@ sub inject { use warnings; } -sub add_link ($$) { +sub add_link ($$;$) { my $page=shift; my $link=shift; + my $type=shift; push @{$links{$page}}, $link unless grep { $_ eq $link } @{$links{$page}}; + + if (defined $type) { + $typedlinks{$page}{$type}{$link} = 1; + } } sub pagespec_translate ($) { @@ -2212,6 +2229,11 @@ sub match_link ($$;@) { $link=derel($link, $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}; return IkiWiki::FailReason->new("$page has no links", $page => $IkiWiki::DEPEND_LINKS, "" => 1) @@ -2219,19 +2241,33 @@ sub match_link ($$;@) { my $bestlink = IkiWiki::bestlink($from, $link); foreach my $p (@{$links}) { if (length $bestlink) { - return IkiWiki::SuccessReason->new("$page links to $link", $page => $IkiWiki::DEPEND_LINKS, "" => 1) - if $bestlink eq IkiWiki::bestlink($page, $p); + if ((!defined $linktype || exists $IkiWiki::typedlinks{$page}{$linktype}{$p}) && $bestlink eq IkiWiki::bestlink($page, $p)) { + return IkiWiki::SuccessReason->new("$page links to $link$qualifier", $page => $IkiWiki::DEPEND_LINKS, "" => 1) + } } else { - return IkiWiki::SuccessReason->new("$page links to page $p matching $link", $page => $IkiWiki::DEPEND_LINKS, "" => 1) - if match_glob($p, $link, %params); + if ((!defined $linktype || exists $IkiWiki::typedlinks{$page}{$linktype}{$p}) && 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=~/^\/?(.*)/; $link=~s/^\///; - return IkiWiki::SuccessReason->new("$page links to page $p_rel matching $link", $page => $IkiWiki::DEPEND_LINKS, "" => 1) - if match_glob($p_rel, $link, %params); + if ((!defined $linktype || exists $IkiWiki::typedlinks{$page}{$linktype}{$p_rel}) && 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_typedlink($$;@) { + my $page = shift; + my $args = shift; + + if ($args =~ /^(\w+)\s+(.*)$/) { + return match_link($page, $2, @_, linktype => $1); + } + + return IkiWiki::ErrorReason->new("typedlink expects e.g. 'tag *' but got: $args"); } sub match_backlink ($$;@) { diff --git a/IkiWiki/Plugin/tag.pm b/IkiWiki/Plugin/tag.pm index cdcfaf536..af4bff1bc 100644 --- a/IkiWiki/Plugin/tag.pm +++ b/IkiWiki/Plugin/tag.pm @@ -6,8 +6,6 @@ use warnings; use strict; use IkiWiki 3.00; -my %tags; - sub import { hook(type => "getopt", id => "tag", call => \&getopt); hook(type => "getsetup", id => "tag", call => \&getsetup); @@ -36,6 +34,13 @@ sub getsetup () { safe => 1, rebuild => 1, }, + tagged_is_strict => { + type => "boolean", + default => 0, + description => "if 1, tagged() doesn't match normal WikiLinks to tag pages", + safe => 1, + rebuild => 1, + }, } sub tagpage ($) { @@ -71,9 +76,8 @@ sub preprocess_tag (@) { foreach my $tag (keys %params) { $tag=linkpage($tag); - $tags{$page}{$tag}=1; # hidden WikiLink - add_link($page, tagpage($tag)); + add_link($page, tagpage($tag), 'tag'); } return ""; @@ -87,15 +91,13 @@ sub preprocess_taglink (@) { return join(" ", map { if (/(.*)\|(.*)/) { my $tag=linkpage($2); - $tags{$params{page}}{$tag}=1; - add_link($params{page}, tagpage($tag)); + add_link($params{page}, tagpage($tag), 'tag'); return taglink($params{page}, $params{destpage}, $tag, linktext => pagetitle($1)); } else { my $tag=linkpage($_); - $tags{$params{page}}{$tag}=1; - add_link($params{page}, tagpage($tag)); + add_link($params{page}, tagpage($tag), 'tag'); return taglink($params{page}, $params{destpage}, $tag); } } @@ -110,17 +112,19 @@ sub pagetemplate (@) { my $destpage=$params{destpage}; my $template=$params{template}; + my $tags = $typedlinks{$page}{tag}; + $template->param(tags => [ map { link => taglink($page, $destpage, $_, rel => "tag") - }, sort keys %{$tags{$page}} - ]) if exists $tags{$page} && %{$tags{$page}} && $template->query(name => "tags"); + }, sort keys %$tags + ]) if defined $tags && %$tags && $template->query(name => "tags"); if ($template->query(name => "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 => $_ }, - sort keys %{$tags{$page}}]); + sort keys %$tags]); } } } @@ -130,7 +134,13 @@ package IkiWiki::PageSpec; sub match_tagged ($$;@) { my $page = shift; my $glob = shift; - return match_link($page, IkiWiki::Plugin::tag::tagpage($glob)); + + if ($IkiWiki::config{tagged_is_strict}) { + return match_link($page, IkiWiki::Plugin::tag::tagpage($glob), linktype => 'tag'); + } + else { + return match_link($page, IkiWiki::Plugin::tag::tagpage($glob)); + } } 1 diff --git a/IkiWiki/Render.pm b/IkiWiki/Render.pm index abafb0887..5810fc974 100644 --- a/IkiWiki/Render.pm +++ b/IkiWiki/Render.pm @@ -167,6 +167,7 @@ sub scan ($) { else { $links{$page}=[]; } + delete $typedlinks{$page}; run_hooks(scan => sub { shift->( @@ -398,6 +399,7 @@ sub find_del_files ($) { push @del, $pagesources{$page}; } $links{$page}=[]; + delete $typedlinks{$page}; $renderedfiles{$page}=[]; $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 ($$$) { my ($changed, $del, $oldlink_targets)=@_; @@ -525,6 +550,14 @@ sub calculate_changed_links ($$$) { } $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}, $oldlinktypes{$page})) { + $linkchangers{lc($page)}=1; + } + } } return \%backlinkchanged, \%linkchangers; diff --git a/doc/bugs/tagged__40____41___matching_wikilinks.mdwn b/doc/bugs/tagged__40____41___matching_wikilinks.mdwn index e7e4af7c3..9037d6c02 100644 --- a/doc/bugs/tagged__40____41___matching_wikilinks.mdwn +++ b/doc/bugs/tagged__40____41___matching_wikilinks.mdwn @@ -28,6 +28,9 @@ 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 >> how it will eventually be solved. --[[Joey]] +>>> [[Done]]: you can now set the `tagged_is_strict` config option to `1` +>>> if you don't want `tagged` to match 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. > 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. diff --git a/doc/ikiwiki/pagespec.mdwn b/doc/ikiwiki/pagespec.mdwn index 5c191f23f..ca6693024 100644 --- a/doc/ikiwiki/pagespec.mdwn +++ b/doc/ikiwiki/pagespec.mdwn @@ -52,6 +52,9 @@ Some more elaborate limits can be added to what matches using these functions: specified IP address. * "`postcomment(glob)`" - matches only when comments are being posted to a page matching the specified glob +* "`typedlink(type glob)`" - matches pages that link to a given page (or glob) + with a given link type. Plugins can create links with a specific type: + for instance, the tag plugin creates links of type `tag`. For example, to match all pages in a blog that link to the page about music and were written in 2005: diff --git a/doc/plugins/tag.mdwn b/doc/plugins/tag.mdwn index 8ff70a069..8cd79da41 100644 --- a/doc/plugins/tag.mdwn +++ b/doc/plugins/tag.mdwn @@ -8,6 +8,11 @@ These directives allow tagging pages. It also provides the `tagged()` [[ikiwiki/PageSpec]], which can be used to match pages that are tagged with a specific tag. +If the `tagged_is_strict` config option is set, `tagged()` will only match +tags explicitly set with [[ikiwiki/directive/tag]] or +[[ikiwiki/directive/taglink]]; if not (the default), it will also match +any other [[WikiLinks|ikiwiki/WikiLink]] to the tag page. + [[!if test="enabled(tag)" then=""" This wiki has the tag plugin enabled, so you'll see a note below that this page is tagged with the "tags" tag. diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn index 96a2aa16d..fe7cf0183 100644 --- a/doc/plugins/write.mdwn +++ b/doc/plugins/write.mdwn @@ -633,6 +633,22 @@ reference. Do not modify this hash directly; call `add_link()`. $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` 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")` will yield something like `foo/feed.rss`. -### `add_link($$)` +### `add_link($$;$)` 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. +An optional third parameter sets the link type (`undef` produces an ordinary +[[ikiwiki/WikiLink]]). + ## Miscellaneous ### Internal use pages diff --git a/t/index.t b/t/index.t index 2f23524a7..44273059d 100755 --- a/t/index.t +++ b/t/index.t @@ -4,7 +4,7 @@ use strict; use IkiWiki; package IkiWiki; # use internal variables -use Test::More tests => 27; +use Test::More tests => 31; $config{wikistatedir}="/tmp/ikiwiki-test.$$"; system "rm -rf $config{wikistatedir}"; @@ -31,6 +31,7 @@ $renderedfiles{"bar"}=["bar.html", "bar.rss", "sparkline-foo.gif"]; $renderedfiles{"bar.png"}=["bar.png"]; $links{"Foo"}=["bar.png"]; $links{"bar"}=["Foo", "new-page"]; +$typedlinks{"bar"}={tag => {"Foo" => 1}}; $links{"bar.png"}=[]; $depends{"Foo"}={}; $depends{"bar"}={"foo*" => 1}; @@ -45,7 +46,7 @@ ok(-s "$config{wikistatedir}/indexdb", "index file created"); # Clear state. %oldrenderedfiles=%pagectime=(); -%pagesources=%pagemtime=%oldlinks=%links=%depends= +%pagesources=%pagemtime=%oldlinks=%links=%depends=%typedlinks=%oldtypedlinks= %destsources=%renderedfiles=%pagecase=%pagestate=(); ok(loadindex(), "load index"); @@ -104,10 +105,16 @@ is_deeply(\%destsources, { "sparkline-foo.gif" => "bar", "bar.png" => "bar.png", }, "%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. %oldrenderedfiles=%pagectime=(); -%pagesources=%pagemtime=%oldlinks=%links=%depends= +%pagesources=%pagemtime=%oldlinks=%links=%depends=%typedlinks=%oldtypedlinks= %destsources=%renderedfiles=%pagecase=%pagestate=(); # When state is loaded for a wiki rebuild, only ctime and oldrenderedfiles @@ -140,5 +147,9 @@ is_deeply(\%pagecase, { }, "%pagecase generated correctly"); is_deeply(\%destsources, { }, "%destsources generated correctly"); +is_deeply(\%typedlinks, { +}, "%typedlinks cleared correctly"); +is_deeply(\%oldtypedlinks, { +}, "%oldtypedlinks cleared correctly"); system "rm -rf $config{wikistatedir}"; diff --git a/t/tag.t b/t/tag.t new file mode 100755 index 000000000..3383fd475 --- /dev/null +++ b/t/tag.t @@ -0,0 +1,45 @@ +#!/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"; +$config{tagged_is_strict} = 1; + +%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; -- cgit v1.2.3 From ef6344144051ed70649ccbff01bcc4fce927ee2f Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Sun, 4 Apr 2010 00:24:27 +0100 Subject: Remove tagged_is_strict option, and just behave as though it was enabled Jon was worried about the backwards-compatibility break involved in making tagged() not match non-tag links, but Joey seems less concerned about it. --- IkiWiki/Plugin/tag.pm | 17 +---------------- doc/bugs/tagged__40____41___matching_wikilinks.mdwn | 3 +-- doc/plugins/tag.mdwn | 5 ----- t/tag.t | 1 - 4 files changed, 2 insertions(+), 24 deletions(-) (limited to 't') diff --git a/IkiWiki/Plugin/tag.pm b/IkiWiki/Plugin/tag.pm index af4bff1bc..7a85874f6 100644 --- a/IkiWiki/Plugin/tag.pm +++ b/IkiWiki/Plugin/tag.pm @@ -34,13 +34,6 @@ sub getsetup () { safe => 1, rebuild => 1, }, - tagged_is_strict => { - type => "boolean", - default => 0, - description => "if 1, tagged() doesn't match normal WikiLinks to tag pages", - safe => 1, - rebuild => 1, - }, } sub tagpage ($) { @@ -132,15 +125,7 @@ sub pagetemplate (@) { package IkiWiki::PageSpec; sub match_tagged ($$;@) { - my $page = shift; - my $glob = shift; - - if ($IkiWiki::config{tagged_is_strict}) { - return match_link($page, IkiWiki::Plugin::tag::tagpage($glob), linktype => 'tag'); - } - else { - return match_link($page, IkiWiki::Plugin::tag::tagpage($glob)); - } + return match_link($_[0], IkiWiki::Plugin::tag::tagpage($_[1]), linktype => 'tag'); } 1 diff --git a/doc/bugs/tagged__40____41___matching_wikilinks.mdwn b/doc/bugs/tagged__40____41___matching_wikilinks.mdwn index 9037d6c02..a211654f1 100644 --- a/doc/bugs/tagged__40____41___matching_wikilinks.mdwn +++ b/doc/bugs/tagged__40____41___matching_wikilinks.mdwn @@ -28,8 +28,7 @@ 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 >> how it will eventually be solved. --[[Joey]] ->>> [[Done]]: you can now set the `tagged_is_strict` config option to `1` ->>> if you don't want `tagged` to match other wikilinks. --[[smcv]] +>>> [[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. diff --git a/doc/plugins/tag.mdwn b/doc/plugins/tag.mdwn index 8cd79da41..8ff70a069 100644 --- a/doc/plugins/tag.mdwn +++ b/doc/plugins/tag.mdwn @@ -8,11 +8,6 @@ These directives allow tagging pages. It also provides the `tagged()` [[ikiwiki/PageSpec]], which can be used to match pages that are tagged with a specific tag. -If the `tagged_is_strict` config option is set, `tagged()` will only match -tags explicitly set with [[ikiwiki/directive/tag]] or -[[ikiwiki/directive/taglink]]; if not (the default), it will also match -any other [[WikiLinks|ikiwiki/WikiLink]] to the tag page. - [[!if test="enabled(tag)" then=""" This wiki has the tag plugin enabled, so you'll see a note below that this page is tagged with the "tags" tag. diff --git a/t/tag.t b/t/tag.t index 3383fd475..cf3bbdf01 100755 --- a/t/tag.t +++ b/t/tag.t @@ -12,7 +12,6 @@ ok(! system("rm -rf t/tmp; mkdir t/tmp")); $config{userdir} = "users"; $config{tagbase} = "tags"; -$config{tagged_is_strict} = 1; %oldrenderedfiles=%pagectime=(); %pagesources=%pagemtime=%oldlinks=%links=%depends=%typedlinks=%oldtypedlinks= -- cgit v1.2.3 From d7e32dc98d7b9b005f983cd6f3321244f25cf6a5 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Sun, 4 Apr 2010 01:20:02 +0100 Subject: Add a unit test for changed-link calculation --- t/calculate_changed_links.t | 58 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100755 t/calculate_changed_links.t (limited to 't') diff --git a/t/calculate_changed_links.t b/t/calculate_changed_links.t new file mode 100755 index 000000000..bf6e2af45 --- /dev/null +++ b/t/calculate_changed_links.t @@ -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 }); -- cgit v1.2.3 From cb8b2f80b2f8c91eba3f3a6a5b9913ab80326df8 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Mon, 5 Apr 2010 22:50:51 +0100 Subject: Use $a and $b for SortSpec cmp callbacks --- IkiWiki.pm | 27 ++++++++++++++++++--------- IkiWiki/Plugin/meta.pm | 14 ++++++-------- IkiWiki/Plugin/sortnaturally.pm | 4 ++-- doc/plugins/write.mdwn | 20 ++++++++++---------- t/pagespec_match_list.t | 2 +- 5 files changed, 37 insertions(+), 30 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index d716e8b39..da36494fb 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -1975,10 +1975,10 @@ sub sortspec_translate ($) { if (exists $IkiWiki::SortSpec::{"cmp_$word"}) { if (defined $params) { push @data, $params; - $code .= "IkiWiki::SortSpec::cmp_$word(\@_, \$data[$#data])"; + $code .= "IkiWiki::SortSpec::cmp_$word(\$data[$#data])"; } else { - $code .= "IkiWiki::SortSpec::cmp_$word(\@_, undef)"; + $code .= "IkiWiki::SortSpec::cmp_$word(undef)"; } } else { @@ -2095,9 +2095,8 @@ sub pagespec_match_list ($$;@) { } if (defined $params{sort}) { - my $f = sortspec_translate($params{sort}); - - @candidates = sort { $f->($a, $b) } @candidates; + @candidates = IkiWiki::SortSpec::sort_pages($params{sort}, + @candidates); } @candidates=reverse(@candidates) if $params{reverse}; @@ -2412,13 +2411,23 @@ sub match_ip ($$;@) { package IkiWiki::SortSpec; +# This is in the SortSpec namespace so that the $a and $b that sort() uses +# $IkiWiki::SortSpec::a and $IkiWiki::SortSpec::b, so that plugins' cmp +# functions can access them easily. +sub sort_pages +{ + my $f = IkiWiki::sortspec_translate(shift); + + return sort $f @_; +} + sub cmp_title { - IkiWiki::pagetitle(IkiWiki::basename($_[0])) + IkiWiki::pagetitle(IkiWiki::basename($a)) cmp - IkiWiki::pagetitle(IkiWiki::basename($_[1])) + IkiWiki::pagetitle(IkiWiki::basename($b)) } -sub cmp_mtime { $IkiWiki::pagemtime{$_[1]} <=> $IkiWiki::pagemtime{$_[0]} } -sub cmp_age { $IkiWiki::pagectime{$_[1]} <=> $IkiWiki::pagectime{$_[0]} } +sub cmp_mtime { $IkiWiki::pagemtime{$b} <=> $IkiWiki::pagemtime{$a} } +sub cmp_age { $IkiWiki::pagectime{$b} <=> $IkiWiki::pagectime{$a} } 1 diff --git a/IkiWiki/Plugin/meta.pm b/IkiWiki/Plugin/meta.pm index 4992617d0..553f93455 100644 --- a/IkiWiki/Plugin/meta.pm +++ b/IkiWiki/Plugin/meta.pm @@ -374,25 +374,23 @@ sub match_copyright ($$;@) { package IkiWiki::SortSpec; sub cmp_meta { - my $left = $_[0]; - my $right = $_[1]; - my $meta = $_[2]; + my $meta = $_[0]; error(gettext("sort=meta requires a parameter")) unless defined $meta; if ($meta eq 'updated' || $meta eq 'date') { - return IkiWiki::Plugin::meta::get_sort_key($left, $meta) + return IkiWiki::Plugin::meta::get_sort_key($a, $meta) <=> - IkiWiki::Plugin::meta::get_sort_key($right, $meta); + IkiWiki::Plugin::meta::get_sort_key($b, $meta); } - return IkiWiki::Plugin::meta::get_sort_key($left, $meta) + return IkiWiki::Plugin::meta::get_sort_key($a, $meta) cmp - IkiWiki::Plugin::meta::get_sort_key($right, $meta); + IkiWiki::Plugin::meta::get_sort_key($b, $meta); } # A prototype of how sort=title could behave in 4.0 or something sub cmp_meta_title { - $_[2] = 'title'; + $_[0] = 'title'; return cmp_meta(@_); } diff --git a/IkiWiki/Plugin/sortnaturally.pm b/IkiWiki/Plugin/sortnaturally.pm index f498820a5..92453749d 100644 --- a/IkiWiki/Plugin/sortnaturally.pm +++ b/IkiWiki/Plugin/sortnaturally.pm @@ -25,8 +25,8 @@ sub checkconfig () { package IkiWiki::SortSpec; sub cmp_title_natural { - Sort::Naturally::ncmp(IkiWiki::pagetitle(IkiWiki::basename($_[0])), - IkiWiki::pagetitle(IkiWiki::basename($_[1]))) + Sort::Naturally::ncmp(IkiWiki::pagetitle(IkiWiki::basename($a)), + IkiWiki::pagetitle(IkiWiki::basename($b))) } 1; diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn index b67142230..f42cc86ae 100644 --- a/doc/plugins/write.mdwn +++ b/doc/plugins/write.mdwn @@ -1117,16 +1117,16 @@ Similarly, it's possible to write plugins that add new functions as the IkiWiki::SortSpec package named `cmp_foo`, which will be used when sorting by `foo` or `foo(...)` is requested. -The function will be passed three or more parameters. The first two are -page names, and the third is `undef` if invoked as `foo`, or the parameter -`"bar"` if invoked as `foo(bar)`. It may also be passed additional, named -parameters. - -It should return the same thing as Perl's `cmp` and `<=>` operators: negative -if the first argument is less than the second, positive if the first argument -is greater, or zero if they are considered equal. It may also raise an -error using `error`, for instance if it needs a parameter but one isn't -provided. +The names of pages to be compared are in the global variables `$a` and `$b` +in the IkiWiki::SortSpec package. The function should return the same thing +as Perl's `cmp` and `<=>` operators: negative if `$a` is less than `$b`, +positive if `$a` is greater, or zero if they are considered equal. It may +also raise an error using `error`, for instance if it needs a parameter but +one isn't provided. + +The function will also be passed one or more parameters. The first is +`undef` if invoked as `foo`, or the parameter `"bar"` if invoked as `foo(bar)`; +it may also be passed additional, named parameters. ### Setup plugins diff --git a/t/pagespec_match_list.t b/t/pagespec_match_list.t index 68112f5c0..2ad7a9105 100755 --- a/t/pagespec_match_list.t +++ b/t/pagespec_match_list.t @@ -12,7 +12,7 @@ IkiWiki::checkconfig(); { package IkiWiki::SortSpec; - sub cmp_path { $_[0] cmp $_[1] } + sub cmp_path { $a cmp $b } } %pagesources=( -- cgit v1.2.3 From 1004e6c739aaf2d66acd41e5a8a8fc3b6d4ba2c1 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sat, 10 Apr 2010 14:40:03 -0400 Subject: fix testsuite typedlink matchspec was removed --- t/tag.t | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 't') diff --git a/t/tag.t b/t/tag.t index cf3bbdf01..fe547d85b 100755 --- a/t/tag.t +++ b/t/tag.t @@ -3,7 +3,7 @@ package IkiWiki; use warnings; use strict; -use Test::More tests => 10; +use Test::More tests => 7; BEGIN { use_ok("IkiWiki"); } BEGIN { use_ok("IkiWiki::Plugin::tag"); } @@ -36,9 +36,4 @@ 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; -- cgit v1.2.3 From 0bfc364a7df124509855b8ed0b1b33ab5bc9ebbb Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 11 Apr 2010 01:30:03 -0400 Subject: optimization: pagespec_match_list with no num limit matches before sorting This can be a lot faster, since huge numbers of pages are not sorted only to mostly be thrown away. It sped up a build of my blog by at least 5 minutes. --- IkiWiki.pm | 38 ++++++++++++++++++++++++++------------ doc/todo/smarter_sorting.mdwn | 3 +++ t/pagespec_match_list.t | 6 +++++- 3 files changed, 34 insertions(+), 13 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 2cad6a3ef..74d452c50 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -1951,8 +1951,9 @@ sub add_link ($$;$) { } } -sub sortspec_translate ($) { +sub sortspec_translate ($$) { my $spec = shift; + my $reverse = shift; my $code = ""; my @data; @@ -2007,6 +2008,10 @@ sub sortspec_translate ($) { return sub { 0 }; } + if ($reverse) { + $code="-($code)"; + } + no warnings; return eval 'sub { '.$code.' }'; } @@ -2109,18 +2114,20 @@ sub pagespec_match_list ($$;@) { ? grep { ! $params{filter}->($_) } keys %pagesources : keys %pagesources; } - - if (defined $params{sort}) { - @candidates = IkiWiki::SortSpec::sort_pages($params{sort}, - @candidates); + + my $num=$params{num}; + my $sort=$params{sort}; + my $reverse=$params{reverse}; + # when only the top matches will be returned, it's efficient to + # sort before matching to pagespec, + if (defined $num && defined $sort) { + @candidates=IkiWiki::SortSpec::sort_pages( + $sort, $reverse, @candidates); } - - @candidates=reverse(@candidates) if $params{reverse}; $depends{$page}{$pagespec} |= ($params{deptype} || $DEPEND_CONTENT); # clear params, remainder is passed to pagespec - my $num=$params{num}; delete @params{qw{num deptype reverse sort filter list}}; my @matches; @@ -2144,7 +2151,15 @@ sub pagespec_match_list ($$;@) { $depends_simple{$page}{lc $k} |= $i->{$k}; } - return @matches; + # when all matches will be returned, it's efficient to + # sort after matching + if (! defined $num && defined $sort) { + return IkiWiki::SortSpec::sort_pages( + $sort, $reverse, @matches); + } + else { + return @matches; + } } sub pagespec_valid ($) { @@ -2437,9 +2452,8 @@ package IkiWiki::SortSpec; # This is in the SortSpec namespace so that the $a and $b that sort() uses # are easily available in this namespace, for cmp functions to use them. sub sort_pages { - my $f = IkiWiki::sortspec_translate(shift); - - return sort $f @_; + my $f=IkiWiki::sortspec_translate(shift, shift); + sort $f @_ } sub cmp_title { diff --git a/doc/todo/smarter_sorting.mdwn b/doc/todo/smarter_sorting.mdwn index 5a6d63ef1..f8ac216dc 100644 --- a/doc/todo/smarter_sorting.mdwn +++ b/doc/todo/smarter_sorting.mdwn @@ -29,5 +29,8 @@ date range.) Well, in this case, it *does* make sense to flip it, limit by pagespe first, and do a (quick)sort second. (No influence complications, either.) +> Flipping when there's no limit implemented, and it knocked 1/3 off +> the rebuild time of my blog's archive pages. --[[Joey]] + Adding these special cases will be more complicated, but I think the best of both worlds. --[[Joey]] diff --git a/t/pagespec_match_list.t b/t/pagespec_match_list.t index 2ad7a9105..c7688c6c0 100755 --- a/t/pagespec_match_list.t +++ b/t/pagespec_match_list.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 90; +use Test::More tests => 92; BEGIN { use_ok("IkiWiki"); } @@ -38,12 +38,16 @@ $links{foo3}=[qw{bar}]; is_deeply([pagespec_match_list("foo", "bar")], ["bar"]); is_deeply([sort(pagespec_match_list("foo", "* and !post/*"))], ["bar", "foo", "foo2", "foo3"]); is_deeply([sort(pagespec_match_list("foo", "post/*"))], ["post/1", "post/2", "post/3"]); +is_deeply([pagespec_match_list("foo", "post/*", sort => "title")], + ["post/1", "post/2", "post/3"]); is_deeply([pagespec_match_list("foo", "post/*", sort => "title", reverse => 1)], ["post/3", "post/2", "post/1"]); is_deeply([pagespec_match_list("foo", "post/*", sort => "title", num => 2)], ["post/1", "post/2"]); is_deeply([pagespec_match_list("foo", "post/*", sort => "title", num => 50)], ["post/1", "post/2", "post/3"]); +is_deeply([pagespec_match_list("foo", "post/*", sort => "title", num => 50, reverse => 1)], + ["post/3", "post/2", "post/1"]); is_deeply([pagespec_match_list("foo", "post/*", sort => "title", filter => sub { $_[0] =~ /3/}) ], ["post/1", "post/2"]); -- cgit v1.2.3 From f5da8d3aed4b710983c7492cf708d0c874bce9e7 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Mon, 12 Apr 2010 15:16:57 -0400 Subject: more tests with more items --- t/pagespec_match_list.t | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 't') diff --git a/t/pagespec_match_list.t b/t/pagespec_match_list.t index c7688c6c0..05dc012fe 100755 --- a/t/pagespec_match_list.t +++ b/t/pagespec_match_list.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 92; +use Test::More tests => 94; BEGIN { use_ok("IkiWiki"); } @@ -132,3 +132,14 @@ foreach my $spec ("nosuchpage or link(bar)", "link(bar) or nosuchpage", %IkiWiki::depends_simple=(); %IkiWiki::depends=(); } + +my @ps; +foreach my $p (100..500) { + $IkiWiki::pagectime{"p/$p"} = $p; + $pagesources{"p/$p"} = "p/$p.mdwn"; + unshift @ps, "p/$p"; +} +is_deeply([pagespec_match_list("foo", "p/*", sort => "age")], + [@ps]); +is_deeply([pagespec_match_list("foo", "p/*", sort => "age", num => 20)], + [@ps[0..19]]); -- cgit v1.2.3 From 9ff5174f1164cb2d31ff487a9c84d08559b81d39 Mon Sep 17 00:00:00 2001 From: Jelmer Vernooij Date: Sat, 17 Apr 2010 18:45:23 +0200 Subject: Fix get_ctime implementation in Bazaar plugin, add get_mtime implementation. --- IkiWiki/Plugin/bzr.pm | 28 +++++++++++++++++++--------- t/bazaar.t | 5 ++++- 2 files changed, 23 insertions(+), 10 deletions(-) (limited to 't') diff --git a/IkiWiki/Plugin/bzr.pm b/IkiWiki/Plugin/bzr.pm index f79ca7c8f..3712302ce 100644 --- a/IkiWiki/Plugin/bzr.pm +++ b/IkiWiki/Plugin/bzr.pm @@ -286,14 +286,8 @@ sub rcs_diff ($) { } } -sub rcs_getctime ($) { - my ($file) = @_; - - # XXX filename passes through the shell here, should try to avoid - # that just in case - my @cmdline = ("bzr", "log", "--limit", '1', "$config{srcdir}/$file"); - open (my $out, "@cmdline |"); - +sub extract_timestamp ($) { + my ($out) = @_; my @log = bzr_log($out); if (length @log < 1) { @@ -307,8 +301,24 @@ sub rcs_getctime ($) { return $ctime; } +sub rcs_getctime ($) { + my ($file) = @_; + + # XXX filename passes through the shell here, should try to avoid + # that just in case + my @cmdline = ("bzr", "log", "--forward", "--limit", '1', "$config{srcdir}/$file"); + open (my $out, "@cmdline |"); + return extract_timestamp($out); +} + sub rcs_getmtime ($) { - error "rcs_getmtime is not implemented for bzr\n"; # TODO + my ($file) = @_; + + # XXX filename passes through the shell here, should try to avoid + # that just in case + my @cmdline = ("bzr", "log", "--limit", '1', "$config{srcdir}/$file"); + open (my $out, "@cmdline |"); + return extract_timestamp($out); } 1 diff --git a/t/bazaar.t b/t/bazaar.t index 2ca44a65e..0bdd883d5 100755 --- a/t/bazaar.t +++ b/t/bazaar.t @@ -12,7 +12,7 @@ BEGIN { } } } -use Test::More tests => 16; +use Test::More tests => 17; BEGIN { use_ok("IkiWiki"); } @@ -60,6 +60,9 @@ is($changes[1]{pages}[0]{"page"}, "test1"); my $ctime = IkiWiki::rcs_getctime("test2.mdwn"); ok($ctime >= time() - 20); +my $mtime = IkiWiki::rcs_getmtime("test2.mdwn"); +ok($mtime >= time() - 20); + writefile('test3.mdwn', $config{srcdir}, $test1); IkiWiki::rcs_add("test3.mdwn"); IkiWiki::rcs_rename("test3.mdwn", "test4.mdwn"); -- cgit v1.2.3 From a97964688b73d0a3237c798dce3fb064ff29ff11 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sat, 17 Apr 2010 19:05:40 -0400 Subject: unfinished file_prune revamp Many calls to file_prune were incorrectly calling it with 2 parameters. In cases where the filename being checked is relative to the srcdir, that is not needed. Made absolute filenames be pruned. (This won't work for the 2 parameter call style.) --- IkiWiki.pm | 2 +- IkiWiki/Plugin/attachment.pm | 2 +- IkiWiki/Plugin/comments.pm | 4 ++-- IkiWiki/Plugin/editpage.pm | 4 ++-- IkiWiki/Plugin/rename.pm | 5 ++--- IkiWiki/Receive.pm | 2 +- t/file_pruned.t | 40 +++++++++++++++++++++++++++++++++++++--- 7 files changed, 46 insertions(+), 13 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index b37b1f344..a5f83ac7a 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -355,7 +355,7 @@ sub getsetup () { }, wiki_file_prune_regexps => { type => "internal", - default => [qr/(^|\/)\.\.(\/|$)/, qr/^\./, qr/\/\./, + default => [qr/(^|\/)\.\.(\/|$)/, qr/^\//, qr/^\./, qr/\/\./, qr/\.x?html?$/, qr/\.ikiwiki-new$/, qr/(^|\/).svn\//, qr/.arch-ids\//, qr/{arch}\//, qr/(^|\/)_MTN\//, qr/(^|\/)_darcs\//, diff --git a/IkiWiki/Plugin/attachment.pm b/IkiWiki/Plugin/attachment.pm index ad1dd9bca..8c3ff887a 100644 --- a/IkiWiki/Plugin/attachment.pm +++ b/IkiWiki/Plugin/attachment.pm @@ -137,7 +137,7 @@ sub formbuilder (@) { $filename=linkpage(IkiWiki::possibly_foolish_untaint( attachment_location($form->field('page')). IkiWiki::basename($filename))); - if (IkiWiki::file_pruned($filename, $config{srcdir})) { + if (IkiWiki::file_pruned($filename)) { error(gettext("bad attachment filename")); } diff --git a/IkiWiki/Plugin/comments.pm b/IkiWiki/Plugin/comments.pm index 0aa043215..1c219b6c6 100644 --- a/IkiWiki/Plugin/comments.pm +++ b/IkiWiki/Plugin/comments.pm @@ -338,7 +338,7 @@ sub editcomment ($$) { my $page = $form->field('page'); $page = IkiWiki::possibly_foolish_untaint($page); if (! defined $page || ! length $page || - IkiWiki::file_pruned($page, $config{srcdir})) { + IkiWiki::file_pruned($page)) { error(gettext("bad page name")); } @@ -548,7 +548,7 @@ sub commentmoderation ($$) { # pending comment before untainting. my ($f)= $id =~ /$config{wiki_file_regexp}/; if (! defined $f || ! length $f || - IkiWiki::file_pruned($f, $config{srcdir})) { + IkiWiki::file_pruned($f)) { error("illegal file"); } diff --git a/IkiWiki/Plugin/editpage.pm b/IkiWiki/Plugin/editpage.pm index 44fe5514a..ee1de8eaa 100644 --- a/IkiWiki/Plugin/editpage.pm +++ b/IkiWiki/Plugin/editpage.pm @@ -94,7 +94,7 @@ sub cgi_editpage ($$) { $page=possibly_foolish_untaint($page); my $absolute=($page =~ s#^/+##); if (! defined $page || ! length $page || - file_pruned($page, $config{srcdir})) { + file_pruned($page)) { error(gettext("bad page name")); } @@ -220,7 +220,7 @@ sub cgi_editpage ($$) { my $best_loc; if (! defined $from || ! length $from || $from ne $form->field('from') || - file_pruned($from, $config{srcdir}) || + file_pruned($from) || $from=~/^\// || $absolute || $form->submitted) { diff --git a/IkiWiki/Plugin/rename.pm b/IkiWiki/Plugin/rename.pm index 1a9da6363..69e615ead 100644 --- a/IkiWiki/Plugin/rename.pm +++ b/IkiWiki/Plugin/rename.pm @@ -63,9 +63,8 @@ sub check_canrename ($$$$$$) { error(gettext("no change to the file name was specified")); } - # Must be a legal filename, and not absolute. - if (IkiWiki::file_pruned($destfile, $config{srcdir}) || - $destfile=~/^\//) { + # Must be a legal filename. + if (IkiWiki::file_pruned($destfile)) { error(sprintf(gettext("illegal name"))); } diff --git a/IkiWiki/Receive.pm b/IkiWiki/Receive.pm index cd94d0938..ae1bd8bef 100644 --- a/IkiWiki/Receive.pm +++ b/IkiWiki/Receive.pm @@ -82,7 +82,7 @@ sub test () { my ($file)=$change->{file}=~/$config{wiki_file_regexp}/; $file=IkiWiki::possibly_foolish_untaint($file); if (! defined $file || ! length $file || - IkiWiki::file_pruned($file, $config{srcdir})) { + IkiWiki::file_pruned($file)) { error(gettext("bad file name %s"), $file); } diff --git a/t/file_pruned.t b/t/file_pruned.t index f9c1c257e..4335ed917 100755 --- a/t/file_pruned.t +++ b/t/file_pruned.t @@ -1,41 +1,75 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 27; +use Test::More tests => 54; BEGIN { use_ok("IkiWiki"); } %config=IkiWiki::defaultconfig(); ok(IkiWiki::file_pruned("src/.htaccess", "src")); +ok(IkiWiki::file_pruned(".htaccess")); ok(IkiWiki::file_pruned("src/.ikiwiki/", "src")); +ok(IkiWiki::file_pruned(".ikiwiki/")); ok(IkiWiki::file_pruned("src/.ikiwiki/index", "src")); +ok(IkiWiki::file_pruned(".ikiwiki/index")); ok(IkiWiki::file_pruned("src/CVS/foo", "src")); +ok(IkiWiki::file_pruned("CVS/foo")); ok(IkiWiki::file_pruned("src/subdir/CVS/foo", "src")); +ok(IkiWiki::file_pruned("subdir/CVS/foo")); ok(IkiWiki::file_pruned("src/.svn", "src")); +ok(IkiWiki::file_pruned(".svn")); ok(IkiWiki::file_pruned("src/subdir/.svn", "src")); +ok(IkiWiki::file_pruned("subdir/.svn")); ok(IkiWiki::file_pruned("src/subdir/.svn/foo", "src")); +ok(IkiWiki::file_pruned("subdir/.svn/foo")); ok(IkiWiki::file_pruned("src/.git", "src")); +ok(IkiWiki::file_pruned(".git")); ok(IkiWiki::file_pruned("src/subdir/.git", "src")); +ok(IkiWiki::file_pruned("subdir/.git")); ok(IkiWiki::file_pruned("src/subdir/.git/foo", "src")); +ok(IkiWiki::file_pruned("subdir/.git/foo")); ok(! IkiWiki::file_pruned("src/svn/fo", "src")); +ok(! IkiWiki::file_pruned("svn/fo")); ok(! IkiWiki::file_pruned("src/git", "src")); +ok(! IkiWiki::file_pruned("git")); ok(! IkiWiki::file_pruned("src/index.mdwn", "src")); +ok(! IkiWiki::file_pruned("index.mdwn")); ok(! IkiWiki::file_pruned("src/index.", "src")); +ok(! IkiWiki::file_pruned("index.")); # these are ok because while the filename starts with ".", the canonpathed # version does not ok(! IkiWiki::file_pruned("src/.", "src")); ok(! IkiWiki::file_pruned("src/./", "src")); +# OTOH, without a srcdir, no canonpath, so they're not allowed. +ok(IkiWiki::file_pruned(".")); +ok(IkiWiki::file_pruned("./")); + +# Without a srcdir, absolute filenames are not allowed. +ok(IkiWiki::file_pruned("/etc/passwd")); +ok(IkiWiki::file_pruned("//etc/passwd")); +ok(IkiWiki::file_pruned("/")); +ok(IkiWiki::file_pruned("//")); +ok(IkiWiki::file_pruned("///")); + ok(IkiWiki::file_pruned("src/..", "src")); +ok(IkiWiki::file_pruned("..")); ok(IkiWiki::file_pruned("src/../", "src")); +ok(IkiWiki::file_pruned("../")); ok(IkiWiki::file_pruned("src/../", "src")); +ok(IkiWiki::file_pruned("../")); +# This is perhaps counterintuitive. ok(! IkiWiki::file_pruned("src", "src")); + +# Dots, etc, in the srcdir are ok. ok(! IkiWiki::file_pruned("/.foo/src", "/.foo/src")); ok(IkiWiki::file_pruned("/.foo/src/.foo/src", "/.foo/src")); ok(! IkiWiki::file_pruned("/.foo/src/index.mdwn", "/.foo/src/index.mdwn")); -ok(IkiWiki::file_pruned("x/y/foo.dpkg-tmp", "src")); -ok(IkiWiki::file_pruned("x/y/foo.ikiwiki-new", "src")); +ok(IkiWiki::file_pruned("src/y/foo.dpkg-tmp", "src")); +ok(IkiWiki::file_pruned("y/foo.dpkg-tmp")); +ok(IkiWiki::file_pruned("src/y/foo.ikiwiki-new", "src")); +ok(IkiWiki::file_pruned("y/foo.ikiwiki-new")); -- cgit v1.2.3 From cff3937b681a6c2505eb52b43b2e3e7086f99c45 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Tue, 20 Apr 2010 14:08:29 -0400 Subject: remove 2 argument form of file_pruned --- IkiWiki.pm | 9 +-------- t/file_pruned.t | 39 ++------------------------------------- 2 files changed, 3 insertions(+), 45 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index a5f83ac7a..6d3b6c606 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -1843,15 +1843,8 @@ sub deptype (@) { } my $file_prune_regexp; -sub file_pruned ($;$) { +sub file_pruned ($) { my $file=shift; - if (@_) { - require File::Spec; - $file=File::Spec->canonpath($file); - my $base=File::Spec->canonpath(shift); - return if $file eq $base; - $file =~ s#^\Q$base\E/+##; - } if (defined $config{include} && length $config{include}) { return 0 if $file =~ m/$config{include}/; diff --git a/t/file_pruned.t b/t/file_pruned.t index 4335ed917..34f366610 100755 --- a/t/file_pruned.t +++ b/t/file_pruned.t @@ -1,52 +1,31 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 54; +use Test::More tests => 27; BEGIN { use_ok("IkiWiki"); } %config=IkiWiki::defaultconfig(); -ok(IkiWiki::file_pruned("src/.htaccess", "src")); ok(IkiWiki::file_pruned(".htaccess")); -ok(IkiWiki::file_pruned("src/.ikiwiki/", "src")); ok(IkiWiki::file_pruned(".ikiwiki/")); -ok(IkiWiki::file_pruned("src/.ikiwiki/index", "src")); ok(IkiWiki::file_pruned(".ikiwiki/index")); -ok(IkiWiki::file_pruned("src/CVS/foo", "src")); ok(IkiWiki::file_pruned("CVS/foo")); -ok(IkiWiki::file_pruned("src/subdir/CVS/foo", "src")); ok(IkiWiki::file_pruned("subdir/CVS/foo")); -ok(IkiWiki::file_pruned("src/.svn", "src")); ok(IkiWiki::file_pruned(".svn")); -ok(IkiWiki::file_pruned("src/subdir/.svn", "src")); ok(IkiWiki::file_pruned("subdir/.svn")); -ok(IkiWiki::file_pruned("src/subdir/.svn/foo", "src")); ok(IkiWiki::file_pruned("subdir/.svn/foo")); -ok(IkiWiki::file_pruned("src/.git", "src")); ok(IkiWiki::file_pruned(".git")); -ok(IkiWiki::file_pruned("src/subdir/.git", "src")); ok(IkiWiki::file_pruned("subdir/.git")); -ok(IkiWiki::file_pruned("src/subdir/.git/foo", "src")); ok(IkiWiki::file_pruned("subdir/.git/foo")); -ok(! IkiWiki::file_pruned("src/svn/fo", "src")); ok(! IkiWiki::file_pruned("svn/fo")); -ok(! IkiWiki::file_pruned("src/git", "src")); ok(! IkiWiki::file_pruned("git")); -ok(! IkiWiki::file_pruned("src/index.mdwn", "src")); ok(! IkiWiki::file_pruned("index.mdwn")); -ok(! IkiWiki::file_pruned("src/index.", "src")); ok(! IkiWiki::file_pruned("index.")); - -# these are ok because while the filename starts with ".", the canonpathed -# version does not -ok(! IkiWiki::file_pruned("src/.", "src")); -ok(! IkiWiki::file_pruned("src/./", "src")); -# OTOH, without a srcdir, no canonpath, so they're not allowed. ok(IkiWiki::file_pruned(".")); ok(IkiWiki::file_pruned("./")); -# Without a srcdir, absolute filenames are not allowed. +# absolute filenames are not allowed. ok(IkiWiki::file_pruned("/etc/passwd")); ok(IkiWiki::file_pruned("//etc/passwd")); ok(IkiWiki::file_pruned("/")); @@ -54,22 +33,8 @@ ok(IkiWiki::file_pruned("//")); ok(IkiWiki::file_pruned("///")); -ok(IkiWiki::file_pruned("src/..", "src")); ok(IkiWiki::file_pruned("..")); -ok(IkiWiki::file_pruned("src/../", "src")); -ok(IkiWiki::file_pruned("../")); -ok(IkiWiki::file_pruned("src/../", "src")); ok(IkiWiki::file_pruned("../")); -# This is perhaps counterintuitive. -ok(! IkiWiki::file_pruned("src", "src")); - -# Dots, etc, in the srcdir are ok. -ok(! IkiWiki::file_pruned("/.foo/src", "/.foo/src")); -ok(IkiWiki::file_pruned("/.foo/src/.foo/src", "/.foo/src")); -ok(! IkiWiki::file_pruned("/.foo/src/index.mdwn", "/.foo/src/index.mdwn")); - -ok(IkiWiki::file_pruned("src/y/foo.dpkg-tmp", "src")); ok(IkiWiki::file_pruned("y/foo.dpkg-tmp")); -ok(IkiWiki::file_pruned("src/y/foo.ikiwiki-new", "src")); ok(IkiWiki::file_pruned("y/foo.ikiwiki-new")); -- cgit v1.2.3 From 13325317a32529e02769baa5e61e6c401c675b27 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 21 Apr 2010 22:04:03 -0400 Subject: backlink influence data seems ok --- doc/bugs/depends_simple_mixup.mdwn | 2 +- t/pagespec_match_list.t | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 't') diff --git a/doc/bugs/depends_simple_mixup.mdwn b/doc/bugs/depends_simple_mixup.mdwn index 472de6349..e7b48f802 100644 --- a/doc/bugs/depends_simple_mixup.mdwn +++ b/doc/bugs/depends_simple_mixup.mdwn @@ -61,4 +61,4 @@ influences should return only the successful influences. In light of this, commit f2b3d1341447cbf29189ab490daae418fbe5d02d seems thuroughly wrong. So, what about influence info for other matches like `!author(foo)` etc? Currently, none is returned, but it should -be a content influence. What about backlink influence data? +be a content influence. (Backlink influence data is ok.) diff --git a/t/pagespec_match_list.t b/t/pagespec_match_list.t index 05dc012fe..ee5d60f88 100755 --- a/t/pagespec_match_list.t +++ b/t/pagespec_match_list.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 94; +use Test::More tests => 107; BEGIN { use_ok("IkiWiki"); } @@ -80,7 +80,7 @@ foreach my $spec ("* and link(bar)", "* or link(bar)") { } # a pagespec with backlinks() will add as an influence the page with the links -foreach my $spec ("bar or (backlink(foo) and !*.png)", "backlink(foo)") { +foreach my $spec ("bar or (backlink(foo) and !*.png)", "backlink(foo)", "!backlink(foo)") { pagespec_match_list("foo2", $spec, deptype => deptype("presence")); ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_PRESENCE); ok(! ($IkiWiki::depends{foo2}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); -- cgit v1.2.3 From c98414e192285b2607ee9fcb27f0e8e00db5fb26 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 21 Apr 2010 23:08:54 -0400 Subject: added a test case for this bug Further analysis suggests fixing this might not be as dreadful as I first thought! --- doc/bugs/depends_simple_mixup.mdwn | 24 ++++++++++++++++++++++-- t/pagespec_match_list.t | 20 +++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) (limited to 't') diff --git a/doc/bugs/depends_simple_mixup.mdwn b/doc/bugs/depends_simple_mixup.mdwn index e7b48f802..79bfa8bdc 100644 --- a/doc/bugs/depends_simple_mixup.mdwn +++ b/doc/bugs/depends_simple_mixup.mdwn @@ -44,9 +44,11 @@ is modified to add the link, the regular dependency calculation code didn't notice, since the pagespec no longer matched. In this case, `depends_simple` needs to contain all pages -that do *not* match `link_done)`, but before my change, it contained +that do *not* match `link(done)`, but before my change, it contained all pages that *do* match. After my change, it contained all pages. +---- + So, seems what is needed is a way for influence info to be manipulated by the boolean operations that are applied. One way would be to have two sets of influences be returned, one for successful matches, and one for @@ -58,7 +60,25 @@ Then, when NOTting a `*Reason`, swap the two sets of influences. When ANDing/ORing, combine the individual sets. Querying the object for influences should return only the successful influences. -In light of this, commit f2b3d1341447cbf29189ab490daae418fbe5d02d seems +---- + +Would it be possible to avoid the complication of maintianing two sets of +influence info? + +Well, notice that the influence of `pagespec_match($page, "link(done)")` +is $page. Iff the match succeeds. + +Also, the influence of `pagespec_match($page, "!link(done)")` is +$page. Iff the (overall) match succeeds. + +Does that hold for all cases? If so, the code that populates +`depends_simple` could just test if the pagespec was successful, and +if not, avoid adding $page influences, while still adding any other, +non-$page influences. + +---- + +Hmm, commit f2b3d1341447cbf29189ab490daae418fbe5d02d seems thuroughly wrong. So, what about influence info for other matches like `!author(foo)` etc? Currently, none is returned, but it should be a content influence. (Backlink influence data is ok.) diff --git a/t/pagespec_match_list.t b/t/pagespec_match_list.t index ee5d60f88..27546e6ca 100755 --- a/t/pagespec_match_list.t +++ b/t/pagespec_match_list.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 107; +use Test::More tests => 115; BEGIN { use_ok("IkiWiki"); } @@ -27,6 +27,8 @@ IkiWiki::checkconfig(); $IkiWiki::pagectime{foo} = 2; $IkiWiki::pagectime{foo2} = 2; $IkiWiki::pagectime{foo3} = 1; +$IkiWiki::pagectime{foo4} = 1; +$IkiWiki::pagectime{foo5} = 1; $IkiWiki::pagectime{bar} = 3; $IkiWiki::pagectime{"post/1"} = 6; $IkiWiki::pagectime{"post/2"} = 6; @@ -69,12 +71,28 @@ foreach my $spec ("* and link(bar)", "* or link(bar)") { ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_PRESENCE); ok(! ($IkiWiki::depends{foo2}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); ok($IkiWiki::depends_simple{foo2}{foo2} == $IkiWiki::DEPEND_LINKS); + ok($IkiWiki::depends_simple{foo2}{foo} != $IkiWiki::DEPEND_LINKS); %IkiWiki::depends_simple=(); %IkiWiki::depends=(); pagespec_match_list("foo3", $spec, deptype => deptype("links")); ok($IkiWiki::depends{foo3}{$spec} & $IkiWiki::DEPEND_LINKS); ok(! ($IkiWiki::depends{foo3}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_PRESENCE))); ok($IkiWiki::depends_simple{foo3}{foo3} == $IkiWiki::DEPEND_LINKS); + ok($IkiWiki::depends_simple{foo3}{foo} != $IkiWiki::DEPEND_LINKS); + %IkiWiki::depends_simple=(); + %IkiWiki::depends=(); +} +# Above we tested that a link pagespec is influenced +# by the pages that currently contain the link. + +# Oppositely, a pagespec that tests for pages that do not have a link +# is not influenced by pages that currently contain the link, but +# is instead influenced by pages that currently do not (but that +# could be changed to have it). +foreach my $spec ("* and !link(bar)", "* and !(!(!link(bar)))") { + pagespec_match_list("foo2", $spec); + ok($IkiWiki::depends_simple{foo2}{foo2} != $IkiWiki::DEPEND_LINKS); + ok($IkiWiki::depends_simple{foo2}{foo} == $IkiWiki::DEPEND_LINKS); %IkiWiki::depends_simple=(); %IkiWiki::depends=(); } -- cgit v1.2.3 From ef34ea7c05189db8952b08d78aad718ccb64475a Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Thu, 22 Apr 2010 00:07:25 -0400 Subject: fix test cases for dynamic influence calculation --- t/add_depends.t | 10 +++++++++- t/pagespec_match_list.t | 25 ++++++++++++++++++------- 2 files changed, 27 insertions(+), 8 deletions(-) (limited to 't') diff --git a/t/add_depends.t b/t/add_depends.t index 9b074818c..aa58fb0ff 100755 --- a/t/add_depends.t +++ b/t/add_depends.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 38; +use Test::More tests => 40; BEGIN { use_ok("IkiWiki"); } %config=IkiWiki::defaultconfig(); @@ -60,3 +60,11 @@ ok(! ($IkiWiki::depends{foo0}{"*"} & $IkiWiki::DEPEND_PRESENCE)); ok(add_depends("foo9", "*", deptype("monkey"))); ok($IkiWiki::depends{foo9}{"*"} & $IkiWiki::DEPEND_CONTENT); ok(! ($IkiWiki::depends{foo9}{"*"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS))); + +# Influences are added for dependencies involving links. +$pagesources{"foo"}="foo.mdwn"; +$links{foo}=[qw{bar}]; +$pagesources{"bar"}="bar.mdwn"; +$links{bar}=[qw{}]; +ok(add_depends("foo", "link(bar) and backlink(meep)")); +ok($IkiWiki::depends_simple{foo}{foo} == $IkiWiki::DEPEND_LINKS); diff --git a/t/pagespec_match_list.t b/t/pagespec_match_list.t index 27546e6ca..244ad9159 100755 --- a/t/pagespec_match_list.t +++ b/t/pagespec_match_list.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 115; +use Test::More tests => 126; BEGIN { use_ok("IkiWiki"); } @@ -71,19 +71,27 @@ foreach my $spec ("* and link(bar)", "* or link(bar)") { ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_PRESENCE); ok(! ($IkiWiki::depends{foo2}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); ok($IkiWiki::depends_simple{foo2}{foo2} == $IkiWiki::DEPEND_LINKS); - ok($IkiWiki::depends_simple{foo2}{foo} != $IkiWiki::DEPEND_LINKS); %IkiWiki::depends_simple=(); %IkiWiki::depends=(); pagespec_match_list("foo3", $spec, deptype => deptype("links")); ok($IkiWiki::depends{foo3}{$spec} & $IkiWiki::DEPEND_LINKS); ok(! ($IkiWiki::depends{foo3}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_PRESENCE))); ok($IkiWiki::depends_simple{foo3}{foo3} == $IkiWiki::DEPEND_LINKS); - ok($IkiWiki::depends_simple{foo3}{foo} != $IkiWiki::DEPEND_LINKS); %IkiWiki::depends_simple=(); %IkiWiki::depends=(); } -# Above we tested that a link pagespec is influenced -# by the pages that currently contain the link. + +# A link pagespec is influenced by the pages that currently contain the link. +# It is not influced by pages that do not currently contain the link, +# because if those pages were changed to contain it, regular dependency +# handling would be triggered. +foreach my $spec ("* and link(bar)", "link(bar)", "no_such_page or link(bar)") { + pagespec_match_list("foo2", $spec); + ok($IkiWiki::depends_simple{foo2}{foo2} == $IkiWiki::DEPEND_LINKS); + ok(! exists $IkiWiki::depends_simple{foo2}{foo}, $spec); + %IkiWiki::depends_simple=(); + %IkiWiki::depends=(); +} # Oppositely, a pagespec that tests for pages that do not have a link # is not influenced by pages that currently contain the link, but @@ -91,8 +99,8 @@ foreach my $spec ("* and link(bar)", "* or link(bar)") { # could be changed to have it). foreach my $spec ("* and !link(bar)", "* and !(!(!link(bar)))") { pagespec_match_list("foo2", $spec); - ok($IkiWiki::depends_simple{foo2}{foo2} != $IkiWiki::DEPEND_LINKS); - ok($IkiWiki::depends_simple{foo2}{foo} == $IkiWiki::DEPEND_LINKS); + ok(! exists $IkiWiki::depends_simple{foo2}{foo2}); + ok($IkiWiki::depends_simple{foo2}{foo} == $IkiWiki::DEPEND_LINKS, $spec); %IkiWiki::depends_simple=(); %IkiWiki::depends=(); } @@ -103,12 +111,14 @@ foreach my $spec ("bar or (backlink(foo) and !*.png)", "backlink(foo)", "!backli ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_PRESENCE); ok(! ($IkiWiki::depends{foo2}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS))); ok($IkiWiki::depends_simple{foo2}{foo} == $IkiWiki::DEPEND_LINKS); + ok(! exists $IkiWiki::depends_simple{foo2}{foo2}); %IkiWiki::depends_simple=(); %IkiWiki::depends=(); pagespec_match_list("foo2", $spec, deptype => deptype("links")); ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_LINKS); ok(! ($IkiWiki::depends{foo2}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_CONTENT))); ok($IkiWiki::depends_simple{foo2}{foo} == $IkiWiki::DEPEND_LINKS); + ok(! exists $IkiWiki::depends_simple{foo2}{foo2}); %IkiWiki::depends_simple=(); %IkiWiki::depends=(); pagespec_match_list("foo2", $spec, deptype => deptype("presence", "links")); @@ -116,6 +126,7 @@ foreach my $spec ("bar or (backlink(foo) and !*.png)", "backlink(foo)", "!backli ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_LINKS); ok(! ($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_CONTENT)); ok($IkiWiki::depends_simple{foo2}{foo} == $IkiWiki::DEPEND_LINKS); + ok(! exists $IkiWiki::depends_simple{foo2}{foo2}); %IkiWiki::depends_simple=(); %IkiWiki::depends=(); pagespec_match_list("foo2", $spec); -- cgit v1.2.3 From 9b112cc986023e3a9ec5a4b5e9824af05e80c4d8 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Fri, 23 Apr 2010 17:23:31 -0400 Subject: add test case to ensure all templates are documented --- doc/templates.mdwn | 42 +++++++++++++++++++++--------------------- t/templates_documented.t | 14 ++++++++++++++ 2 files changed, 35 insertions(+), 21 deletions(-) create mode 100755 t/templates_documented.t (limited to 't') diff --git a/doc/templates.mdwn b/doc/templates.mdwn index f7b3adae5..7b56600d1 100644 --- a/doc/templates.mdwn +++ b/doc/templates.mdwn @@ -30,34 +30,34 @@ html out of ikiwiki and in the templates. * `page.tmpl` - Used for displaying all regular wiki pages. * `misc.tmpl` - Generic template used for any page that doesn't have a custom template. -* `editpage.tmpl` - Create/edit page. -* `change.tmpl` - Used to create a page describing a change made to the wiki. -* `passwordmail.tmpl` - Not a html template, this is used to - generate a mail with an url the user can use to reset their password. -* `rsspage.tmpl` - Used for generating rss feeds for [[blogs|blog]]. +* `rsspage.tmpl` - Used for generating rss feeds for blogs. * `rssitem.tmpl` - Used for generating individual items on rss feeds. * `atompage.tmpl` - Used for generating atom feeds for blogs. * `atomitem.tmpl` - Used for generating individual items on atom feeds. -* `inlinepage.tmpl` - Used for adding a page inline in a blog - page. +* `inlinepage.tmpl` - Used for displaying a post in a blog. * `archivepage.tmpl` - Used for listing a page in a blog archive page. +* `titlepage.tmpl` - Used for listing a page by title in a blog archive page. * `microblog.tmpl` - Used for showing a microblogging post inline. * `blogpost.tmpl` - Used for a form to add a post to a blog (and a rss/atom links) -* `feedlink.tmpl` - Used to add rss/atom links if blogpost.tmpl is not used. -* `aggregatepost.tmpl` - Used by the [[plugins/aggregate]] plugin to create +* `feedlink.tmpl` - Used to add rss/atom links if `blogpost.tmpl` is not used. +* `aggregatepost.tmpl` - Used by the aggregate plugin to create a page for a post. -* `searchform.tmpl` - Used by the [[plugins/search]] plugin to add a search - form to wiki pages. -* `searchquery.tmpl` - This is an omega template, used by the - [[plugins/search]] plugin. -* `comment.tmpl` - This template is used to display a comment - by the [[plugins/comments]] plugin. -* `editcomment.tmpl` - This template is the comment post form for the - [[plugins/comments]] plugin. -* `commentmoderation.tmpl` - This template is used to produce the comment - moderation form. -* `recentchanges.tmpl` - This template is used for listing a change - on the RecentChanges page. +* `searchform.tmpl`, `googleform.tmpl` - Used by the search plugin + and google plugin to add search forms to wiki pages. +* `searchquery.tmpl` - This is a Omega template, used by the + search plugin. +* `comment.tmpl` - Used by the comments plugin to display a comment. +* `change.tmpl` - Used to create a page describing a change made to the wiki. +* `recentchanges.tmpl` - Used for listing a change on the RecentChanges page. +* `autoindex.tmpl` - Filled in by the autoindex plugin to make index pages. +* `autotag.tmpl` - Filled in by the tag plugin to make tag pages. +* `calendarmonth.tmpl`, `calendaryear.tmpl` - Used by ikiwiki-calendar to + make calendar archive pages. +* `editpage.tmpl`, `editconflict.tmpl`, `editcreationconflict.tmpl`, + `editfailedsave.tmpl`, `editpagegone.tmpl`, `pocreatepage.tmpl`, + `editcomment.tmpl` `commentmoderation.tmpl`, `renamesummary.tmpl`, + `passwordmail.tmpl` - Parts of ikiwiki's user interface; do not + normally need to be customised. [[!if test="enabled(pagetemplate)" then=""" ## The pagetemplate directive diff --git a/t/templates_documented.t b/t/templates_documented.t new file mode 100755 index 000000000..826c51d36 --- /dev/null +++ b/t/templates_documented.t @@ -0,0 +1,14 @@ +#!/usr/bin/perl +use warnings; +use strict; +use Test::More 'no_plan'; + +$/=undef; +open(IN, "doc/templates.mdwn") || die "doc/templates.mdwn: $!"; +my $page=; +close IN; + +foreach my $file (glob("templates/*.tmpl")) { + $file=~s/templates\///; + ok($page =~ /\Q$file\E/, "$file documented on doc/templates.mdwn"); +} -- cgit v1.2.3 From 3ac2ae1f14952bd92038183d92b1eb618c9d0f55 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Mon, 26 Apr 2010 18:47:17 -0400 Subject: Add page() PageSpec, which is like glob() but matches only pages, not other files. --- IkiWiki.pm | 10 +++++++++- debian/changelog | 2 ++ doc/ikiwiki/pagespec.mdwn | 11 ++++++----- t/pagespec_match.t | 16 +++++++++++++++- 4 files changed, 32 insertions(+), 7 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 944001d9b..623396c9c 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -2299,7 +2299,11 @@ sub match_glob ($$;@) { my $regexp=IkiWiki::glob2re($glob); if ($page=~/^$regexp$/i) { - if (! IkiWiki::isinternal($page) || $params{internal}) { + if ($params{onlypage} && + ! defined IkiWiki::pagetype($IkiWiki::pagesources{$page})) { + return IkiWiki::FailReason->new("$page is not a page"); + } + elsif (! IkiWiki::isinternal($page) || $params{internal}) { return IkiWiki::SuccessReason->new("$glob matches $page"); } else { @@ -2315,6 +2319,10 @@ sub match_internal ($$;@) { return match_glob($_[0], $_[1], @_, internal => 1) } +sub match_page ($$;@) { + return match_glob($_[0], $_[1], @_, onlypage => 1) +} + sub match_link ($$;@) { my $page=shift; my $link=lc(shift); diff --git a/debian/changelog b/debian/changelog index 1229b1198..610d0c9cb 100644 --- a/debian/changelog +++ b/debian/changelog @@ -75,6 +75,8 @@ ikiwiki (3.20100424) UNRELEASED; urgency=low the top of the web root. This is another things that requires a wiki rebuild on upgrade to this version. * Fix removal of rendered files in rebuild mode. + * Add page() PageSpec, which is like glob() but matches only pages, + not other files. -- Joey Hess Sun, 04 Apr 2010 12:17:11 -0400 diff --git a/doc/ikiwiki/pagespec.mdwn b/doc/ikiwiki/pagespec.mdwn index 7810e790b..1c99aefac 100644 --- a/doc/ikiwiki/pagespec.mdwn +++ b/doc/ikiwiki/pagespec.mdwn @@ -24,19 +24,20 @@ match all pages except for Discussion pages and the SandBox: Some more elaborate limits can be added to what matches using these functions: +* "`glob(someglob)`" - matches pages and other files that match the given glob. + Just writing the glob by itself is actually a shorthand for this function. +* "`page(glob)`" - like `glob()`, but only matches pages, not other files * "`link(page)`" - matches only pages that link to a given page (or glob) * "`tagged(tag)`" - matches pages that are tagged or link to the given tag (or tags matched by a glob) * "`backlink(page)`" - matches only pages that a given page links to -* "`creation_month(month)`" - matches only pages created on the given month +* "`creation_month(month)`" - matches only files created on the given month * "`creation_day(mday)`" - or day of the month * "`creation_year(year)`" - or year -* "`created_after(page)`" - matches only pages created after the given page +* "`created_after(page)`" - matches only files created after the given page was created -* "`created_before(page)`" - matches only pages created before the given page +* "`created_before(page)`" - matches only files created before the given page was created -* "`glob(someglob)`" - matches pages that match the given glob. Just writing - the glob by itself is actually a shorthand for this function. * "`internal(glob)`" - like `glob()`, but matches even internal-use pages that globs do not usually match. * "`title(glob)`", "`author(glob)`", "`authorurl(glob)`", diff --git a/t/pagespec_match.t b/t/pagespec_match.t index ade9bca5a..97bcc969c 100755 --- a/t/pagespec_match.t +++ b/t/pagespec_match.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 75; +use Test::More tests => 85; BEGIN { use_ok("IkiWiki"); } @@ -66,7 +66,21 @@ $links{"ook"}=[qw{/blog/tags/foo}]; foreach my $p (keys %links) { $pagesources{$p}="$p.mdwn"; } +$pagesources{"foo.png"}="foo.png"; +$pagesources{"foo"}="foo.mdwn"; +$IkiWiki::hooks{htmlize}{mdwn}={}; +ok(pagespec_match("foo", "foo"), "simple"); +ok(! pagespec_match("foo", "bar"), "simple fail"); +ok(pagespec_match("foo", "foo"), "simple glob"); +ok(pagespec_match("foo", "f*"), "simple glob fail"); +ok(pagespec_match("foo", "page(foo)"), "page()"); +print pagespec_match("foo", "page(foo)")."\n"; +ok(! pagespec_match("foo", "page(bar)"), "page() fail"); +ok(! pagespec_match("foo.png", "page(foo.png)"), "page() fails on non-page"); +ok(! pagespec_match("foo.png", "page(foo*)"), "page() fails on non-page glob"); +ok(pagespec_match("foo", "page(foo)"), "page() glob"); +ok(pagespec_match("foo", "page(f*)"), "page() glob fail"); ok(pagespec_match("foo", "link(bar)"), "link"); ok(pagespec_match("foo", "link(ba?)"), "glob link"); ok(! pagespec_match("foo", "link(quux)"), "failed link"); -- cgit v1.2.3 From a6d38b7f26561e26b8c3d813f19b8eff736684d2 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Mon, 26 Apr 2010 19:01:31 -0400 Subject: update test suite for recent index load on rebuild changes --- t/index.t | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 't') diff --git a/t/index.t b/t/index.t index 44273059d..392a167e9 100755 --- a/t/index.t +++ b/t/index.t @@ -117,11 +117,14 @@ is_deeply(\%oldtypedlinks, { %pagesources=%pagemtime=%oldlinks=%links=%depends=%typedlinks=%oldtypedlinks= %destsources=%renderedfiles=%pagecase=%pagestate=(); -# When state is loaded for a wiki rebuild, only ctime and oldrenderedfiles -# are retained. +# When state is loaded for a wiki rebuild, only ctime, oldrenderedfiles, +# and pagesources are retained. $config{rebuild}=1; ok(loadindex(), "load index"); is_deeply(\%pagesources, { + Foo => "Foo.mdwn", + bar => "bar.mdwn", + "bar.png" => "bar.png", }, "%pagesources loaded correctly"); is_deeply(\%pagemtime, { }, "%pagemtime loaded correctly"); @@ -143,7 +146,10 @@ is_deeply(\%depends, { }, "%depends loaded correctly"); is_deeply(\%pagestate, { }, "%pagestate loaded correctly"); -is_deeply(\%pagecase, { +is_deeply(\%pagecase, { # generated implicitly since pagesources is loaded + foo => "Foo", + bar => "bar", + "bar.png" => "bar.png" }, "%pagecase generated correctly"); is_deeply(\%destsources, { }, "%destsources generated correctly"); -- cgit v1.2.3 From 34afaf1015d58f1313a392e3f5f514d484dc7efa Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Tue, 4 May 2010 23:20:47 -0400 Subject: add test suite for find_src_files --- t/find_src_files.t | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100755 t/find_src_files.t (limited to 't') diff --git a/t/find_src_files.t b/t/find_src_files.t new file mode 100755 index 000000000..4a995c2d3 --- /dev/null +++ b/t/find_src_files.t @@ -0,0 +1,97 @@ +#!/usr/bin/perl +use warnings; +use strict; +use Test::More tests => 19; + +BEGIN { use_ok("IkiWiki"); } +BEGIN { use_ok("IkiWiki::Render"); } + +%config=IkiWiki::defaultconfig(); +$config{srcdir}="t/tmp/srcdir"; +$config{underlaydir}="t/tmp/underlaydir"; +IkiWiki::checkconfig(); + +sub cleanup { + ok(! system("rm -rf t/tmp")); +} + +sub setup_underlay { + foreach my $file (@_) { + writefile($file, $config{underlaydir}, "test content"); + } + return @_; +} + +sub setup_srcdir { + foreach my $file (@_) { + writefile($file, $config{srcdir}, "test content"); + } + return @_; +} + +sub test_src_files { + my %expected=map { $_ => 1 } @{shift()}; # the input list may have dups + my $desc=shift; + + close STDERR; # find_src_files prints warnings about bad files + + my ($files, $pages)=IkiWiki::find_src_files(); + is_deeply([sort @$files], [sort keys %expected], $desc); +} + +cleanup(); + +my @list=setup_underlay(qw{index.mdwn sandbox.mdwn smiley.png ikiwiki.mdwn ikiwiki/directive.mdwn ikiwiki/directive/foo.mdwn}); +push @list, setup_srcdir(qw{index.mdwn foo.mwdn icon.jpeg blog/archive/1/2/3/foo.mdwn blog/archive/1/2/4/bar.mdwn blog/archive.mdwn}); +test_src_files(\@list, "simple test"); + +setup_srcdir(".badfile"); +test_src_files(\@list, "srcdir dotfile is skipped"); + +setup_underlay(".badfile"); +test_src_files(\@list, "underlay dotfile is skipped"); + +setup_srcdir(".ikiwiki/index"); +test_src_files(\@list, "srcdir dotdir is skipped"); + +setup_underlay(".ikiwiki/index"); +test_src_files(\@list, "underlay dotdir is skipped"); + +setup_srcdir("foo>.mdwn"); +test_src_files(\@list, "illegal srcdir filename skipped"); + +setup_underlay("foo>.mdwn"); +test_src_files(\@list, "illegal underlay filename skipped"); + +system("mkdir -p $config{srcdir}/empty"); +test_src_files(\@list, "empty srcdir directory ignored"); + +system("mkdir -p $config{underlaydir}/empty"); +test_src_files(\@list, "empty underlay directory ignored"); + +setup_underlay("bad.mdwn"); +system("ln -sf /etc/passwd $config{srcdir}/bad.mdwn"); +test_src_files(\@list, "underlaydir override attack foiled"); + +system("ln -sf /etc/passwd $config{srcdir}/symlink.mdwn"); +test_src_files(\@list, "file symlink in srcdir skipped"); + +system("ln -sf /etc/passwd $config{underlaydir}/symlink.mdwn"); +test_src_files(\@list, "file symlink in underlaydir skipped"); + +system("ln -sf /etc/ $config{srcdir}/symdir"); +test_src_files(\@list, "dir symlink in srcdir skipped"); + +system("ln -sf /etc/ $config{underlaydir}/symdir"); +test_src_files(\@list, "dir symlink in underlaydir skipped"); + +system("ln -sf /etc/ $config{srcdir}/blog/symdir"); +test_src_files(\@list, "deep dir symlink in srcdir skipped"); + +system("ln -sf /etc/ $config{underlaydir}/ikiwiki/symdir"); +test_src_files(\@list, "deep dir symlink in underlaydir skipped"); + + + + +cleanup(); -- cgit v1.2.3 From 9257381e8d6e0ec18eac699e22aca63873859e32 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 5 May 2010 18:32:48 -0400 Subject: fix test count --- t/find_src_files.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 't') diff --git a/t/find_src_files.t b/t/find_src_files.t index 4a995c2d3..a3742db75 100755 --- a/t/find_src_files.t +++ b/t/find_src_files.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 19; +use Test::More tests => 20; BEGIN { use_ok("IkiWiki"); } BEGIN { use_ok("IkiWiki::Render"); } -- cgit v1.2.3 From 57405061f4176bfa3fdfa686101a726a439d67f9 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 5 May 2010 22:12:31 -0400 Subject: add test --- t/template_syntax.t | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100755 t/template_syntax.t (limited to 't') diff --git a/t/template_syntax.t b/t/template_syntax.t new file mode 100755 index 000000000..1e156eed8 --- /dev/null +++ b/t/template_syntax.t @@ -0,0 +1,15 @@ +#!/usr/bin/perl +use warnings; +use strict; +use Test::More; + +my @templates=glob("templates/*.tmpl"), glob("doc/templates/*.mdwn"); +plan(tests => 2*@templates); + +use HTML::Template; + +foreach my $template (@templates) { + my $obj=eval {HTML::Template->new(filename => $template)}; + ok(! $@, $template." $@"); + ok($obj, $template); +} -- cgit v1.2.3 From 81948abda76e2aba370533397e9e786ee9f71905 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 5 May 2010 22:47:37 -0400 Subject: remove bogus test for empty page --- t/parentlinks.t | 1 - 1 file changed, 1 deletion(-) (limited to 't') diff --git a/t/parentlinks.t b/t/parentlinks.t index 593937a97..9b4654903 100755 --- a/t/parentlinks.t +++ b/t/parentlinks.t @@ -21,7 +21,6 @@ IkiWiki::checkconfig(); # Test data $expected{'parentlinks'} = { - "" => [], "ikiwiki" => [], "ikiwiki/pagespec" => [ {depth => 0, height => 2, }, -- cgit v1.2.3 From f8c2a67b3cdbce81bbb661e05411a129b07414ce Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Fri, 7 May 2010 20:14:25 -0400 Subject: pretty openid login * openid: Incorporated a fancy openid-selector signin form. (http://code.google.com/p/openid-selector/) * openid: Use "openid_identifier" as the form field, as required by OpenID Authentication v2.0 spec. --- IkiWiki/Plugin/openid.pm | 68 ++---- debian/changelog | 6 +- debian/copyright | 6 +- doc/style.css | 57 +++-- t/openiduser.t | 2 +- t/permalink.t | 2 +- templates/openid-selector.tmpl | 37 ++-- .../javascript/ikiwiki/openid-selector/jquery.js | 32 --- .../ikiwiki/openid-selector/openid-jquery.js | 240 --------------------- underlays/openid-selector/ikiwiki/openid/jquery.js | 32 +++ .../ikiwiki/openid/openid-jquery.js | 240 +++++++++++++++++++++ 11 files changed, 363 insertions(+), 359 deletions(-) delete mode 100644 underlays/javascript/ikiwiki/openid-selector/jquery.js delete mode 100644 underlays/javascript/ikiwiki/openid-selector/openid-jquery.js create mode 100644 underlays/openid-selector/ikiwiki/openid/jquery.js create mode 100644 underlays/openid-selector/ikiwiki/openid/openid-jquery.js (limited to 't') diff --git a/IkiWiki/Plugin/openid.pm b/IkiWiki/Plugin/openid.pm index 0ad8697d8..e722c68ba 100644 --- a/IkiWiki/Plugin/openid.pm +++ b/IkiWiki/Plugin/openid.pm @@ -7,6 +7,7 @@ use strict; use IkiWiki 3.00; sub import { + add_underlay("openid-selector"); hook(type => "checkconfig", id => "openid", call => \&checkconfig); hook(type => "getopt", id => "openid", call => \&getopt); hook(type => "getsetup", id => "openid", call => \&getsetup); @@ -19,10 +20,16 @@ sub checkconfig () { if ($config{cgi}) { # Intercept normal signin form, so the openid selector # can be displayed. - require IkiWiki::CGI; - my $real_cgi_signin=\&IkiWiki::cgi_signin; + # + # When other auth hooks are registered, give the selector + # a reference to the normal signin form. + my $real_cgi_signin; + if (keys %{$IkiWiki::hooks{auth}} > 1) { + require IkiWiki::CGI; + $real_cgi_signin=\&IkiWiki::cgi_signin; + } inject(name => "IkiWiki::cgi_signin", call => sub ($$) { - openid_selector($real_cgi_signin, @_); + openid_selector($real_cgi_signin, @_); }); } } @@ -55,15 +62,21 @@ sub openid_selector { my $q=shift; my $session=shift; - my $openid_url=$q->param('openid_url'); + my $openid_url=$q->param('openid_identifier'); my $openid_error; - if (defined $q->param("action") && $q->param("action") eq "verify") { + if (! load_openid_module()) { + if ($real_cgi_signin) { + $real_cgi_signin->($q, $session); + } + error(sprintf(gettext("failed to load openid module: "), @_)); + } + elsif (defined $q->param("action") && $q->param("action") eq "verify") { validate($q, $session, $openid_url, sub { $openid_error=shift; }); } - elsif ($q->param("do") eq "signin" || ! load_openid_module()) { + elsif ($q->param("do") eq "signin" && $real_cgi_signin) { $real_cgi_signin->($q, $session); exit; } @@ -73,8 +86,7 @@ sub openid_selector { cgiurl => $config{cgiurl}, (defined $openid_error ? (openid_error => $openid_error) : ()), (defined $openid_url ? (openid_url => $openid_url) : ()), - # TODO only if other auth methods are available - nonopenidurl => IkiWiki::cgiurl(do => "signin"), + ($real_cgi_signin ? (nonopenidurl => IkiWiki::cgiurl(do => "signin")) : ()), loginlabel => loginlabel(), ); @@ -90,45 +102,9 @@ sub formbuilder_setup (@) { my $session=$params{session}; my $cgi=$params{cgi}; - if ($form->title eq "signin") { - return unless load_openid_module(); - - # This avoids it displaying a redundant label for the - # OpenID fieldset. - $form->fieldsets("OpenID"); - - $form->field( - name => "openid_url", - label => loginlabel(), - fieldset => "OpenID", - size => 30, - comment => ($config{openidsignup} ? " | ".gettext("Get an OpenID")."" : "") - ); - - # Handle submission of an OpenID as validation. - if ($form->submitted && $form->submitted eq "Login" && - defined $form->field("openid_url") && - length $form->field("openid_url")) { - $form->field( - name => "openid_url", - validate => sub { - validate($cgi, $session, shift, sub { - # Display error in the form. - $form->field(name => "openid_url", comment => shift); - }); - }, - ); - # Skip all other required fields in this case. - foreach my $field ($form->field) { - next if $field eq "openid_url"; - $form->field(name => $field, required => 0, - validate => '/.*/'); - } - } - } - elsif ($form->title eq "preferences" && + if ($form->title eq "preferences" && IkiWiki::openiduser($session->param("name"))) { - $form->field(name => "openid_url", disabled => 1, + $form->field(name => "openid_identifier", disabled => 1, label => htmllink("", "", "ikiwiki/OpenID", noimageinline => 1), value => $session->param("name"), size => 50, force => 1, diff --git a/debian/changelog b/debian/changelog index c335030ed..3f20aec1a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -10,8 +10,10 @@ ikiwiki (3.20100505) UNRELEASED; urgency=low * inline: Call indexhtml when inlining internal pages, so their text can be indexed for searching. * Delete hooks are passed deleted internal pages. - * openid: Incorporated a fancy openid-selector signin form - (http://code.google.com/p/openid-selector/). + * openid: Incorporated a fancy openid-selector signin form. + (http://code.google.com/p/openid-selector/) + * openid: Use "openid_identifier" as the form field, as required + by OpenID Authentication v2.0 spec. -- Joey Hess Wed, 05 May 2010 18:07:29 -0400 diff --git a/debian/copyright b/debian/copyright index b6dd3782b..7ef266151 100644 --- a/debian/copyright +++ b/debian/copyright @@ -193,11 +193,15 @@ Files: cvs.pm Copyright: © 2009 Amitai Schlair License: BSD-C2 -Files: underlays/javascript/ikiwiki/openid-selector/* +Files: underlays/openid-selector/ikiwiki/openid/* Copyright: © 2008-2010 andyjm, david.j.boden License: BSD-C2 From http://code.google.com/p/openid-selector/ +Files: underlays/openid-selector/ikiwiki/openid/jquery.js +Copyright: © 2005-2008 by John Resig, Branden Aaron & Jörn Zaefferer +License: GPL-2 + License: BSD-C2 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/doc/style.css b/doc/style.css index 5f77037e4..75aa28407 100644 --- a/doc/style.css +++ b/doc/style.css @@ -319,16 +319,6 @@ legend { float: left; margin: 2px 0; } -#signin_openid_url_label { - float: left; - margin-right: 1ex; -} -#signin_openid { - padding: 10px 10px; - border: 1px solid #aaa; - background: #eee; - color: black !important; -} label.block { display: block; } @@ -342,13 +332,6 @@ ol.form { li.form { padding-bottom: 1em; } -input#openid_url { - background: url(wikiicons/openidlogin-bg.gif) no-repeat; - background-color: #fff; - background-position: 0 50%; - color: #000; - padding-left: 18px; -} input#searchbox { background: url(wikiicons/search-bg.gif) no-repeat; background-color: #fff; @@ -440,3 +423,43 @@ li.L8 { list-style: upper-alpha; } color: white !important; background: #ff9900; } + +/* openid selector */ +#openid_choice { + display: none; +} +#openid_input_area { + clear: both; + padding: 10px; +} +#openid_btns, #openid_btns br { + clear: both; +} +#openid_highlight { + padding: 3px; + background-color: #FFFCC9; + float: left; +} +.openid_large_btn { + width: 100px; + height: 60px; + border: 1px solid #DDD; + margin: 3px; + float: left; +} +.openid_small_btn { + width: 24px; + height: 24px; + border: 1px solid #DDD; + margin: 3px; + float: left; +} +a.openid_large_btn:focus { + outline: none; +} +a.openid_large_btn:focus { + -moz-outline-style: none; +} +.openid_selected { + border: 4px solid #DDD; +} diff --git a/t/openiduser.t b/t/openiduser.t index caabbcefc..746090103 100755 --- a/t/openiduser.t +++ b/t/openiduser.t @@ -12,7 +12,7 @@ BEGIN { else { eval q{use Test::More tests => 11}; } - use_ok("IkiWiki::Plugin::openid"); + use_ok("IkiWiki"); } # Some typical examples: diff --git a/t/permalink.t b/t/permalink.t index b49b98338..adb8648b4 100755 --- a/t/permalink.t +++ b/t/permalink.t @@ -5,7 +5,7 @@ use Test::More 'no_plan'; ok(! system("mkdir t/tmp")); ok(! system("make -s ikiwiki.out")); -ok(! system("perl -I. ./ikiwiki.out -plugin inline -url=http://example.com -cgiurl=http://example.com/ikiwiki.cgi -rss -atom -underlaydir=underlays/basewiki -templatedir=templates t/tinyblog t/tmp/out")); +ok(! system("perl -I. ./ikiwiki.out -plugin inline -url=http://example.com -cgiurl=http://example.com/ikiwiki.cgi -rss -atom -underlaydir=underlays/basewiki -set underlaydirbase=underlays -templatedir=templates t/tinyblog t/tmp/out")); # This guid should never, ever change, for any reason whatsoever! my $guid="http://example.com/post/"; ok(length `grep '$guid' t/tmp/out/index.rss`); diff --git a/templates/openid-selector.tmpl b/templates/openid-selector.tmpl index 76c4051ed..393479865 100644 --- a/templates/openid-selector.tmpl +++ b/templates/openid-selector.tmpl @@ -1,30 +1,29 @@ - - + + - -
- -
-
-
-

:

-
+
+ +
+
+
+ + local account +
- - local account +
+ + +
+ +
-
- - - - - + diff --git a/underlays/javascript/ikiwiki/openid-selector/jquery.js b/underlays/javascript/ikiwiki/openid-selector/jquery.js deleted file mode 100644 index 82b98e1d7..000000000 --- a/underlays/javascript/ikiwiki/openid-selector/jquery.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * jQuery 1.2.6 - New Wave Javascript - * - * Copyright (c) 2008 John Resig (jquery.com) - * Dual licensed under the MIT (MIT-LICENSE.txt) - * and GPL (GPL-LICENSE.txt) licenses. - * - * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $ - * $Rev: 5685 $ - */ -(function(){var _jQuery=window.jQuery,_$=window.$;var jQuery=window.jQuery=window.$=function(selector,context){return new jQuery.fn.init(selector,context);};var quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,isSimple=/^.[^:#\[\.]*$/,undefined;jQuery.fn=jQuery.prototype={init:function(selector,context){selector=selector||document;if(selector.nodeType){this[0]=selector;this.length=1;return this;}if(typeof selector=="string"){var match=quickExpr.exec(selector);if(match&&(match[1]||!context)){if(match[1])selector=jQuery.clean([match[1]],context);else{var elem=document.getElementById(match[3]);if(elem){if(elem.id!=match[3])return jQuery().find(selector);return jQuery(elem);}selector=[];}}else -return jQuery(context).find(selector);}else if(jQuery.isFunction(selector))return jQuery(document)[jQuery.fn.ready?"ready":"load"](selector);return this.setArray(jQuery.makeArray(selector));},jquery:"1.2.6",size:function(){return this.length;},length:0,get:function(num){return num==undefined?jQuery.makeArray(this):this[num];},pushStack:function(elems){var ret=jQuery(elems);ret.prevObject=this;return ret;},setArray:function(elems){this.length=0;Array.prototype.push.apply(this,elems);return this;},each:function(callback,args){return jQuery.each(this,callback,args);},index:function(elem){var ret=-1;return jQuery.inArray(elem&&elem.jquery?elem[0]:elem,this);},attr:function(name,value,type){var options=name;if(name.constructor==String)if(value===undefined)return this[0]&&jQuery[type||"attr"](this[0],name);else{options={};options[name]=value;}return this.each(function(i){for(name in options)jQuery.attr(type?this.style:this,name,jQuery.prop(this,options[name],type,i,name));});},css:function(key,value){if((key=='width'||key=='height')&&parseFloat(value)<0)value=undefined;return this.attr(key,value,"curCSS");},text:function(text){if(typeof text!="object"&&text!=null)return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(text));var ret="";jQuery.each(text||this,function(){jQuery.each(this.childNodes,function(){if(this.nodeType!=8)ret+=this.nodeType!=1?this.nodeValue:jQuery.fn.text([this]);});});return ret;},wrapAll:function(html){if(this[0])jQuery(html,this[0].ownerDocument).clone().insertBefore(this[0]).map(function(){var elem=this;while(elem.firstChild)elem=elem.firstChild;return elem;}).append(this);return this;},wrapInner:function(html){return this.each(function(){jQuery(this).contents().wrapAll(html);});},wrap:function(html){return this.each(function(){jQuery(this).wrapAll(html);});},append:function(){return this.domManip(arguments,true,false,function(elem){if(this.nodeType==1)this.appendChild(elem);});},prepend:function(){return this.domManip(arguments,true,true,function(elem){if(this.nodeType==1)this.insertBefore(elem,this.firstChild);});},before:function(){return this.domManip(arguments,false,false,function(elem){this.parentNode.insertBefore(elem,this);});},after:function(){return this.domManip(arguments,false,true,function(elem){this.parentNode.insertBefore(elem,this.nextSibling);});},end:function(){return this.prevObject||jQuery([]);},find:function(selector){var elems=jQuery.map(this,function(elem){return jQuery.find(selector,elem);});return this.pushStack(/[^+>] [^+>]/.test(selector)||selector.indexOf("..")>-1?jQuery.unique(elems):elems);},clone:function(events){var ret=this.map(function(){if(jQuery.browser.msie&&!jQuery.isXMLDoc(this)){var clone=this.cloneNode(true),container=document.createElement("div");container.appendChild(clone);return jQuery.clean([container.innerHTML])[0];}else -return this.cloneNode(true);});var clone=ret.find("*").andSelf().each(function(){if(this[expando]!=undefined)this[expando]=null;});if(events===true)this.find("*").andSelf().each(function(i){if(this.nodeType==3)return;var events=jQuery.data(this,"events");for(var type in events)for(var handler in events[type])jQuery.event.add(clone[i],type,events[type][handler],events[type][handler].data);});return ret;},filter:function(selector){return this.pushStack(jQuery.isFunction(selector)&&jQuery.grep(this,function(elem,i){return selector.call(elem,i);})||jQuery.multiFilter(selector,this));},not:function(selector){if(selector.constructor==String)if(isSimple.test(selector))return this.pushStack(jQuery.multiFilter(selector,this,true));else -selector=jQuery.multiFilter(selector,this);var isArrayLike=selector.length&&selector[selector.length-1]!==undefined&&!selector.nodeType;return this.filter(function(){return isArrayLike?jQuery.inArray(this,selector)<0:this!=selector;});},add:function(selector){return this.pushStack(jQuery.unique(jQuery.merge(this.get(),typeof selector=='string'?jQuery(selector):jQuery.makeArray(selector))));},is:function(selector){return!!selector&&jQuery.multiFilter(selector,this).length>0;},hasClass:function(selector){return this.is("."+selector);},val:function(value){if(value==undefined){if(this.length){var elem=this[0];if(jQuery.nodeName(elem,"select")){var index=elem.selectedIndex,values=[],options=elem.options,one=elem.type=="select-one";if(index<0)return null;for(var i=one?index:0,max=one?index+1:options.length;i=0||jQuery.inArray(this.name,value)>=0);else if(jQuery.nodeName(this,"select")){var values=jQuery.makeArray(value);jQuery("option",this).each(function(){this.selected=(jQuery.inArray(this.value,values)>=0||jQuery.inArray(this.text,values)>=0);});if(!values.length)this.selectedIndex=-1;}else -this.value=value;});},html:function(value){return value==undefined?(this[0]?this[0].innerHTML:null):this.empty().append(value);},replaceWith:function(value){return this.after(value).remove();},eq:function(i){return this.slice(i,i+1);},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments));},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem);}));},andSelf:function(){return this.add(this.prevObject);},data:function(key,value){var parts=key.split(".");parts[1]=parts[1]?"."+parts[1]:"";if(value===undefined){var data=this.triggerHandler("getData"+parts[1]+"!",[parts[0]]);if(data===undefined&&this.length)data=jQuery.data(this[0],key);return data===undefined&&parts[1]?this.data(parts[0]):data;}else -return this.trigger("setData"+parts[1]+"!",[parts[0],value]).each(function(){jQuery.data(this,key,value);});},removeData:function(key){return this.each(function(){jQuery.removeData(this,key);});},domManip:function(args,table,reverse,callback){var clone=this.length>1,elems;return this.each(function(){if(!elems){elems=jQuery.clean(args,this.ownerDocument);if(reverse)elems.reverse();}var obj=this;if(table&&jQuery.nodeName(this,"table")&&jQuery.nodeName(elems[0],"tr"))obj=this.getElementsByTagName("tbody")[0]||this.appendChild(this.ownerDocument.createElement("tbody"));var scripts=jQuery([]);jQuery.each(elems,function(){var elem=clone?jQuery(this).clone(true)[0]:this;if(jQuery.nodeName(elem,"script"))scripts=scripts.add(elem);else{if(elem.nodeType==1)scripts=scripts.add(jQuery("script",elem).remove());callback.call(obj,elem);}});scripts.each(evalScript);});}};jQuery.fn.init.prototype=jQuery.fn;function evalScript(i,elem){if(elem.src)jQuery.ajax({url:elem.src,async:false,dataType:"script"});else -jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");if(elem.parentNode)elem.parentNode.removeChild(elem);}function now(){return+new Date;}jQuery.extend=jQuery.fn.extend=function(){var target=arguments[0]||{},i=1,length=arguments.length,deep=false,options;if(target.constructor==Boolean){deep=target;target=arguments[1]||{};i=2;}if(typeof target!="object"&&typeof target!="function")target={};if(length==i){target=this;--i;}for(;i-1;}},swap:function(elem,options,callback){var old={};for(var name in options){old[name]=elem.style[name];elem.style[name]=options[name];}callback.call(elem);for(var name in options)elem.style[name]=old[name];},css:function(elem,name,force){if(name=="width"||name=="height"){var val,props={position:"absolute",visibility:"hidden",display:"block"},which=name=="width"?["Left","Right"]:["Top","Bottom"];function getWH(){val=name=="width"?elem.offsetWidth:elem.offsetHeight;var padding=0,border=0;jQuery.each(which,function(){padding+=parseFloat(jQuery.curCSS(elem,"padding"+this,true))||0;border+=parseFloat(jQuery.curCSS(elem,"border"+this+"Width",true))||0;});val-=Math.round(padding+border);}if(jQuery(elem).is(":visible"))getWH();else -jQuery.swap(elem,props,getWH);return Math.max(0,val);}return jQuery.curCSS(elem,name,force);},curCSS:function(elem,name,force){var ret,style=elem.style;function color(elem){if(!jQuery.browser.safari)return false;var ret=defaultView.getComputedStyle(elem,null);return!ret||ret.getPropertyValue("color")=="";}if(name=="opacity"&&jQuery.browser.msie){ret=jQuery.attr(style,"opacity");return ret==""?"1":ret;}if(jQuery.browser.opera&&name=="display"){var save=style.outline;style.outline="0 solid black";style.outline=save;}if(name.match(/float/i))name=styleFloat;if(!force&&style&&style[name])ret=style[name];else if(defaultView.getComputedStyle){if(name.match(/float/i))name="float";name=name.replace(/([A-Z])/g,"-$1").toLowerCase();var computedStyle=defaultView.getComputedStyle(elem,null);if(computedStyle&&!color(elem))ret=computedStyle.getPropertyValue(name);else{var swap=[],stack=[],a=elem,i=0;for(;a&&color(a);a=a.parentNode)stack.unshift(a);for(;i]*?)\/>/g,function(all,front,tag){return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?all:front+">";});var tags=jQuery.trim(elem).toLowerCase(),div=context.createElement("div");var wrap=!tags.indexOf("",""]||!tags.indexOf("",""]||tags.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!tags.indexOf("",""]||(!tags.indexOf("",""]||!tags.indexOf("",""]||jQuery.browser.msie&&[1,"div
","
"]||[0,"",""];div.innerHTML=wrap[1]+elem+wrap[2];while(wrap[0]--)div=div.lastChild;if(jQuery.browser.msie){var tbody=!tags.indexOf(""&&tags.indexOf("=0;--j)if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length)tbody[j].parentNode.removeChild(tbody[j]);if(/^\s/.test(elem))div.insertBefore(context.createTextNode(elem.match(/^\s*/)[0]),div.firstChild);}elem=jQuery.makeArray(div.childNodes);}if(elem.length===0&&(!jQuery.nodeName(elem,"form")&&!jQuery.nodeName(elem,"select")))return;if(elem[0]==undefined||jQuery.nodeName(elem,"form")||elem.options)ret.push(elem);else -ret=jQuery.merge(ret,elem);});return ret;},attr:function(elem,name,value){if(!elem||elem.nodeType==3||elem.nodeType==8)return undefined;var notxml=!jQuery.isXMLDoc(elem),set=value!==undefined,msie=jQuery.browser.msie;name=notxml&&jQuery.props[name]||name;if(elem.tagName){var special=/href|src|style/.test(name);if(name=="selected"&&jQuery.browser.safari)elem.parentNode.selectedIndex;if(name in elem&¬xml&&!special){if(set){if(name=="type"&&jQuery.nodeName(elem,"input")&&elem.parentNode)throw"type property can't be changed";elem[name]=value;}if(jQuery.nodeName(elem,"form")&&elem.getAttributeNode(name))return elem.getAttributeNode(name).nodeValue;return elem[name];}if(msie&¬xml&&name=="style")return jQuery.attr(elem.style,"cssText",value);if(set)elem.setAttribute(name,""+value);var attr=msie&¬xml&&special?elem.getAttribute(name,2):elem.getAttribute(name);return attr===null?undefined:attr;}if(msie&&name=="opacity"){if(set){elem.zoom=1;elem.filter=(elem.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(value)+''=="NaN"?"":"alpha(opacity="+value*100+")");}return elem.filter&&elem.filter.indexOf("opacity=")>=0?(parseFloat(elem.filter.match(/opacity=([^)]*)/)[1])/100)+'':"";}name=name.replace(/-([a-z])/ig,function(all,letter){return letter.toUpperCase();});if(set)elem[name]=value;return elem[name];},trim:function(text){return(text||"").replace(/^\s+|\s+$/g,"");},makeArray:function(array){var ret=[];if(array!=null){var i=array.length;if(i==null||array.split||array.setInterval||array.call)ret[0]=array;else -while(i)ret[--i]=array[i];}return ret;},inArray:function(elem,array){for(var i=0,length=array.length;i*",this).remove();while(this.firstChild)this.removeChild(this.firstChild);}},function(name,fn){jQuery.fn[name]=function(){return this.each(fn,arguments);};});jQuery.each(["Height","Width"],function(i,name){var type=name.toLowerCase();jQuery.fn[type]=function(size){return this[0]==window?jQuery.browser.opera&&document.body["client"+name]||jQuery.browser.safari&&window["inner"+name]||document.compatMode=="CSS1Compat"&&document.documentElement["client"+name]||document.body["client"+name]:this[0]==document?Math.max(Math.max(document.body["scroll"+name],document.documentElement["scroll"+name]),Math.max(document.body["offset"+name],document.documentElement["offset"+name])):size==undefined?(this.length?jQuery.css(this[0],type):null):this.css(type,size.constructor==String?size:size+"px");};});function num(elem,prop){return elem[0]&&parseInt(jQuery.curCSS(elem[0],prop,true),10)||0;}var chars=jQuery.browser.safari&&parseInt(jQuery.browser.version)<417?"(?:[\\w*_-]|\\\\.)":"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",quickChild=new RegExp("^>\\s*("+chars+"+)"),quickID=new RegExp("^("+chars+"+)(#)("+chars+"+)"),quickClass=new RegExp("^([#.]?)("+chars+"*)");jQuery.extend({expr:{"":function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},"#":function(a,i,m){return a.getAttribute("id")==m[2];},":":{lt:function(a,i,m){return im[3]-0;},nth:function(a,i,m){return m[3]-0==i;},eq:function(a,i,m){return m[3]-0==i;},first:function(a,i){return i==0;},last:function(a,i,m,r){return i==r.length-1;},even:function(a,i){return i%2==0;},odd:function(a,i){return i%2;},"first-child":function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},"last-child":function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},"only-child":function(a){return!jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},parent:function(a){return a.firstChild;},empty:function(a){return!a.firstChild;},contains:function(a,i,m){return(a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},visible:function(a){return"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},hidden:function(a){return"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},enabled:function(a){return!a.disabled;},disabled:function(a){return a.disabled;},checked:function(a){return a.checked;},selected:function(a){return a.selected||jQuery.attr(a,"selected");},text:function(a){return"text"==a.type;},radio:function(a){return"radio"==a.type;},checkbox:function(a){return"checkbox"==a.type;},file:function(a){return"file"==a.type;},password:function(a){return"password"==a.type;},submit:function(a){return"submit"==a.type;},image:function(a){return"image"==a.type;},reset:function(a){return"reset"==a.type;},button:function(a){return"button"==a.type||jQuery.nodeName(a,"button");},input:function(a){return/input|select|textarea|button/i.test(a.nodeName);},has:function(a,i,m){return jQuery.find(m[3],a).length;},header:function(a){return/h\d/i.test(a.nodeName);},animated:function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}}},parse:[/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,new RegExp("^([:.#]*)("+chars+"+)")],multiFilter:function(expr,elems,not){var old,cur=[];while(expr&&expr!=old){old=expr;var f=jQuery.filter(expr,elems,not);expr=f.t.replace(/^\s*,\s*/,"");cur=not?elems=f.r:jQuery.merge(cur,f.r);}return cur;},find:function(t,context){if(typeof t!="string")return[t];if(context&&context.nodeType!=1&&context.nodeType!=9)return[];context=context||document;var ret=[context],done=[],last,nodeName;while(t&&last!=t){var r=[];last=t;t=jQuery.trim(t);var foundToken=false,re=quickChild,m=re.exec(t);if(m){nodeName=m[1].toUpperCase();for(var i=0;ret[i];i++)for(var c=ret[i].firstChild;c;c=c.nextSibling)if(c.nodeType==1&&(nodeName=="*"||c.nodeName.toUpperCase()==nodeName))r.push(c);ret=r;t=t.replace(re,"");if(t.indexOf(" ")==0)continue;foundToken=true;}else{re=/^([>+~])\s*(\w*)/i;if((m=re.exec(t))!=null){r=[];var merge={};nodeName=m[2].toUpperCase();m=m[1];for(var j=0,rl=ret.length;j=0;if(!not&&pass||not&&!pass)tmp.push(r[i]);}return tmp;},filter:function(t,r,not){var last;while(t&&t!=last){last=t;var p=jQuery.parse,m;for(var i=0;p[i];i++){m=p[i].exec(t);if(m){t=t.substring(m[0].length);m[2]=m[2].replace(/\\/g,"");break;}}if(!m)break;if(m[1]==":"&&m[2]=="not")r=isSimple.test(m[3])?jQuery.filter(m[3],r,true).r:jQuery(r).not(m[3]);else if(m[1]==".")r=jQuery.classFilter(r,m[2],not);else if(m[1]=="["){var tmp=[],type=m[3];for(var i=0,rl=r.length;i=0)^not)tmp.push(a);}r=tmp;}else if(m[1]==":"&&m[2]=="nth-child"){var merge={},tmp=[],test=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(m[3]=="even"&&"2n"||m[3]=="odd"&&"2n+1"||!/\D/.test(m[3])&&"0n+"+m[3]||m[3]),first=(test[1]+(test[2]||1))-0,last=test[3]-0;for(var i=0,rl=r.length;i=0)add=true;if(add^not)tmp.push(node);}r=tmp;}else{var fn=jQuery.expr[m[1]];if(typeof fn=="object")fn=fn[m[2]];if(typeof fn=="string")fn=eval("false||function(a,i){return "+fn+";}");r=jQuery.grep(r,function(elem,i){return fn(elem,i,m,r);},not);}}return{r:r,t:t};},dir:function(elem,dir){var matched=[],cur=elem[dir];while(cur&&cur!=document){if(cur.nodeType==1)matched.push(cur);cur=cur[dir];}return matched;},nth:function(cur,result,dir,elem){result=result||1;var num=0;for(;cur;cur=cur[dir])if(cur.nodeType==1&&++num==result)break;return cur;},sibling:function(n,elem){var r=[];for(;n;n=n.nextSibling){if(n.nodeType==1&&n!=elem)r.push(n);}return r;}});jQuery.event={add:function(elem,types,handler,data){if(elem.nodeType==3||elem.nodeType==8)return;if(jQuery.browser.msie&&elem.setInterval)elem=window;if(!handler.guid)handler.guid=this.guid++;if(data!=undefined){var fn=handler;handler=this.proxy(fn,function(){return fn.apply(this,arguments);});handler.data=data;}var events=jQuery.data(elem,"events")||jQuery.data(elem,"events",{}),handle=jQuery.data(elem,"handle")||jQuery.data(elem,"handle",function(){if(typeof jQuery!="undefined"&&!jQuery.event.triggered)return jQuery.event.handle.apply(arguments.callee.elem,arguments);});handle.elem=elem;jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];handler.type=parts[1];var handlers=events[type];if(!handlers){handlers=events[type]={};if(!jQuery.event.special[type]||jQuery.event.special[type].setup.call(elem)===false){if(elem.addEventListener)elem.addEventListener(type,handle,false);else if(elem.attachEvent)elem.attachEvent("on"+type,handle);}}handlers[handler.guid]=handler;jQuery.event.global[type]=true;});elem=null;},guid:1,global:{},remove:function(elem,types,handler){if(elem.nodeType==3||elem.nodeType==8)return;var events=jQuery.data(elem,"events"),ret,index;if(events){if(types==undefined||(typeof types=="string"&&types.charAt(0)=="."))for(var type in events)this.remove(elem,type+(types||""));else{if(types.type){handler=types.handler;types=types.type;}jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];if(events[type]){if(handler)delete events[type][handler.guid];else -for(handler in events[type])if(!parts[1]||events[type][handler].type==parts[1])delete events[type][handler];for(ret in events[type])break;if(!ret){if(!jQuery.event.special[type]||jQuery.event.special[type].teardown.call(elem)===false){if(elem.removeEventListener)elem.removeEventListener(type,jQuery.data(elem,"handle"),false);else if(elem.detachEvent)elem.detachEvent("on"+type,jQuery.data(elem,"handle"));}ret=null;delete events[type];}}});}for(ret in events)break;if(!ret){var handle=jQuery.data(elem,"handle");if(handle)handle.elem=null;jQuery.removeData(elem,"events");jQuery.removeData(elem,"handle");}}},trigger:function(type,data,elem,donative,extra){data=jQuery.makeArray(data);if(type.indexOf("!")>=0){type=type.slice(0,-1);var exclusive=true;}if(!elem){if(this.global[type])jQuery("*").add([window,document]).trigger(type,data);}else{if(elem.nodeType==3||elem.nodeType==8)return undefined;var val,ret,fn=jQuery.isFunction(elem[type]||null),event=!data[0]||!data[0].preventDefault;if(event){data.unshift({type:type,target:elem,preventDefault:function(){},stopPropagation:function(){},timeStamp:now()});data[0][expando]=true;}data[0].type=type;if(exclusive)data[0].exclusive=true;var handle=jQuery.data(elem,"handle");if(handle)val=handle.apply(elem,data);if((!fn||(jQuery.nodeName(elem,'a')&&type=="click"))&&elem["on"+type]&&elem["on"+type].apply(elem,data)===false)val=false;if(event)data.shift();if(extra&&jQuery.isFunction(extra)){ret=extra.apply(elem,val==null?data:data.concat(val));if(ret!==undefined)val=ret;}if(fn&&donative!==false&&val!==false&&!(jQuery.nodeName(elem,'a')&&type=="click")){this.triggered=true;try{elem[type]();}catch(e){}}this.triggered=false;}return val;},handle:function(event){var val,ret,namespace,all,handlers;event=arguments[0]=jQuery.event.fix(event||window.event);namespace=event.type.split(".");event.type=namespace[0];namespace=namespace[1];all=!namespace&&!event.exclusive;handlers=(jQuery.data(this,"events")||{})[event.type];for(var j in handlers){var handler=handlers[j];if(all||handler.type==namespace){event.handler=handler;event.data=handler.data;ret=handler.apply(this,arguments);if(val!==false)val=ret;if(ret===false){event.preventDefault();event.stopPropagation();}}}return val;},fix:function(event){if(event[expando]==true)return event;var originalEvent=event;event={originalEvent:originalEvent};var props="altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which".split(" ");for(var i=props.length;i;i--)event[props[i]]=originalEvent[props[i]];event[expando]=true;event.preventDefault=function(){if(originalEvent.preventDefault)originalEvent.preventDefault();originalEvent.returnValue=false;};event.stopPropagation=function(){if(originalEvent.stopPropagation)originalEvent.stopPropagation();originalEvent.cancelBubble=true;};event.timeStamp=event.timeStamp||now();if(!event.target)event.target=event.srcElement||document;if(event.target.nodeType==3)event.target=event.target.parentNode;if(!event.relatedTarget&&event.fromElement)event.relatedTarget=event.fromElement==event.target?event.toElement:event.fromElement;if(event.pageX==null&&event.clientX!=null){var doc=document.documentElement,body=document.body;event.pageX=event.clientX+(doc&&doc.scrollLeft||body&&body.scrollLeft||0)-(doc.clientLeft||0);event.pageY=event.clientY+(doc&&doc.scrollTop||body&&body.scrollTop||0)-(doc.clientTop||0);}if(!event.which&&((event.charCode||event.charCode===0)?event.charCode:event.keyCode))event.which=event.charCode||event.keyCode;if(!event.metaKey&&event.ctrlKey)event.metaKey=event.ctrlKey;if(!event.which&&event.button)event.which=(event.button&1?1:(event.button&2?3:(event.button&4?2:0)));return event;},proxy:function(fn,proxy){proxy.guid=fn.guid=fn.guid||proxy.guid||this.guid++;return proxy;},special:{ready:{setup:function(){bindReady();return;},teardown:function(){return;}},mouseenter:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseover",jQuery.event.special.mouseenter.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseover",jQuery.event.special.mouseenter.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseenter";return jQuery.event.handle.apply(this,arguments);}},mouseleave:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseout",jQuery.event.special.mouseleave.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseout",jQuery.event.special.mouseleave.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseleave";return jQuery.event.handle.apply(this,arguments);}}}};jQuery.fn.extend({bind:function(type,data,fn){return type=="unload"?this.one(type,data,fn):this.each(function(){jQuery.event.add(this,type,fn||data,fn&&data);});},one:function(type,data,fn){var one=jQuery.event.proxy(fn||data,function(event){jQuery(this).unbind(event,one);return(fn||data).apply(this,arguments);});return this.each(function(){jQuery.event.add(this,type,one,fn&&data);});},unbind:function(type,fn){return this.each(function(){jQuery.event.remove(this,type,fn);});},trigger:function(type,data,fn){return this.each(function(){jQuery.event.trigger(type,data,this,true,fn);});},triggerHandler:function(type,data,fn){return this[0]&&jQuery.event.trigger(type,data,this[0],false,fn);},toggle:function(fn){var args=arguments,i=1;while(i=0){var selector=url.slice(off,url.length);url=url.slice(0,off);}callback=callback||function(){};var type="GET";if(params)if(jQuery.isFunction(params)){callback=params;params=null;}else{params=jQuery.param(params);type="POST";}var self=this;jQuery.ajax({url:url,type:type,dataType:"html",data:params,complete:function(res,status){if(status=="success"||status=="notmodified")self.html(selector?jQuery("
").append(res.responseText.replace(//g,"")).find(selector):res.responseText);self.each(callback,[res.responseText,status,res]);}});return this;},serialize:function(){return jQuery.param(this.serializeArray());},serializeArray:function(){return this.map(function(){return jQuery.nodeName(this,"form")?jQuery.makeArray(this.elements):this;}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type));}).map(function(i,elem){var val=jQuery(this).val();return val==null?null:val.constructor==Array?jQuery.map(val,function(val,i){return{name:elem.name,value:val};}):{name:elem.name,value:val};}).get();}});jQuery.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(i,o){jQuery.fn[o]=function(f){return this.bind(o,f);};});var jsc=now();jQuery.extend({get:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data=null;}return jQuery.ajax({type:"GET",url:url,data:data,success:callback,dataType:type});},getScript:function(url,callback){return jQuery.get(url,null,callback,"script");},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json");},post:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data={};}return jQuery.ajax({type:"POST",url:url,data:data,success:callback,dataType:type});},ajaxSetup:function(settings){jQuery.extend(jQuery.ajaxSettings,settings);},ajaxSettings:{url:location.href,global:true,type:"GET",timeout:0,contentType:"application/x-www-form-urlencoded",processData:true,async:true,data:null,username:null,password:null,accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(s){s=jQuery.extend(true,s,jQuery.extend(true,{},jQuery.ajaxSettings,s));var jsonp,jsre=/=\?(&|$)/g,status,data,type=s.type.toUpperCase();if(s.data&&s.processData&&typeof s.data!="string")s.data=jQuery.param(s.data);if(s.dataType=="jsonp"){if(type=="GET"){if(!s.url.match(jsre))s.url+=(s.url.match(/\?/)?"&":"?")+(s.jsonp||"callback")+"=?";}else if(!s.data||!s.data.match(jsre))s.data=(s.data?s.data+"&":"")+(s.jsonp||"callback")+"=?";s.dataType="json";}if(s.dataType=="json"&&(s.data&&s.data.match(jsre)||s.url.match(jsre))){jsonp="jsonp"+jsc++;if(s.data)s.data=(s.data+"").replace(jsre,"="+jsonp+"$1");s.url=s.url.replace(jsre,"="+jsonp+"$1");s.dataType="script";window[jsonp]=function(tmp){data=tmp;success();complete();window[jsonp]=undefined;try{delete window[jsonp];}catch(e){}if(head)head.removeChild(script);};}if(s.dataType=="script"&&s.cache==null)s.cache=false;if(s.cache===false&&type=="GET"){var ts=now();var ret=s.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+ts+"$2");s.url=ret+((ret==s.url)?(s.url.match(/\?/)?"&":"?")+"_="+ts:"");}if(s.data&&type=="GET"){s.url+=(s.url.match(/\?/)?"&":"?")+s.data;s.data=null;}if(s.global&&!jQuery.active++)jQuery.event.trigger("ajaxStart");var remote=/^(?:\w+:)?\/\/([^\/?#]+)/;if(s.dataType=="script"&&type=="GET"&&remote.test(s.url)&&remote.exec(s.url)[1]!=location.host){var head=document.getElementsByTagName("head")[0];var script=document.createElement("script");script.src=s.url;if(s.scriptCharset)script.charset=s.scriptCharset;if(!jsonp){var done=false;script.onload=script.onreadystatechange=function(){if(!done&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){done=true;success();complete();head.removeChild(script);}};}head.appendChild(script);return undefined;}var requestDone=false;var xhr=window.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest();if(s.username)xhr.open(type,s.url,s.async,s.username,s.password);else -xhr.open(type,s.url,s.async);try{if(s.data)xhr.setRequestHeader("Content-Type",s.contentType);if(s.ifModified)xhr.setRequestHeader("If-Modified-Since",jQuery.lastModified[s.url]||"Thu, 01 Jan 1970 00:00:00 GMT");xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Accept",s.dataType&&s.accepts[s.dataType]?s.accepts[s.dataType]+", */*":s.accepts._default);}catch(e){}if(s.beforeSend&&s.beforeSend(xhr,s)===false){s.global&&jQuery.active--;xhr.abort();return false;}if(s.global)jQuery.event.trigger("ajaxSend",[xhr,s]);var onreadystatechange=function(isTimeout){if(!requestDone&&xhr&&(xhr.readyState==4||isTimeout=="timeout")){requestDone=true;if(ival){clearInterval(ival);ival=null;}status=isTimeout=="timeout"&&"timeout"||!jQuery.httpSuccess(xhr)&&"error"||s.ifModified&&jQuery.httpNotModified(xhr,s.url)&&"notmodified"||"success";if(status=="success"){try{data=jQuery.httpData(xhr,s.dataType,s.dataFilter);}catch(e){status="parsererror";}}if(status=="success"){var modRes;try{modRes=xhr.getResponseHeader("Last-Modified");}catch(e){}if(s.ifModified&&modRes)jQuery.lastModified[s.url]=modRes;if(!jsonp)success();}else -jQuery.handleError(s,xhr,status);complete();if(s.async)xhr=null;}};if(s.async){var ival=setInterval(onreadystatechange,13);if(s.timeout>0)setTimeout(function(){if(xhr){xhr.abort();if(!requestDone)onreadystatechange("timeout");}},s.timeout);}try{xhr.send(s.data);}catch(e){jQuery.handleError(s,xhr,null,e);}if(!s.async)onreadystatechange();function success(){if(s.success)s.success(data,status);if(s.global)jQuery.event.trigger("ajaxSuccess",[xhr,s]);}function complete(){if(s.complete)s.complete(xhr,status);if(s.global)jQuery.event.trigger("ajaxComplete",[xhr,s]);if(s.global&&!--jQuery.active)jQuery.event.trigger("ajaxStop");}return xhr;},handleError:function(s,xhr,status,e){if(s.error)s.error(xhr,status,e);if(s.global)jQuery.event.trigger("ajaxError",[xhr,s,e]);},active:0,httpSuccess:function(xhr){try{return!xhr.status&&location.protocol=="file:"||(xhr.status>=200&&xhr.status<300)||xhr.status==304||xhr.status==1223||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpNotModified:function(xhr,url){try{var xhrRes=xhr.getResponseHeader("Last-Modified");return xhr.status==304||xhrRes==jQuery.lastModified[url]||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpData:function(xhr,type,filter){var ct=xhr.getResponseHeader("content-type"),xml=type=="xml"||!type&&ct&&ct.indexOf("xml")>=0,data=xml?xhr.responseXML:xhr.responseText;if(xml&&data.documentElement.tagName=="parsererror")throw"parsererror";if(filter)data=filter(data,type);if(type=="script")jQuery.globalEval(data);if(type=="json")data=eval("("+data+")");return data;},param:function(a){var s=[];if(a.constructor==Array||a.jquery)jQuery.each(a,function(){s.push(encodeURIComponent(this.name)+"="+encodeURIComponent(this.value));});else -for(var j in a)if(a[j]&&a[j].constructor==Array)jQuery.each(a[j],function(){s.push(encodeURIComponent(j)+"="+encodeURIComponent(this));});else -s.push(encodeURIComponent(j)+"="+encodeURIComponent(jQuery.isFunction(a[j])?a[j]():a[j]));return s.join("&").replace(/%20/g,"+");}});jQuery.fn.extend({show:function(speed,callback){return speed?this.animate({height:"show",width:"show",opacity:"show"},speed,callback):this.filter(":hidden").each(function(){this.style.display=this.oldblock||"";if(jQuery.css(this,"display")=="none"){var elem=jQuery("<"+this.tagName+" />").appendTo("body");this.style.display=elem.css("display");if(this.style.display=="none")this.style.display="block";elem.remove();}}).end();},hide:function(speed,callback){return speed?this.animate({height:"hide",width:"hide",opacity:"hide"},speed,callback):this.filter(":visible").each(function(){this.oldblock=this.oldblock||jQuery.css(this,"display");this.style.display="none";}).end();},_toggle:jQuery.fn.toggle,toggle:function(fn,fn2){return jQuery.isFunction(fn)&&jQuery.isFunction(fn2)?this._toggle.apply(this,arguments):fn?this.animate({height:"toggle",width:"toggle",opacity:"toggle"},fn,fn2):this.each(function(){jQuery(this)[jQuery(this).is(":hidden")?"show":"hide"]();});},slideDown:function(speed,callback){return this.animate({height:"show"},speed,callback);},slideUp:function(speed,callback){return this.animate({height:"hide"},speed,callback);},slideToggle:function(speed,callback){return this.animate({height:"toggle"},speed,callback);},fadeIn:function(speed,callback){return this.animate({opacity:"show"},speed,callback);},fadeOut:function(speed,callback){return this.animate({opacity:"hide"},speed,callback);},fadeTo:function(speed,to,callback){return this.animate({opacity:to},speed,callback);},animate:function(prop,speed,easing,callback){var optall=jQuery.speed(speed,easing,callback);return this[optall.queue===false?"each":"queue"](function(){if(this.nodeType!=1)return false;var opt=jQuery.extend({},optall),p,hidden=jQuery(this).is(":hidden"),self=this;for(p in prop){if(prop[p]=="hide"&&hidden||prop[p]=="show"&&!hidden)return opt.complete.call(this);if(p=="height"||p=="width"){opt.display=jQuery.css(this,"display");opt.overflow=this.style.overflow;}}if(opt.overflow!=null)this.style.overflow="hidden";opt.curAnim=jQuery.extend({},prop);jQuery.each(prop,function(name,val){var e=new jQuery.fx(self,opt,name);if(/toggle|show|hide/.test(val))e[val=="toggle"?hidden?"show":"hide":val](prop);else{var parts=val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),start=e.cur(true)||0;if(parts){var end=parseFloat(parts[2]),unit=parts[3]||"px";if(unit!="px"){self.style[name]=(end||1)+unit;start=((end||1)/e.cur(true))*start;self.style[name]=start+unit;}if(parts[1])end=((parts[1]=="-="?-1:1)*end)+start;e.custom(start,end,unit);}else -e.custom(start,val,"");}});return true;});},queue:function(type,fn){if(jQuery.isFunction(type)||(type&&type.constructor==Array)){fn=type;type="fx";}if(!type||(typeof type=="string"&&!fn))return queue(this[0],type);return this.each(function(){if(fn.constructor==Array)queue(this,type,fn);else{queue(this,type).push(fn);if(queue(this,type).length==1)fn.call(this);}});},stop:function(clearQueue,gotoEnd){var timers=jQuery.timers;if(clearQueue)this.queue([]);this.each(function(){for(var i=timers.length-1;i>=0;i--)if(timers[i].elem==this){if(gotoEnd)timers[i](true);timers.splice(i,1);}});if(!gotoEnd)this.dequeue();return this;}});var queue=function(elem,type,array){if(elem){type=type||"fx";var q=jQuery.data(elem,type+"queue");if(!q||array)q=jQuery.data(elem,type+"queue",jQuery.makeArray(array));}return q;};jQuery.fn.dequeue=function(type){type=type||"fx";return this.each(function(){var q=queue(this,type);q.shift();if(q.length)q[0].call(this);});};jQuery.extend({speed:function(speed,easing,fn){var opt=speed&&speed.constructor==Object?speed:{complete:fn||!fn&&easing||jQuery.isFunction(speed)&&speed,duration:speed,easing:fn&&easing||easing&&easing.constructor!=Function&&easing};opt.duration=(opt.duration&&opt.duration.constructor==Number?opt.duration:jQuery.fx.speeds[opt.duration])||jQuery.fx.speeds.def;opt.old=opt.complete;opt.complete=function(){if(opt.queue!==false)jQuery(this).dequeue();if(jQuery.isFunction(opt.old))opt.old.call(this);};return opt;},easing:{linear:function(p,n,firstNum,diff){return firstNum+diff*p;},swing:function(p,n,firstNum,diff){return((-Math.cos(p*Math.PI)/2)+0.5)*diff+firstNum;}},timers:[],timerId:null,fx:function(elem,options,prop){this.options=options;this.elem=elem;this.prop=prop;if(!options.orig)options.orig={};}});jQuery.fx.prototype={update:function(){if(this.options.step)this.options.step.call(this.elem,this.now,this);(jQuery.fx.step[this.prop]||jQuery.fx.step._default)(this);if(this.prop=="height"||this.prop=="width")this.elem.style.display="block";},cur:function(force){if(this.elem[this.prop]!=null&&this.elem.style[this.prop]==null)return this.elem[this.prop];var r=parseFloat(jQuery.css(this.elem,this.prop,force));return r&&r>-10000?r:parseFloat(jQuery.curCSS(this.elem,this.prop))||0;},custom:function(from,to,unit){this.startTime=now();this.start=from;this.end=to;this.unit=unit||this.unit||"px";this.now=this.start;this.pos=this.state=0;this.update();var self=this;function t(gotoEnd){return self.step(gotoEnd);}t.elem=this.elem;jQuery.timers.push(t);if(jQuery.timerId==null){jQuery.timerId=setInterval(function(){var timers=jQuery.timers;for(var i=0;ithis.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var done=true;for(var i in this.options.curAnim)if(this.options.curAnim[i]!==true)done=false;if(done){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(jQuery.css(this.elem,"display")=="none")this.elem.style.display="block";}if(this.options.hide)this.elem.style.display="none";if(this.options.hide||this.options.show)for(var p in this.options.curAnim)jQuery.attr(this.elem.style,p,this.options.orig[p]);}if(done)this.options.complete.call(this.elem);return false;}else{var n=t-this.startTime;this.state=n/this.options.duration;this.pos=jQuery.easing[this.options.easing||(jQuery.easing.swing?"swing":"linear")](this.state,n,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update();}return true;}};jQuery.extend(jQuery.fx,{speeds:{slow:600,fast:200,def:400},step:{scrollLeft:function(fx){fx.elem.scrollLeft=fx.now;},scrollTop:function(fx){fx.elem.scrollTop=fx.now;},opacity:function(fx){jQuery.attr(fx.elem.style,"opacity",fx.now);},_default:function(fx){fx.elem.style[fx.prop]=fx.now+fx.unit;}}});jQuery.fn.offset=function(){var left=0,top=0,elem=this[0],results;if(elem)with(jQuery.browser){var parent=elem.parentNode,offsetChild=elem,offsetParent=elem.offsetParent,doc=elem.ownerDocument,safari2=safari&&parseInt(version)<522&&!/adobeair/i.test(userAgent),css=jQuery.curCSS,fixed=css(elem,"position")=="fixed";if(elem.getBoundingClientRect){var box=elem.getBoundingClientRect();add(box.left+Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),box.top+Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));add(-doc.documentElement.clientLeft,-doc.documentElement.clientTop);}else{add(elem.offsetLeft,elem.offsetTop);while(offsetParent){add(offsetParent.offsetLeft,offsetParent.offsetTop);if(mozilla&&!/^t(able|d|h)$/i.test(offsetParent.tagName)||safari&&!safari2)border(offsetParent);if(!fixed&&css(offsetParent,"position")=="fixed")fixed=true;offsetChild=/^body$/i.test(offsetParent.tagName)?offsetChild:offsetParent;offsetParent=offsetParent.offsetParent;}while(parent&&parent.tagName&&!/^body|html$/i.test(parent.tagName)){if(!/^inline|table.*$/i.test(css(parent,"display")))add(-parent.scrollLeft,-parent.scrollTop);if(mozilla&&css(parent,"overflow")!="visible")border(parent);parent=parent.parentNode;}if((safari2&&(fixed||css(offsetChild,"position")=="absolute"))||(mozilla&&css(offsetChild,"position")!="absolute"))add(-doc.body.offsetLeft,-doc.body.offsetTop);if(fixed)add(Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));}results={top:top,left:left};}function border(elem){add(jQuery.curCSS(elem,"borderLeftWidth",true),jQuery.curCSS(elem,"borderTopWidth",true));}function add(l,t){left+=parseInt(l,10)||0;top+=parseInt(t,10)||0;}return results;};jQuery.fn.extend({position:function(){var left=0,top=0,results;if(this[0]){var offsetParent=this.offsetParent(),offset=this.offset(),parentOffset=/^body|html$/i.test(offsetParent[0].tagName)?{top:0,left:0}:offsetParent.offset();offset.top-=num(this,'marginTop');offset.left-=num(this,'marginLeft');parentOffset.top+=num(offsetParent,'borderTopWidth');parentOffset.left+=num(offsetParent,'borderLeftWidth');results={top:offset.top-parentOffset.top,left:offset.left-parentOffset.left};}return results;},offsetParent:function(){var offsetParent=this[0].offsetParent;while(offsetParent&&(!/^body|html$/i.test(offsetParent.tagName)&&jQuery.css(offsetParent,'position')=='static'))offsetParent=offsetParent.offsetParent;return jQuery(offsetParent);}});jQuery.each(['Left','Top'],function(i,name){var method='scroll'+name;jQuery.fn[method]=function(val){if(!this[0])return;return val!=undefined?this.each(function(){this==window||this==document?window.scrollTo(!i?val:jQuery(window).scrollLeft(),i?val:jQuery(window).scrollTop()):this[method]=val;}):this[0]==window||this[0]==document?self[i?'pageYOffset':'pageXOffset']||jQuery.boxModel&&document.documentElement[method]||document.body[method]:this[0][method];};});jQuery.each(["Height","Width"],function(i,name){var tl=i?"Left":"Top",br=i?"Right":"Bottom";jQuery.fn["inner"+name]=function(){return this[name.toLowerCase()]()+num(this,"padding"+tl)+num(this,"padding"+br);};jQuery.fn["outer"+name]=function(margin){return this["inner"+name]()+num(this,"border"+tl+"Width")+num(this,"border"+br+"Width")+(margin?num(this,"margin"+tl)+num(this,"margin"+br):0);};});})(); \ No newline at end of file diff --git a/underlays/javascript/ikiwiki/openid-selector/openid-jquery.js b/underlays/javascript/ikiwiki/openid-selector/openid-jquery.js deleted file mode 100644 index f39fff842..000000000 --- a/underlays/javascript/ikiwiki/openid-selector/openid-jquery.js +++ /dev/null @@ -1,240 +0,0 @@ -/* -Simple OpenID Plugin -http://code.google.com/p/openid-selector/ - -This code is licenced under the New BSD License. -*/ - -var providers_large = { - google: { - name: 'Google', - url: 'https://www.google.com/accounts/o8/id' - }, - yahoo: { - name: 'Yahoo', - url: 'http://me.yahoo.com/' - }, - aol: { - name: 'AOL', - label: 'Enter your AOL screenname.', - url: 'http://openid.aol.com/{username}' - }, - verisign: { - name: 'Verisign', - label: 'Your Verisign username', - url: 'http://{username}.pip.verisignlabs.com/' - }, - openid: { - name: 'OpenID', - label: 'Enter your OpenID.', - url: null - } -}; -var providers_small = { - myopenid: { - name: 'MyOpenID', - label: 'Enter your MyOpenID username.', - url: 'http://{username}.myopenid.com/' - }, - livejournal: { - name: 'LiveJournal', - label: 'Enter your Livejournal username.', - url: 'http://{username}.livejournal.com/' - }, - flickr: { - name: 'Flickr', - label: 'Enter your Flickr username.', - url: 'http://flickr.com/{username}/' - }, - technorati: { - name: 'Technorati', - label: 'Enter your Technorati username.', - url: 'http://technorati.com/people/technorati/{username}/' - }, - wordpress: { - name: 'Wordpress', - label: 'Enter your Wordpress.com username.', - url: 'http://{username}.wordpress.com/' - }, - blogger: { - name: 'Blogger', - label: 'Your Blogger account', - url: 'http://{username}.blogspot.com/' - }, - vidoop: { - name: 'Vidoop', - label: 'Your Vidoop username', - url: 'http://{username}.myvidoop.com/' - }, - claimid: { - name: 'ClaimID', - label: 'Your ClaimID username', - url: 'http://claimid.com/{username}' - } -}; -var providers = $.extend({}, providers_large, providers_small); - -var openid = { - - demo: false, - ajaxHandler: null, - cookie_expires: 6*30, // 6 months. - cookie_name: 'openid_provider', - cookie_path: '/', - - img_path: 'images/', - - input_id: null, - provider_url: null, - provider_id: null, - - init: function(input_id) { - - var openid_btns = $('#openid_btns'); - - this.input_id = input_id; - - $('#openid_choice').show(); - $('#openid_input_area').empty(); - - // add box for each provider - for (id in providers_large) { - - openid_btns.append(this.getBoxHTML(providers_large[id], 'large', '.gif')); - } - if (providers_small) { - openid_btns.append('
'); - - for (id in providers_small) { - - openid_btns.append(this.getBoxHTML(providers_small[id], 'small', '.ico')); - } - } - - $('#openid_form').submit(this.submit); - - var box_id = this.readCookie(); - if (box_id) { - this.signin(box_id, true); - } - }, - getBoxHTML: function(provider, box_size, image_ext) { - - var box_id = provider["name"].toLowerCase(); - return ''; - - }, - /* Provider image click */ - signin: function(box_id, onload) { - - var provider = providers[box_id]; - if (! provider) { - return; - } - - this.highlight(box_id); - this.setCookie(box_id); - - this.provider_id = box_id; - this.provider_url = provider['url']; - - // prompt user for input? - if (provider['label']) { - this.useInputBox(provider); - } else { - $('#openid_input_area').empty(); - if (! onload) { - $('#openid_form').submit(); - } - } - }, - /* Sign-in button click */ - submit: function() { - - var url = openid.provider_url; - if (url) { - url = url.replace('{username}', $('#openid_username').val()); - openid.setOpenIdUrl(url); - } - if(openid.ajaxHandler) { - openid.ajaxHandler(openid.provider_id, document.getElementById(openid.input_id).value); - return false; - } - if(openid.demo) { - alert("In client demo mode. Normally would have submitted OpenID:\r\n" + document.getElementById(openid.input_id).value); - return false; - } - return true; - }, - setOpenIdUrl: function (url) { - - var hidden = $('#'+this.input_id); - if (hidden.length > 0) { - hidden.value = url; - } else { - $('#openid_form').append(''); - } - }, - highlight: function (box_id) { - - // remove previous highlight. - var highlight = $('#openid_highlight'); - if (highlight) { - highlight.replaceWith($('#openid_highlight a')[0]); - } - // add new highlight. - $('.'+box_id).wrap('
'); - }, - setCookie: function (value) { - - var date = new Date(); - date.setTime(date.getTime()+(this.cookie_expires*24*60*60*1000)); - var expires = "; expires="+date.toGMTString(); - - document.cookie = this.cookie_name+"="+value+expires+"; path=" + this.cookie_path; - }, - readCookie: function () { - var nameEQ = this.cookie_name + "="; - var ca = document.cookie.split(';'); - for(var i=0;i < ca.length;i++) { - var c = ca[i]; - while (c.charAt(0)==' ') c = c.substring(1,c.length); - if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); - } - return null; - }, - useInputBox: function (provider) { - - var input_area = $('#openid_input_area'); - - var html = ''; - var id = 'openid_username'; - var value = ''; - var label = provider['label']; - var style = ''; - - if (label) { - html = '

' + label + '

'; - } - if (provider['name'] == 'OpenID') { - id = this.input_id; - value = 'http://'; - style = 'background:#FFF url('+this.img_path+'openid-inputicon.gif) no-repeat scroll 0 50%; padding-left:18px;'; - } - html += '' + - ''; - - input_area.empty(); - input_area.append(html); - - $('#'+id).focus(); - }, - setDemoMode: function (demoMode) { - this.demo = demoMode; - }, - setAjaxHandler: function (ajaxFunction) { - this.ajaxHandler = ajaxFunction; - } -}; diff --git a/underlays/openid-selector/ikiwiki/openid/jquery.js b/underlays/openid-selector/ikiwiki/openid/jquery.js new file mode 100644 index 000000000..82b98e1d7 --- /dev/null +++ b/underlays/openid-selector/ikiwiki/openid/jquery.js @@ -0,0 +1,32 @@ +/* + * jQuery 1.2.6 - New Wave Javascript + * + * Copyright (c) 2008 John Resig (jquery.com) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $ + * $Rev: 5685 $ + */ +(function(){var _jQuery=window.jQuery,_$=window.$;var jQuery=window.jQuery=window.$=function(selector,context){return new jQuery.fn.init(selector,context);};var quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,isSimple=/^.[^:#\[\.]*$/,undefined;jQuery.fn=jQuery.prototype={init:function(selector,context){selector=selector||document;if(selector.nodeType){this[0]=selector;this.length=1;return this;}if(typeof selector=="string"){var match=quickExpr.exec(selector);if(match&&(match[1]||!context)){if(match[1])selector=jQuery.clean([match[1]],context);else{var elem=document.getElementById(match[3]);if(elem){if(elem.id!=match[3])return jQuery().find(selector);return jQuery(elem);}selector=[];}}else +return jQuery(context).find(selector);}else if(jQuery.isFunction(selector))return jQuery(document)[jQuery.fn.ready?"ready":"load"](selector);return this.setArray(jQuery.makeArray(selector));},jquery:"1.2.6",size:function(){return this.length;},length:0,get:function(num){return num==undefined?jQuery.makeArray(this):this[num];},pushStack:function(elems){var ret=jQuery(elems);ret.prevObject=this;return ret;},setArray:function(elems){this.length=0;Array.prototype.push.apply(this,elems);return this;},each:function(callback,args){return jQuery.each(this,callback,args);},index:function(elem){var ret=-1;return jQuery.inArray(elem&&elem.jquery?elem[0]:elem,this);},attr:function(name,value,type){var options=name;if(name.constructor==String)if(value===undefined)return this[0]&&jQuery[type||"attr"](this[0],name);else{options={};options[name]=value;}return this.each(function(i){for(name in options)jQuery.attr(type?this.style:this,name,jQuery.prop(this,options[name],type,i,name));});},css:function(key,value){if((key=='width'||key=='height')&&parseFloat(value)<0)value=undefined;return this.attr(key,value,"curCSS");},text:function(text){if(typeof text!="object"&&text!=null)return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(text));var ret="";jQuery.each(text||this,function(){jQuery.each(this.childNodes,function(){if(this.nodeType!=8)ret+=this.nodeType!=1?this.nodeValue:jQuery.fn.text([this]);});});return ret;},wrapAll:function(html){if(this[0])jQuery(html,this[0].ownerDocument).clone().insertBefore(this[0]).map(function(){var elem=this;while(elem.firstChild)elem=elem.firstChild;return elem;}).append(this);return this;},wrapInner:function(html){return this.each(function(){jQuery(this).contents().wrapAll(html);});},wrap:function(html){return this.each(function(){jQuery(this).wrapAll(html);});},append:function(){return this.domManip(arguments,true,false,function(elem){if(this.nodeType==1)this.appendChild(elem);});},prepend:function(){return this.domManip(arguments,true,true,function(elem){if(this.nodeType==1)this.insertBefore(elem,this.firstChild);});},before:function(){return this.domManip(arguments,false,false,function(elem){this.parentNode.insertBefore(elem,this);});},after:function(){return this.domManip(arguments,false,true,function(elem){this.parentNode.insertBefore(elem,this.nextSibling);});},end:function(){return this.prevObject||jQuery([]);},find:function(selector){var elems=jQuery.map(this,function(elem){return jQuery.find(selector,elem);});return this.pushStack(/[^+>] [^+>]/.test(selector)||selector.indexOf("..")>-1?jQuery.unique(elems):elems);},clone:function(events){var ret=this.map(function(){if(jQuery.browser.msie&&!jQuery.isXMLDoc(this)){var clone=this.cloneNode(true),container=document.createElement("div");container.appendChild(clone);return jQuery.clean([container.innerHTML])[0];}else +return this.cloneNode(true);});var clone=ret.find("*").andSelf().each(function(){if(this[expando]!=undefined)this[expando]=null;});if(events===true)this.find("*").andSelf().each(function(i){if(this.nodeType==3)return;var events=jQuery.data(this,"events");for(var type in events)for(var handler in events[type])jQuery.event.add(clone[i],type,events[type][handler],events[type][handler].data);});return ret;},filter:function(selector){return this.pushStack(jQuery.isFunction(selector)&&jQuery.grep(this,function(elem,i){return selector.call(elem,i);})||jQuery.multiFilter(selector,this));},not:function(selector){if(selector.constructor==String)if(isSimple.test(selector))return this.pushStack(jQuery.multiFilter(selector,this,true));else +selector=jQuery.multiFilter(selector,this);var isArrayLike=selector.length&&selector[selector.length-1]!==undefined&&!selector.nodeType;return this.filter(function(){return isArrayLike?jQuery.inArray(this,selector)<0:this!=selector;});},add:function(selector){return this.pushStack(jQuery.unique(jQuery.merge(this.get(),typeof selector=='string'?jQuery(selector):jQuery.makeArray(selector))));},is:function(selector){return!!selector&&jQuery.multiFilter(selector,this).length>0;},hasClass:function(selector){return this.is("."+selector);},val:function(value){if(value==undefined){if(this.length){var elem=this[0];if(jQuery.nodeName(elem,"select")){var index=elem.selectedIndex,values=[],options=elem.options,one=elem.type=="select-one";if(index<0)return null;for(var i=one?index:0,max=one?index+1:options.length;i=0||jQuery.inArray(this.name,value)>=0);else if(jQuery.nodeName(this,"select")){var values=jQuery.makeArray(value);jQuery("option",this).each(function(){this.selected=(jQuery.inArray(this.value,values)>=0||jQuery.inArray(this.text,values)>=0);});if(!values.length)this.selectedIndex=-1;}else +this.value=value;});},html:function(value){return value==undefined?(this[0]?this[0].innerHTML:null):this.empty().append(value);},replaceWith:function(value){return this.after(value).remove();},eq:function(i){return this.slice(i,i+1);},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments));},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem);}));},andSelf:function(){return this.add(this.prevObject);},data:function(key,value){var parts=key.split(".");parts[1]=parts[1]?"."+parts[1]:"";if(value===undefined){var data=this.triggerHandler("getData"+parts[1]+"!",[parts[0]]);if(data===undefined&&this.length)data=jQuery.data(this[0],key);return data===undefined&&parts[1]?this.data(parts[0]):data;}else +return this.trigger("setData"+parts[1]+"!",[parts[0],value]).each(function(){jQuery.data(this,key,value);});},removeData:function(key){return this.each(function(){jQuery.removeData(this,key);});},domManip:function(args,table,reverse,callback){var clone=this.length>1,elems;return this.each(function(){if(!elems){elems=jQuery.clean(args,this.ownerDocument);if(reverse)elems.reverse();}var obj=this;if(table&&jQuery.nodeName(this,"table")&&jQuery.nodeName(elems[0],"tr"))obj=this.getElementsByTagName("tbody")[0]||this.appendChild(this.ownerDocument.createElement("tbody"));var scripts=jQuery([]);jQuery.each(elems,function(){var elem=clone?jQuery(this).clone(true)[0]:this;if(jQuery.nodeName(elem,"script"))scripts=scripts.add(elem);else{if(elem.nodeType==1)scripts=scripts.add(jQuery("script",elem).remove());callback.call(obj,elem);}});scripts.each(evalScript);});}};jQuery.fn.init.prototype=jQuery.fn;function evalScript(i,elem){if(elem.src)jQuery.ajax({url:elem.src,async:false,dataType:"script"});else +jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");if(elem.parentNode)elem.parentNode.removeChild(elem);}function now(){return+new Date;}jQuery.extend=jQuery.fn.extend=function(){var target=arguments[0]||{},i=1,length=arguments.length,deep=false,options;if(target.constructor==Boolean){deep=target;target=arguments[1]||{};i=2;}if(typeof target!="object"&&typeof target!="function")target={};if(length==i){target=this;--i;}for(;i-1;}},swap:function(elem,options,callback){var old={};for(var name in options){old[name]=elem.style[name];elem.style[name]=options[name];}callback.call(elem);for(var name in options)elem.style[name]=old[name];},css:function(elem,name,force){if(name=="width"||name=="height"){var val,props={position:"absolute",visibility:"hidden",display:"block"},which=name=="width"?["Left","Right"]:["Top","Bottom"];function getWH(){val=name=="width"?elem.offsetWidth:elem.offsetHeight;var padding=0,border=0;jQuery.each(which,function(){padding+=parseFloat(jQuery.curCSS(elem,"padding"+this,true))||0;border+=parseFloat(jQuery.curCSS(elem,"border"+this+"Width",true))||0;});val-=Math.round(padding+border);}if(jQuery(elem).is(":visible"))getWH();else +jQuery.swap(elem,props,getWH);return Math.max(0,val);}return jQuery.curCSS(elem,name,force);},curCSS:function(elem,name,force){var ret,style=elem.style;function color(elem){if(!jQuery.browser.safari)return false;var ret=defaultView.getComputedStyle(elem,null);return!ret||ret.getPropertyValue("color")=="";}if(name=="opacity"&&jQuery.browser.msie){ret=jQuery.attr(style,"opacity");return ret==""?"1":ret;}if(jQuery.browser.opera&&name=="display"){var save=style.outline;style.outline="0 solid black";style.outline=save;}if(name.match(/float/i))name=styleFloat;if(!force&&style&&style[name])ret=style[name];else if(defaultView.getComputedStyle){if(name.match(/float/i))name="float";name=name.replace(/([A-Z])/g,"-$1").toLowerCase();var computedStyle=defaultView.getComputedStyle(elem,null);if(computedStyle&&!color(elem))ret=computedStyle.getPropertyValue(name);else{var swap=[],stack=[],a=elem,i=0;for(;a&&color(a);a=a.parentNode)stack.unshift(a);for(;i]*?)\/>/g,function(all,front,tag){return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?all:front+">";});var tags=jQuery.trim(elem).toLowerCase(),div=context.createElement("div");var wrap=!tags.indexOf("",""]||!tags.indexOf("",""]||tags.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!tags.indexOf("",""]||(!tags.indexOf("",""]||!tags.indexOf("",""]||jQuery.browser.msie&&[1,"div
","
"]||[0,"",""];div.innerHTML=wrap[1]+elem+wrap[2];while(wrap[0]--)div=div.lastChild;if(jQuery.browser.msie){var tbody=!tags.indexOf(""&&tags.indexOf("=0;--j)if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length)tbody[j].parentNode.removeChild(tbody[j]);if(/^\s/.test(elem))div.insertBefore(context.createTextNode(elem.match(/^\s*/)[0]),div.firstChild);}elem=jQuery.makeArray(div.childNodes);}if(elem.length===0&&(!jQuery.nodeName(elem,"form")&&!jQuery.nodeName(elem,"select")))return;if(elem[0]==undefined||jQuery.nodeName(elem,"form")||elem.options)ret.push(elem);else +ret=jQuery.merge(ret,elem);});return ret;},attr:function(elem,name,value){if(!elem||elem.nodeType==3||elem.nodeType==8)return undefined;var notxml=!jQuery.isXMLDoc(elem),set=value!==undefined,msie=jQuery.browser.msie;name=notxml&&jQuery.props[name]||name;if(elem.tagName){var special=/href|src|style/.test(name);if(name=="selected"&&jQuery.browser.safari)elem.parentNode.selectedIndex;if(name in elem&¬xml&&!special){if(set){if(name=="type"&&jQuery.nodeName(elem,"input")&&elem.parentNode)throw"type property can't be changed";elem[name]=value;}if(jQuery.nodeName(elem,"form")&&elem.getAttributeNode(name))return elem.getAttributeNode(name).nodeValue;return elem[name];}if(msie&¬xml&&name=="style")return jQuery.attr(elem.style,"cssText",value);if(set)elem.setAttribute(name,""+value);var attr=msie&¬xml&&special?elem.getAttribute(name,2):elem.getAttribute(name);return attr===null?undefined:attr;}if(msie&&name=="opacity"){if(set){elem.zoom=1;elem.filter=(elem.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(value)+''=="NaN"?"":"alpha(opacity="+value*100+")");}return elem.filter&&elem.filter.indexOf("opacity=")>=0?(parseFloat(elem.filter.match(/opacity=([^)]*)/)[1])/100)+'':"";}name=name.replace(/-([a-z])/ig,function(all,letter){return letter.toUpperCase();});if(set)elem[name]=value;return elem[name];},trim:function(text){return(text||"").replace(/^\s+|\s+$/g,"");},makeArray:function(array){var ret=[];if(array!=null){var i=array.length;if(i==null||array.split||array.setInterval||array.call)ret[0]=array;else +while(i)ret[--i]=array[i];}return ret;},inArray:function(elem,array){for(var i=0,length=array.length;i*",this).remove();while(this.firstChild)this.removeChild(this.firstChild);}},function(name,fn){jQuery.fn[name]=function(){return this.each(fn,arguments);};});jQuery.each(["Height","Width"],function(i,name){var type=name.toLowerCase();jQuery.fn[type]=function(size){return this[0]==window?jQuery.browser.opera&&document.body["client"+name]||jQuery.browser.safari&&window["inner"+name]||document.compatMode=="CSS1Compat"&&document.documentElement["client"+name]||document.body["client"+name]:this[0]==document?Math.max(Math.max(document.body["scroll"+name],document.documentElement["scroll"+name]),Math.max(document.body["offset"+name],document.documentElement["offset"+name])):size==undefined?(this.length?jQuery.css(this[0],type):null):this.css(type,size.constructor==String?size:size+"px");};});function num(elem,prop){return elem[0]&&parseInt(jQuery.curCSS(elem[0],prop,true),10)||0;}var chars=jQuery.browser.safari&&parseInt(jQuery.browser.version)<417?"(?:[\\w*_-]|\\\\.)":"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",quickChild=new RegExp("^>\\s*("+chars+"+)"),quickID=new RegExp("^("+chars+"+)(#)("+chars+"+)"),quickClass=new RegExp("^([#.]?)("+chars+"*)");jQuery.extend({expr:{"":function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},"#":function(a,i,m){return a.getAttribute("id")==m[2];},":":{lt:function(a,i,m){return im[3]-0;},nth:function(a,i,m){return m[3]-0==i;},eq:function(a,i,m){return m[3]-0==i;},first:function(a,i){return i==0;},last:function(a,i,m,r){return i==r.length-1;},even:function(a,i){return i%2==0;},odd:function(a,i){return i%2;},"first-child":function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},"last-child":function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},"only-child":function(a){return!jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},parent:function(a){return a.firstChild;},empty:function(a){return!a.firstChild;},contains:function(a,i,m){return(a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},visible:function(a){return"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},hidden:function(a){return"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},enabled:function(a){return!a.disabled;},disabled:function(a){return a.disabled;},checked:function(a){return a.checked;},selected:function(a){return a.selected||jQuery.attr(a,"selected");},text:function(a){return"text"==a.type;},radio:function(a){return"radio"==a.type;},checkbox:function(a){return"checkbox"==a.type;},file:function(a){return"file"==a.type;},password:function(a){return"password"==a.type;},submit:function(a){return"submit"==a.type;},image:function(a){return"image"==a.type;},reset:function(a){return"reset"==a.type;},button:function(a){return"button"==a.type||jQuery.nodeName(a,"button");},input:function(a){return/input|select|textarea|button/i.test(a.nodeName);},has:function(a,i,m){return jQuery.find(m[3],a).length;},header:function(a){return/h\d/i.test(a.nodeName);},animated:function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}}},parse:[/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,new RegExp("^([:.#]*)("+chars+"+)")],multiFilter:function(expr,elems,not){var old,cur=[];while(expr&&expr!=old){old=expr;var f=jQuery.filter(expr,elems,not);expr=f.t.replace(/^\s*,\s*/,"");cur=not?elems=f.r:jQuery.merge(cur,f.r);}return cur;},find:function(t,context){if(typeof t!="string")return[t];if(context&&context.nodeType!=1&&context.nodeType!=9)return[];context=context||document;var ret=[context],done=[],last,nodeName;while(t&&last!=t){var r=[];last=t;t=jQuery.trim(t);var foundToken=false,re=quickChild,m=re.exec(t);if(m){nodeName=m[1].toUpperCase();for(var i=0;ret[i];i++)for(var c=ret[i].firstChild;c;c=c.nextSibling)if(c.nodeType==1&&(nodeName=="*"||c.nodeName.toUpperCase()==nodeName))r.push(c);ret=r;t=t.replace(re,"");if(t.indexOf(" ")==0)continue;foundToken=true;}else{re=/^([>+~])\s*(\w*)/i;if((m=re.exec(t))!=null){r=[];var merge={};nodeName=m[2].toUpperCase();m=m[1];for(var j=0,rl=ret.length;j=0;if(!not&&pass||not&&!pass)tmp.push(r[i]);}return tmp;},filter:function(t,r,not){var last;while(t&&t!=last){last=t;var p=jQuery.parse,m;for(var i=0;p[i];i++){m=p[i].exec(t);if(m){t=t.substring(m[0].length);m[2]=m[2].replace(/\\/g,"");break;}}if(!m)break;if(m[1]==":"&&m[2]=="not")r=isSimple.test(m[3])?jQuery.filter(m[3],r,true).r:jQuery(r).not(m[3]);else if(m[1]==".")r=jQuery.classFilter(r,m[2],not);else if(m[1]=="["){var tmp=[],type=m[3];for(var i=0,rl=r.length;i=0)^not)tmp.push(a);}r=tmp;}else if(m[1]==":"&&m[2]=="nth-child"){var merge={},tmp=[],test=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(m[3]=="even"&&"2n"||m[3]=="odd"&&"2n+1"||!/\D/.test(m[3])&&"0n+"+m[3]||m[3]),first=(test[1]+(test[2]||1))-0,last=test[3]-0;for(var i=0,rl=r.length;i=0)add=true;if(add^not)tmp.push(node);}r=tmp;}else{var fn=jQuery.expr[m[1]];if(typeof fn=="object")fn=fn[m[2]];if(typeof fn=="string")fn=eval("false||function(a,i){return "+fn+";}");r=jQuery.grep(r,function(elem,i){return fn(elem,i,m,r);},not);}}return{r:r,t:t};},dir:function(elem,dir){var matched=[],cur=elem[dir];while(cur&&cur!=document){if(cur.nodeType==1)matched.push(cur);cur=cur[dir];}return matched;},nth:function(cur,result,dir,elem){result=result||1;var num=0;for(;cur;cur=cur[dir])if(cur.nodeType==1&&++num==result)break;return cur;},sibling:function(n,elem){var r=[];for(;n;n=n.nextSibling){if(n.nodeType==1&&n!=elem)r.push(n);}return r;}});jQuery.event={add:function(elem,types,handler,data){if(elem.nodeType==3||elem.nodeType==8)return;if(jQuery.browser.msie&&elem.setInterval)elem=window;if(!handler.guid)handler.guid=this.guid++;if(data!=undefined){var fn=handler;handler=this.proxy(fn,function(){return fn.apply(this,arguments);});handler.data=data;}var events=jQuery.data(elem,"events")||jQuery.data(elem,"events",{}),handle=jQuery.data(elem,"handle")||jQuery.data(elem,"handle",function(){if(typeof jQuery!="undefined"&&!jQuery.event.triggered)return jQuery.event.handle.apply(arguments.callee.elem,arguments);});handle.elem=elem;jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];handler.type=parts[1];var handlers=events[type];if(!handlers){handlers=events[type]={};if(!jQuery.event.special[type]||jQuery.event.special[type].setup.call(elem)===false){if(elem.addEventListener)elem.addEventListener(type,handle,false);else if(elem.attachEvent)elem.attachEvent("on"+type,handle);}}handlers[handler.guid]=handler;jQuery.event.global[type]=true;});elem=null;},guid:1,global:{},remove:function(elem,types,handler){if(elem.nodeType==3||elem.nodeType==8)return;var events=jQuery.data(elem,"events"),ret,index;if(events){if(types==undefined||(typeof types=="string"&&types.charAt(0)=="."))for(var type in events)this.remove(elem,type+(types||""));else{if(types.type){handler=types.handler;types=types.type;}jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];if(events[type]){if(handler)delete events[type][handler.guid];else +for(handler in events[type])if(!parts[1]||events[type][handler].type==parts[1])delete events[type][handler];for(ret in events[type])break;if(!ret){if(!jQuery.event.special[type]||jQuery.event.special[type].teardown.call(elem)===false){if(elem.removeEventListener)elem.removeEventListener(type,jQuery.data(elem,"handle"),false);else if(elem.detachEvent)elem.detachEvent("on"+type,jQuery.data(elem,"handle"));}ret=null;delete events[type];}}});}for(ret in events)break;if(!ret){var handle=jQuery.data(elem,"handle");if(handle)handle.elem=null;jQuery.removeData(elem,"events");jQuery.removeData(elem,"handle");}}},trigger:function(type,data,elem,donative,extra){data=jQuery.makeArray(data);if(type.indexOf("!")>=0){type=type.slice(0,-1);var exclusive=true;}if(!elem){if(this.global[type])jQuery("*").add([window,document]).trigger(type,data);}else{if(elem.nodeType==3||elem.nodeType==8)return undefined;var val,ret,fn=jQuery.isFunction(elem[type]||null),event=!data[0]||!data[0].preventDefault;if(event){data.unshift({type:type,target:elem,preventDefault:function(){},stopPropagation:function(){},timeStamp:now()});data[0][expando]=true;}data[0].type=type;if(exclusive)data[0].exclusive=true;var handle=jQuery.data(elem,"handle");if(handle)val=handle.apply(elem,data);if((!fn||(jQuery.nodeName(elem,'a')&&type=="click"))&&elem["on"+type]&&elem["on"+type].apply(elem,data)===false)val=false;if(event)data.shift();if(extra&&jQuery.isFunction(extra)){ret=extra.apply(elem,val==null?data:data.concat(val));if(ret!==undefined)val=ret;}if(fn&&donative!==false&&val!==false&&!(jQuery.nodeName(elem,'a')&&type=="click")){this.triggered=true;try{elem[type]();}catch(e){}}this.triggered=false;}return val;},handle:function(event){var val,ret,namespace,all,handlers;event=arguments[0]=jQuery.event.fix(event||window.event);namespace=event.type.split(".");event.type=namespace[0];namespace=namespace[1];all=!namespace&&!event.exclusive;handlers=(jQuery.data(this,"events")||{})[event.type];for(var j in handlers){var handler=handlers[j];if(all||handler.type==namespace){event.handler=handler;event.data=handler.data;ret=handler.apply(this,arguments);if(val!==false)val=ret;if(ret===false){event.preventDefault();event.stopPropagation();}}}return val;},fix:function(event){if(event[expando]==true)return event;var originalEvent=event;event={originalEvent:originalEvent};var props="altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which".split(" ");for(var i=props.length;i;i--)event[props[i]]=originalEvent[props[i]];event[expando]=true;event.preventDefault=function(){if(originalEvent.preventDefault)originalEvent.preventDefault();originalEvent.returnValue=false;};event.stopPropagation=function(){if(originalEvent.stopPropagation)originalEvent.stopPropagation();originalEvent.cancelBubble=true;};event.timeStamp=event.timeStamp||now();if(!event.target)event.target=event.srcElement||document;if(event.target.nodeType==3)event.target=event.target.parentNode;if(!event.relatedTarget&&event.fromElement)event.relatedTarget=event.fromElement==event.target?event.toElement:event.fromElement;if(event.pageX==null&&event.clientX!=null){var doc=document.documentElement,body=document.body;event.pageX=event.clientX+(doc&&doc.scrollLeft||body&&body.scrollLeft||0)-(doc.clientLeft||0);event.pageY=event.clientY+(doc&&doc.scrollTop||body&&body.scrollTop||0)-(doc.clientTop||0);}if(!event.which&&((event.charCode||event.charCode===0)?event.charCode:event.keyCode))event.which=event.charCode||event.keyCode;if(!event.metaKey&&event.ctrlKey)event.metaKey=event.ctrlKey;if(!event.which&&event.button)event.which=(event.button&1?1:(event.button&2?3:(event.button&4?2:0)));return event;},proxy:function(fn,proxy){proxy.guid=fn.guid=fn.guid||proxy.guid||this.guid++;return proxy;},special:{ready:{setup:function(){bindReady();return;},teardown:function(){return;}},mouseenter:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseover",jQuery.event.special.mouseenter.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseover",jQuery.event.special.mouseenter.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseenter";return jQuery.event.handle.apply(this,arguments);}},mouseleave:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseout",jQuery.event.special.mouseleave.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseout",jQuery.event.special.mouseleave.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseleave";return jQuery.event.handle.apply(this,arguments);}}}};jQuery.fn.extend({bind:function(type,data,fn){return type=="unload"?this.one(type,data,fn):this.each(function(){jQuery.event.add(this,type,fn||data,fn&&data);});},one:function(type,data,fn){var one=jQuery.event.proxy(fn||data,function(event){jQuery(this).unbind(event,one);return(fn||data).apply(this,arguments);});return this.each(function(){jQuery.event.add(this,type,one,fn&&data);});},unbind:function(type,fn){return this.each(function(){jQuery.event.remove(this,type,fn);});},trigger:function(type,data,fn){return this.each(function(){jQuery.event.trigger(type,data,this,true,fn);});},triggerHandler:function(type,data,fn){return this[0]&&jQuery.event.trigger(type,data,this[0],false,fn);},toggle:function(fn){var args=arguments,i=1;while(i=0){var selector=url.slice(off,url.length);url=url.slice(0,off);}callback=callback||function(){};var type="GET";if(params)if(jQuery.isFunction(params)){callback=params;params=null;}else{params=jQuery.param(params);type="POST";}var self=this;jQuery.ajax({url:url,type:type,dataType:"html",data:params,complete:function(res,status){if(status=="success"||status=="notmodified")self.html(selector?jQuery("
").append(res.responseText.replace(//g,"")).find(selector):res.responseText);self.each(callback,[res.responseText,status,res]);}});return this;},serialize:function(){return jQuery.param(this.serializeArray());},serializeArray:function(){return this.map(function(){return jQuery.nodeName(this,"form")?jQuery.makeArray(this.elements):this;}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type));}).map(function(i,elem){var val=jQuery(this).val();return val==null?null:val.constructor==Array?jQuery.map(val,function(val,i){return{name:elem.name,value:val};}):{name:elem.name,value:val};}).get();}});jQuery.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(i,o){jQuery.fn[o]=function(f){return this.bind(o,f);};});var jsc=now();jQuery.extend({get:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data=null;}return jQuery.ajax({type:"GET",url:url,data:data,success:callback,dataType:type});},getScript:function(url,callback){return jQuery.get(url,null,callback,"script");},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json");},post:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data={};}return jQuery.ajax({type:"POST",url:url,data:data,success:callback,dataType:type});},ajaxSetup:function(settings){jQuery.extend(jQuery.ajaxSettings,settings);},ajaxSettings:{url:location.href,global:true,type:"GET",timeout:0,contentType:"application/x-www-form-urlencoded",processData:true,async:true,data:null,username:null,password:null,accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(s){s=jQuery.extend(true,s,jQuery.extend(true,{},jQuery.ajaxSettings,s));var jsonp,jsre=/=\?(&|$)/g,status,data,type=s.type.toUpperCase();if(s.data&&s.processData&&typeof s.data!="string")s.data=jQuery.param(s.data);if(s.dataType=="jsonp"){if(type=="GET"){if(!s.url.match(jsre))s.url+=(s.url.match(/\?/)?"&":"?")+(s.jsonp||"callback")+"=?";}else if(!s.data||!s.data.match(jsre))s.data=(s.data?s.data+"&":"")+(s.jsonp||"callback")+"=?";s.dataType="json";}if(s.dataType=="json"&&(s.data&&s.data.match(jsre)||s.url.match(jsre))){jsonp="jsonp"+jsc++;if(s.data)s.data=(s.data+"").replace(jsre,"="+jsonp+"$1");s.url=s.url.replace(jsre,"="+jsonp+"$1");s.dataType="script";window[jsonp]=function(tmp){data=tmp;success();complete();window[jsonp]=undefined;try{delete window[jsonp];}catch(e){}if(head)head.removeChild(script);};}if(s.dataType=="script"&&s.cache==null)s.cache=false;if(s.cache===false&&type=="GET"){var ts=now();var ret=s.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+ts+"$2");s.url=ret+((ret==s.url)?(s.url.match(/\?/)?"&":"?")+"_="+ts:"");}if(s.data&&type=="GET"){s.url+=(s.url.match(/\?/)?"&":"?")+s.data;s.data=null;}if(s.global&&!jQuery.active++)jQuery.event.trigger("ajaxStart");var remote=/^(?:\w+:)?\/\/([^\/?#]+)/;if(s.dataType=="script"&&type=="GET"&&remote.test(s.url)&&remote.exec(s.url)[1]!=location.host){var head=document.getElementsByTagName("head")[0];var script=document.createElement("script");script.src=s.url;if(s.scriptCharset)script.charset=s.scriptCharset;if(!jsonp){var done=false;script.onload=script.onreadystatechange=function(){if(!done&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){done=true;success();complete();head.removeChild(script);}};}head.appendChild(script);return undefined;}var requestDone=false;var xhr=window.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest();if(s.username)xhr.open(type,s.url,s.async,s.username,s.password);else +xhr.open(type,s.url,s.async);try{if(s.data)xhr.setRequestHeader("Content-Type",s.contentType);if(s.ifModified)xhr.setRequestHeader("If-Modified-Since",jQuery.lastModified[s.url]||"Thu, 01 Jan 1970 00:00:00 GMT");xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Accept",s.dataType&&s.accepts[s.dataType]?s.accepts[s.dataType]+", */*":s.accepts._default);}catch(e){}if(s.beforeSend&&s.beforeSend(xhr,s)===false){s.global&&jQuery.active--;xhr.abort();return false;}if(s.global)jQuery.event.trigger("ajaxSend",[xhr,s]);var onreadystatechange=function(isTimeout){if(!requestDone&&xhr&&(xhr.readyState==4||isTimeout=="timeout")){requestDone=true;if(ival){clearInterval(ival);ival=null;}status=isTimeout=="timeout"&&"timeout"||!jQuery.httpSuccess(xhr)&&"error"||s.ifModified&&jQuery.httpNotModified(xhr,s.url)&&"notmodified"||"success";if(status=="success"){try{data=jQuery.httpData(xhr,s.dataType,s.dataFilter);}catch(e){status="parsererror";}}if(status=="success"){var modRes;try{modRes=xhr.getResponseHeader("Last-Modified");}catch(e){}if(s.ifModified&&modRes)jQuery.lastModified[s.url]=modRes;if(!jsonp)success();}else +jQuery.handleError(s,xhr,status);complete();if(s.async)xhr=null;}};if(s.async){var ival=setInterval(onreadystatechange,13);if(s.timeout>0)setTimeout(function(){if(xhr){xhr.abort();if(!requestDone)onreadystatechange("timeout");}},s.timeout);}try{xhr.send(s.data);}catch(e){jQuery.handleError(s,xhr,null,e);}if(!s.async)onreadystatechange();function success(){if(s.success)s.success(data,status);if(s.global)jQuery.event.trigger("ajaxSuccess",[xhr,s]);}function complete(){if(s.complete)s.complete(xhr,status);if(s.global)jQuery.event.trigger("ajaxComplete",[xhr,s]);if(s.global&&!--jQuery.active)jQuery.event.trigger("ajaxStop");}return xhr;},handleError:function(s,xhr,status,e){if(s.error)s.error(xhr,status,e);if(s.global)jQuery.event.trigger("ajaxError",[xhr,s,e]);},active:0,httpSuccess:function(xhr){try{return!xhr.status&&location.protocol=="file:"||(xhr.status>=200&&xhr.status<300)||xhr.status==304||xhr.status==1223||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpNotModified:function(xhr,url){try{var xhrRes=xhr.getResponseHeader("Last-Modified");return xhr.status==304||xhrRes==jQuery.lastModified[url]||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpData:function(xhr,type,filter){var ct=xhr.getResponseHeader("content-type"),xml=type=="xml"||!type&&ct&&ct.indexOf("xml")>=0,data=xml?xhr.responseXML:xhr.responseText;if(xml&&data.documentElement.tagName=="parsererror")throw"parsererror";if(filter)data=filter(data,type);if(type=="script")jQuery.globalEval(data);if(type=="json")data=eval("("+data+")");return data;},param:function(a){var s=[];if(a.constructor==Array||a.jquery)jQuery.each(a,function(){s.push(encodeURIComponent(this.name)+"="+encodeURIComponent(this.value));});else +for(var j in a)if(a[j]&&a[j].constructor==Array)jQuery.each(a[j],function(){s.push(encodeURIComponent(j)+"="+encodeURIComponent(this));});else +s.push(encodeURIComponent(j)+"="+encodeURIComponent(jQuery.isFunction(a[j])?a[j]():a[j]));return s.join("&").replace(/%20/g,"+");}});jQuery.fn.extend({show:function(speed,callback){return speed?this.animate({height:"show",width:"show",opacity:"show"},speed,callback):this.filter(":hidden").each(function(){this.style.display=this.oldblock||"";if(jQuery.css(this,"display")=="none"){var elem=jQuery("<"+this.tagName+" />").appendTo("body");this.style.display=elem.css("display");if(this.style.display=="none")this.style.display="block";elem.remove();}}).end();},hide:function(speed,callback){return speed?this.animate({height:"hide",width:"hide",opacity:"hide"},speed,callback):this.filter(":visible").each(function(){this.oldblock=this.oldblock||jQuery.css(this,"display");this.style.display="none";}).end();},_toggle:jQuery.fn.toggle,toggle:function(fn,fn2){return jQuery.isFunction(fn)&&jQuery.isFunction(fn2)?this._toggle.apply(this,arguments):fn?this.animate({height:"toggle",width:"toggle",opacity:"toggle"},fn,fn2):this.each(function(){jQuery(this)[jQuery(this).is(":hidden")?"show":"hide"]();});},slideDown:function(speed,callback){return this.animate({height:"show"},speed,callback);},slideUp:function(speed,callback){return this.animate({height:"hide"},speed,callback);},slideToggle:function(speed,callback){return this.animate({height:"toggle"},speed,callback);},fadeIn:function(speed,callback){return this.animate({opacity:"show"},speed,callback);},fadeOut:function(speed,callback){return this.animate({opacity:"hide"},speed,callback);},fadeTo:function(speed,to,callback){return this.animate({opacity:to},speed,callback);},animate:function(prop,speed,easing,callback){var optall=jQuery.speed(speed,easing,callback);return this[optall.queue===false?"each":"queue"](function(){if(this.nodeType!=1)return false;var opt=jQuery.extend({},optall),p,hidden=jQuery(this).is(":hidden"),self=this;for(p in prop){if(prop[p]=="hide"&&hidden||prop[p]=="show"&&!hidden)return opt.complete.call(this);if(p=="height"||p=="width"){opt.display=jQuery.css(this,"display");opt.overflow=this.style.overflow;}}if(opt.overflow!=null)this.style.overflow="hidden";opt.curAnim=jQuery.extend({},prop);jQuery.each(prop,function(name,val){var e=new jQuery.fx(self,opt,name);if(/toggle|show|hide/.test(val))e[val=="toggle"?hidden?"show":"hide":val](prop);else{var parts=val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),start=e.cur(true)||0;if(parts){var end=parseFloat(parts[2]),unit=parts[3]||"px";if(unit!="px"){self.style[name]=(end||1)+unit;start=((end||1)/e.cur(true))*start;self.style[name]=start+unit;}if(parts[1])end=((parts[1]=="-="?-1:1)*end)+start;e.custom(start,end,unit);}else +e.custom(start,val,"");}});return true;});},queue:function(type,fn){if(jQuery.isFunction(type)||(type&&type.constructor==Array)){fn=type;type="fx";}if(!type||(typeof type=="string"&&!fn))return queue(this[0],type);return this.each(function(){if(fn.constructor==Array)queue(this,type,fn);else{queue(this,type).push(fn);if(queue(this,type).length==1)fn.call(this);}});},stop:function(clearQueue,gotoEnd){var timers=jQuery.timers;if(clearQueue)this.queue([]);this.each(function(){for(var i=timers.length-1;i>=0;i--)if(timers[i].elem==this){if(gotoEnd)timers[i](true);timers.splice(i,1);}});if(!gotoEnd)this.dequeue();return this;}});var queue=function(elem,type,array){if(elem){type=type||"fx";var q=jQuery.data(elem,type+"queue");if(!q||array)q=jQuery.data(elem,type+"queue",jQuery.makeArray(array));}return q;};jQuery.fn.dequeue=function(type){type=type||"fx";return this.each(function(){var q=queue(this,type);q.shift();if(q.length)q[0].call(this);});};jQuery.extend({speed:function(speed,easing,fn){var opt=speed&&speed.constructor==Object?speed:{complete:fn||!fn&&easing||jQuery.isFunction(speed)&&speed,duration:speed,easing:fn&&easing||easing&&easing.constructor!=Function&&easing};opt.duration=(opt.duration&&opt.duration.constructor==Number?opt.duration:jQuery.fx.speeds[opt.duration])||jQuery.fx.speeds.def;opt.old=opt.complete;opt.complete=function(){if(opt.queue!==false)jQuery(this).dequeue();if(jQuery.isFunction(opt.old))opt.old.call(this);};return opt;},easing:{linear:function(p,n,firstNum,diff){return firstNum+diff*p;},swing:function(p,n,firstNum,diff){return((-Math.cos(p*Math.PI)/2)+0.5)*diff+firstNum;}},timers:[],timerId:null,fx:function(elem,options,prop){this.options=options;this.elem=elem;this.prop=prop;if(!options.orig)options.orig={};}});jQuery.fx.prototype={update:function(){if(this.options.step)this.options.step.call(this.elem,this.now,this);(jQuery.fx.step[this.prop]||jQuery.fx.step._default)(this);if(this.prop=="height"||this.prop=="width")this.elem.style.display="block";},cur:function(force){if(this.elem[this.prop]!=null&&this.elem.style[this.prop]==null)return this.elem[this.prop];var r=parseFloat(jQuery.css(this.elem,this.prop,force));return r&&r>-10000?r:parseFloat(jQuery.curCSS(this.elem,this.prop))||0;},custom:function(from,to,unit){this.startTime=now();this.start=from;this.end=to;this.unit=unit||this.unit||"px";this.now=this.start;this.pos=this.state=0;this.update();var self=this;function t(gotoEnd){return self.step(gotoEnd);}t.elem=this.elem;jQuery.timers.push(t);if(jQuery.timerId==null){jQuery.timerId=setInterval(function(){var timers=jQuery.timers;for(var i=0;ithis.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var done=true;for(var i in this.options.curAnim)if(this.options.curAnim[i]!==true)done=false;if(done){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(jQuery.css(this.elem,"display")=="none")this.elem.style.display="block";}if(this.options.hide)this.elem.style.display="none";if(this.options.hide||this.options.show)for(var p in this.options.curAnim)jQuery.attr(this.elem.style,p,this.options.orig[p]);}if(done)this.options.complete.call(this.elem);return false;}else{var n=t-this.startTime;this.state=n/this.options.duration;this.pos=jQuery.easing[this.options.easing||(jQuery.easing.swing?"swing":"linear")](this.state,n,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update();}return true;}};jQuery.extend(jQuery.fx,{speeds:{slow:600,fast:200,def:400},step:{scrollLeft:function(fx){fx.elem.scrollLeft=fx.now;},scrollTop:function(fx){fx.elem.scrollTop=fx.now;},opacity:function(fx){jQuery.attr(fx.elem.style,"opacity",fx.now);},_default:function(fx){fx.elem.style[fx.prop]=fx.now+fx.unit;}}});jQuery.fn.offset=function(){var left=0,top=0,elem=this[0],results;if(elem)with(jQuery.browser){var parent=elem.parentNode,offsetChild=elem,offsetParent=elem.offsetParent,doc=elem.ownerDocument,safari2=safari&&parseInt(version)<522&&!/adobeair/i.test(userAgent),css=jQuery.curCSS,fixed=css(elem,"position")=="fixed";if(elem.getBoundingClientRect){var box=elem.getBoundingClientRect();add(box.left+Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),box.top+Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));add(-doc.documentElement.clientLeft,-doc.documentElement.clientTop);}else{add(elem.offsetLeft,elem.offsetTop);while(offsetParent){add(offsetParent.offsetLeft,offsetParent.offsetTop);if(mozilla&&!/^t(able|d|h)$/i.test(offsetParent.tagName)||safari&&!safari2)border(offsetParent);if(!fixed&&css(offsetParent,"position")=="fixed")fixed=true;offsetChild=/^body$/i.test(offsetParent.tagName)?offsetChild:offsetParent;offsetParent=offsetParent.offsetParent;}while(parent&&parent.tagName&&!/^body|html$/i.test(parent.tagName)){if(!/^inline|table.*$/i.test(css(parent,"display")))add(-parent.scrollLeft,-parent.scrollTop);if(mozilla&&css(parent,"overflow")!="visible")border(parent);parent=parent.parentNode;}if((safari2&&(fixed||css(offsetChild,"position")=="absolute"))||(mozilla&&css(offsetChild,"position")!="absolute"))add(-doc.body.offsetLeft,-doc.body.offsetTop);if(fixed)add(Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));}results={top:top,left:left};}function border(elem){add(jQuery.curCSS(elem,"borderLeftWidth",true),jQuery.curCSS(elem,"borderTopWidth",true));}function add(l,t){left+=parseInt(l,10)||0;top+=parseInt(t,10)||0;}return results;};jQuery.fn.extend({position:function(){var left=0,top=0,results;if(this[0]){var offsetParent=this.offsetParent(),offset=this.offset(),parentOffset=/^body|html$/i.test(offsetParent[0].tagName)?{top:0,left:0}:offsetParent.offset();offset.top-=num(this,'marginTop');offset.left-=num(this,'marginLeft');parentOffset.top+=num(offsetParent,'borderTopWidth');parentOffset.left+=num(offsetParent,'borderLeftWidth');results={top:offset.top-parentOffset.top,left:offset.left-parentOffset.left};}return results;},offsetParent:function(){var offsetParent=this[0].offsetParent;while(offsetParent&&(!/^body|html$/i.test(offsetParent.tagName)&&jQuery.css(offsetParent,'position')=='static'))offsetParent=offsetParent.offsetParent;return jQuery(offsetParent);}});jQuery.each(['Left','Top'],function(i,name){var method='scroll'+name;jQuery.fn[method]=function(val){if(!this[0])return;return val!=undefined?this.each(function(){this==window||this==document?window.scrollTo(!i?val:jQuery(window).scrollLeft(),i?val:jQuery(window).scrollTop()):this[method]=val;}):this[0]==window||this[0]==document?self[i?'pageYOffset':'pageXOffset']||jQuery.boxModel&&document.documentElement[method]||document.body[method]:this[0][method];};});jQuery.each(["Height","Width"],function(i,name){var tl=i?"Left":"Top",br=i?"Right":"Bottom";jQuery.fn["inner"+name]=function(){return this[name.toLowerCase()]()+num(this,"padding"+tl)+num(this,"padding"+br);};jQuery.fn["outer"+name]=function(margin){return this["inner"+name]()+num(this,"border"+tl+"Width")+num(this,"border"+br+"Width")+(margin?num(this,"margin"+tl)+num(this,"margin"+br):0);};});})(); \ No newline at end of file diff --git a/underlays/openid-selector/ikiwiki/openid/openid-jquery.js b/underlays/openid-selector/ikiwiki/openid/openid-jquery.js new file mode 100644 index 000000000..f39fff842 --- /dev/null +++ b/underlays/openid-selector/ikiwiki/openid/openid-jquery.js @@ -0,0 +1,240 @@ +/* +Simple OpenID Plugin +http://code.google.com/p/openid-selector/ + +This code is licenced under the New BSD License. +*/ + +var providers_large = { + google: { + name: 'Google', + url: 'https://www.google.com/accounts/o8/id' + }, + yahoo: { + name: 'Yahoo', + url: 'http://me.yahoo.com/' + }, + aol: { + name: 'AOL', + label: 'Enter your AOL screenname.', + url: 'http://openid.aol.com/{username}' + }, + verisign: { + name: 'Verisign', + label: 'Your Verisign username', + url: 'http://{username}.pip.verisignlabs.com/' + }, + openid: { + name: 'OpenID', + label: 'Enter your OpenID.', + url: null + } +}; +var providers_small = { + myopenid: { + name: 'MyOpenID', + label: 'Enter your MyOpenID username.', + url: 'http://{username}.myopenid.com/' + }, + livejournal: { + name: 'LiveJournal', + label: 'Enter your Livejournal username.', + url: 'http://{username}.livejournal.com/' + }, + flickr: { + name: 'Flickr', + label: 'Enter your Flickr username.', + url: 'http://flickr.com/{username}/' + }, + technorati: { + name: 'Technorati', + label: 'Enter your Technorati username.', + url: 'http://technorati.com/people/technorati/{username}/' + }, + wordpress: { + name: 'Wordpress', + label: 'Enter your Wordpress.com username.', + url: 'http://{username}.wordpress.com/' + }, + blogger: { + name: 'Blogger', + label: 'Your Blogger account', + url: 'http://{username}.blogspot.com/' + }, + vidoop: { + name: 'Vidoop', + label: 'Your Vidoop username', + url: 'http://{username}.myvidoop.com/' + }, + claimid: { + name: 'ClaimID', + label: 'Your ClaimID username', + url: 'http://claimid.com/{username}' + } +}; +var providers = $.extend({}, providers_large, providers_small); + +var openid = { + + demo: false, + ajaxHandler: null, + cookie_expires: 6*30, // 6 months. + cookie_name: 'openid_provider', + cookie_path: '/', + + img_path: 'images/', + + input_id: null, + provider_url: null, + provider_id: null, + + init: function(input_id) { + + var openid_btns = $('#openid_btns'); + + this.input_id = input_id; + + $('#openid_choice').show(); + $('#openid_input_area').empty(); + + // add box for each provider + for (id in providers_large) { + + openid_btns.append(this.getBoxHTML(providers_large[id], 'large', '.gif')); + } + if (providers_small) { + openid_btns.append('
'); + + for (id in providers_small) { + + openid_btns.append(this.getBoxHTML(providers_small[id], 'small', '.ico')); + } + } + + $('#openid_form').submit(this.submit); + + var box_id = this.readCookie(); + if (box_id) { + this.signin(box_id, true); + } + }, + getBoxHTML: function(provider, box_size, image_ext) { + + var box_id = provider["name"].toLowerCase(); + return ''; + + }, + /* Provider image click */ + signin: function(box_id, onload) { + + var provider = providers[box_id]; + if (! provider) { + return; + } + + this.highlight(box_id); + this.setCookie(box_id); + + this.provider_id = box_id; + this.provider_url = provider['url']; + + // prompt user for input? + if (provider['label']) { + this.useInputBox(provider); + } else { + $('#openid_input_area').empty(); + if (! onload) { + $('#openid_form').submit(); + } + } + }, + /* Sign-in button click */ + submit: function() { + + var url = openid.provider_url; + if (url) { + url = url.replace('{username}', $('#openid_username').val()); + openid.setOpenIdUrl(url); + } + if(openid.ajaxHandler) { + openid.ajaxHandler(openid.provider_id, document.getElementById(openid.input_id).value); + return false; + } + if(openid.demo) { + alert("In client demo mode. Normally would have submitted OpenID:\r\n" + document.getElementById(openid.input_id).value); + return false; + } + return true; + }, + setOpenIdUrl: function (url) { + + var hidden = $('#'+this.input_id); + if (hidden.length > 0) { + hidden.value = url; + } else { + $('#openid_form').append(''); + } + }, + highlight: function (box_id) { + + // remove previous highlight. + var highlight = $('#openid_highlight'); + if (highlight) { + highlight.replaceWith($('#openid_highlight a')[0]); + } + // add new highlight. + $('.'+box_id).wrap('
'); + }, + setCookie: function (value) { + + var date = new Date(); + date.setTime(date.getTime()+(this.cookie_expires*24*60*60*1000)); + var expires = "; expires="+date.toGMTString(); + + document.cookie = this.cookie_name+"="+value+expires+"; path=" + this.cookie_path; + }, + readCookie: function () { + var nameEQ = this.cookie_name + "="; + var ca = document.cookie.split(';'); + for(var i=0;i < ca.length;i++) { + var c = ca[i]; + while (c.charAt(0)==' ') c = c.substring(1,c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); + } + return null; + }, + useInputBox: function (provider) { + + var input_area = $('#openid_input_area'); + + var html = ''; + var id = 'openid_username'; + var value = ''; + var label = provider['label']; + var style = ''; + + if (label) { + html = '

' + label + '

'; + } + if (provider['name'] == 'OpenID') { + id = this.input_id; + value = 'http://'; + style = 'background:#FFF url('+this.img_path+'openid-inputicon.gif) no-repeat scroll 0 50%; padding-left:18px;'; + } + html += '' + + ''; + + input_area.empty(); + input_area.append(html); + + $('#'+id).focus(); + }, + setDemoMode: function (demoMode) { + this.demo = demoMode; + }, + setAjaxHandler: function (ajaxFunction) { + this.ajaxHandler = ajaxFunction; + } +}; -- cgit v1.2.3 From ecdfd1b8644bc926db008054ab6192e18351afed Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 23 Jun 2010 17:35:21 -0400 Subject: rcs_commit and rcs_commit_staged api changes Using named parameters for these is overdue. Passing the session in a parameter instead of passing username and IP separately will later allow storing other session info, like username or part of the email. Note that these functions are not part of the exported API, and the prototype change will catch (most) skew, so I am not changing API versions. Any third-party plugins that call them will need updated though. --- IkiWiki.pm | 4 +- IkiWiki/Plugin/attachment.pm | 10 ++-- IkiWiki/Plugin/autoindex.pm | 4 +- IkiWiki/Plugin/bzr.pm | 43 +++++++++-------- IkiWiki/Plugin/comments.pm | 14 +++--- IkiWiki/Plugin/cvs.pm | 59 ++++++++++++----------- IkiWiki/Plugin/darcs.pm | 75 +++++++++++++++--------------- IkiWiki/Plugin/editpage.pm | 10 ++-- IkiWiki/Plugin/git.pm | 52 +++++++++++++-------- IkiWiki/Plugin/mercurial.pm | 32 ++++++------- IkiWiki/Plugin/monotone.pm | 85 +++++++++++++++------------------- IkiWiki/Plugin/norcs.pm | 6 +-- IkiWiki/Plugin/poll.pm | 10 ++-- IkiWiki/Plugin/remove.pm | 7 +-- IkiWiki/Plugin/rename.pm | 5 +- IkiWiki/Plugin/svn.pm | 59 ++++++++++++----------- IkiWiki/Plugin/tag.pm | 2 +- IkiWiki/Plugin/tla.pm | 34 ++++++++------ debian/changelog | 5 +- doc/plugins/write.mdwn | 16 ++++--- ikiwiki-calendar.in | 2 +- ikiwiki.spec | 2 +- po/ikiwiki.pot | 108 +++++++++++++++++++++---------------------- t/bazaar.t | 25 ++++++++-- t/cvs.t | 6 ++- t/git.t | 12 +++-- t/mercurial.t | 11 ++++- t/svn.t | 6 ++- 28 files changed, 380 insertions(+), 324 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 203da3ba2..35b38df46 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -1840,11 +1840,11 @@ sub rcs_prepedit ($) { $hooks{rcs}{rcs_prepedit}{call}->(@_); } -sub rcs_commit ($$$;$$) { +sub rcs_commit (@) { $hooks{rcs}{rcs_commit}{call}->(@_); } -sub rcs_commit_staged ($$$) { +sub rcs_commit_staged (@) { $hooks{rcs}{rcs_commit_staged}{call}->(@_); } diff --git a/IkiWiki/Plugin/attachment.pm b/IkiWiki/Plugin/attachment.pm index 216e00b3d..ee105a170 100644 --- a/IkiWiki/Plugin/attachment.pm +++ b/IkiWiki/Plugin/attachment.pm @@ -183,10 +183,12 @@ sub formbuilder (@) { if ($config{rcs}) { IkiWiki::rcs_add($filename); IkiWiki::disable_commit_hook(); - IkiWiki::rcs_commit($filename, gettext("attachment upload"), - IkiWiki::rcs_prepedit($filename), - $session->param("name"), - $session->remote_addr()); + IkiWiki::rcs_commit( + file => $filename, + message => gettext("attachment upload"), + token => IkiWiki::rcs_prepedit($filename), + session => $session, + ); IkiWiki::enable_commit_hook(); IkiWiki::rcs_update(); } diff --git a/IkiWiki/Plugin/autoindex.pm b/IkiWiki/Plugin/autoindex.pm index c3eb53300..11595e217 100644 --- a/IkiWiki/Plugin/autoindex.pm +++ b/IkiWiki/Plugin/autoindex.pm @@ -117,8 +117,8 @@ sub refresh () { } if ($config{rcs}) { IkiWiki::rcs_commit_staged( - gettext("automatic index generation"), - undef, undef); + message => gettext("automatic index generation"), + ); IkiWiki::enable_commit_hook(); } } diff --git a/IkiWiki/Plugin/bzr.pm b/IkiWiki/Plugin/bzr.pm index 44ab9a86a..562d5d389 100644 --- a/IkiWiki/Plugin/bzr.pm +++ b/IkiWiki/Plugin/bzr.pm @@ -123,8 +123,13 @@ sub rcs_prepedit ($) { return ""; } -sub bzr_author ($$) { - my ($user, $ipaddr) = @_; +sub bzr_author ($) { + my $session=shift; + + return unless defined $session; + + my $user=$session->param("name"); + my $ipaddr=$session->remote_addr(); if (defined $user) { return IkiWiki::possibly_foolish_untaint($user); @@ -137,18 +142,19 @@ sub bzr_author ($$) { } } -sub rcs_commit ($$$;$$$) { - my ($file, $message, $rcstoken, $user, $ipaddr, $emailuser) = @_; +sub rcs_commit (@) { + my %params=@_; - $user = bzr_author($user, $ipaddr); + my $user=bzr_author($params{session}); - $message = IkiWiki::possibly_foolish_untaint($message); - if (! length $message) { - $message = "no message given"; + $params{message} = IkiWiki::possibly_foolish_untaint($params{message}); + if (! length $params{message}) { + $params{message} = "no message given"; } - my @cmdline = ("bzr", "commit", "--quiet", "-m", $message, "--author", $user, - $config{srcdir}."/".$file); + my @cmdline = ("bzr", "commit", "--quiet", "-m", $params{message}, + (defined $user ? ("--author", $user) : ()), + $config{srcdir}."/".$params{file}); if (system(@cmdline) != 0) { warn "'@cmdline' failed: $!"; } @@ -156,19 +162,18 @@ sub rcs_commit ($$$;$$$) { return undef; # success } -sub rcs_commit_staged ($$$;$) { - # Commits all staged changes. Changes can be staged using rcs_add, - # rcs_remove, and rcs_rename. - my ($message, $user, $ipaddr, $emailuser)=@_; +sub rcs_commit_staged (@) { + my %params=@_; - $user = bzr_author($user, $ipaddr); + my $user=bzr_author($params{session}); - $message = IkiWiki::possibly_foolish_untaint($message); - if (! length $message) { - $message = "no message given"; + $params{message} = IkiWiki::possibly_foolish_untaint($params{message}); + if (! length $params{message}) { + $params{message} = "no message given"; } - my @cmdline = ("bzr", "commit", "--quiet", "-m", $message, "--author", $user, + my @cmdline = ("bzr", "commit", "--quiet", "-m", $params{message}, + (defined $user ? ("--author", $user) : ()), $config{srcdir}); if (system(@cmdline) != 0) { warn "'@cmdline' failed: $!"; diff --git a/IkiWiki/Plugin/comments.pm b/IkiWiki/Plugin/comments.pm index 4770209c9..41c6948e8 100644 --- a/IkiWiki/Plugin/comments.pm +++ b/IkiWiki/Plugin/comments.pm @@ -513,9 +513,10 @@ sub editcomment ($$) { IkiWiki::rcs_add($file); IkiWiki::disable_commit_hook(); - $conflict = IkiWiki::rcs_commit_staged($message, - $session->param('name'), - $session->remote_addr()); + $conflict = IkiWiki::rcs_commit_staged( + message => $message, + session => $session, + ); IkiWiki::enable_commit_hook(); IkiWiki::rcs_update(); } @@ -603,9 +604,10 @@ sub commentmoderation ($$) { if ($config{rcs} and $config{comments_commit}) { my $message = gettext("Comment moderation"); IkiWiki::disable_commit_hook(); - $conflict=IkiWiki::rcs_commit_staged($message, - $session->param('name'), - $session->remote_addr()); + $conflict=IkiWiki::rcs_commit_staged( + message => $message, + session => $session, + ); IkiWiki::enable_commit_hook(); IkiWiki::rcs_update(); } diff --git a/IkiWiki/Plugin/cvs.pm b/IkiWiki/Plugin/cvs.pm index a9fe162a1..c6687d780 100644 --- a/IkiWiki/Plugin/cvs.pm +++ b/IkiWiki/Plugin/cvs.pm @@ -183,41 +183,47 @@ sub rcs_prepedit ($) { return defined $rev ? $rev : ""; } -sub rcs_commit ($$$;$$$) { +sub commitmessage (@) { + my %params=@_; + + if (defined $params{session}) { + if (defined $params{session}->param("name")) { + return "web commit by ". + $params{session}->param("name"). + (length $params{message} ? ": $params{message}" : ""); + } + elsif (defined $params{session}->remote_addr()) { + return "web commit from ". + $params{session}->remote_addr(). + (length $params{message} ? ": $params{message}" : ""); + } + } + return $params{message}; +} + +sub rcs_commit (@) { # Tries to commit the page; returns undef on _success_ and # a version of the page with the rcs's conflict markers on failure. # The file is relative to the srcdir. - my $file=shift; - my $message=shift; - my $rcstoken=shift; - my $user=shift; - my $ipaddr=shift; - my $emailuser=shift; + my %params=@_; return unless cvs_is_controlling; - if (defined $user) { - $message="web commit by $user".(length $message ? ": $message" : ""); - } - elsif (defined $ipaddr) { - $message="web commit from $ipaddr".(length $message ? ": $message" : ""); - } - # Check to see if the page has been changed by someone # else since rcs_prepedit was called. - my ($oldrev)=$rcstoken=~/^([0-9]+)$/; # untaint - my $rev=cvs_info("Repository revision", "$config{srcdir}/$file"); + my ($oldrev)=$params{token}=~/^([0-9]+)$/; # untaint + my $rev=cvs_info("Repository revision", "$config{srcdir}/$params{file}"); if (defined $rev && defined $oldrev && $rev != $oldrev) { # Merge their changes into the file that we've # changed. - cvs_runcvs('update', $file) || + cvs_runcvs('update', $params{file}) || warn("cvs merge from $oldrev to $rev failed\n"); } if (! cvs_runcvs('commit', '-m', - IkiWiki::possibly_foolish_untaint $message)) { - my $conflict=readfile("$config{srcdir}/$file"); - cvs_runcvs('update', '-C', $file) || + IkiWiki::possibly_foolish_untaint(commitmessage(%params)))) { + my $conflict=readfile("$config{srcdir}/$params{file}"); + cvs_runcvs('update', '-C', $params{file}) || warn("cvs revert failed\n"); return $conflict; } @@ -225,20 +231,13 @@ sub rcs_commit ($$$;$$$) { return undef # success } -sub rcs_commit_staged ($$$;$) { +sub rcs_commit_staged (@) { # Commits all staged changes. Changes can be staged using rcs_add, # rcs_remove, and rcs_rename. - my ($message, $user, $ipaddr, $emailuser)=@_; - - if (defined $user) { - $message="web commit by $user".(length $message ? ": $message" : ""); - } - elsif (defined $ipaddr) { - $message="web commit from $ipaddr".(length $message ? ": $message" : ""); - } + my %params=@_; if (! cvs_runcvs('commit', '-m', - IkiWiki::possibly_foolish_untaint $message)) { + IkiWiki::possibly_foolish_untaint(commitmessage(%params)))) { warn "cvs staged commit failed\n"; return 1; # failure } diff --git a/IkiWiki/Plugin/darcs.pm b/IkiWiki/Plugin/darcs.pm index 345456c01..0dfc8708d 100644 --- a/IkiWiki/Plugin/darcs.pm +++ b/IkiWiki/Plugin/darcs.pm @@ -140,14 +140,31 @@ sub rcs_prepedit ($) { return $rev; } -sub rcs_commit ($$$;$$$) { +sub commitauthor (@) { + my %params=@_; + + my $author="anon\@web"; + if (defined $params{session}) { + if (defined $params{session}->param("name")) { + return $params{session}->param("name").'@web'; + } + elsif (defined $params{session}->remote_addr()) { + return $params{session}->remote_addr().'@web'; + } + } + return 'anon@web'; +} + +sub rcs_commit (@) { # Commit the page. Returns 'undef' on success and a version of the page # with conflict markers on failure. + my %params=@_; - my ($file, $message, $rcstoken, $user, $ipaddr, $emailuser) = @_; + my ($file, $message, $token) = + ($params{file}, $params{message}, $params{token}); # Compute if the "revision" of $file changed. - my $changed = darcs_rev($file) ne $rcstoken; + my $changed = darcs_rev($file) ne $token; # Yes, the following is a bit convoluted. if ($changed) { @@ -155,7 +172,7 @@ sub rcs_commit ($$$;$$$) { rename("$config{srcdir}/$file", "$config{srcdir}/$file.save") or error("failed to rename $file to $file.save: $!"); - # Roll the repository back to $rcstoken. + # Roll the repository back to $token. # TODO. Can we be sure that no changes are lost? I think that # we can, if we make sure that the 'darcs push' below will always @@ -166,37 +183,28 @@ sub rcs_commit ($$$;$$$) { # TODO: 'yes | ...' needed? Doesn't seem so. silentsystem('darcs', "revert", "--repodir", $config{srcdir}, "--all") == 0 || error("'darcs revert' failed"); - # Remove all patches starting at $rcstoken. + # Remove all patches starting at $token. my $child = open(DARCS_OBLITERATE, "|-"); if (! $child) { open(STDOUT, ">/dev/null"); exec('darcs', "obliterate", "--repodir", $config{srcdir}, - "--match", "hash " . $rcstoken) and + "--match", "hash " . $token) and error("'darcs obliterate' failed"); } 1 while print DARCS_OBLITERATE "y"; close(DARCS_OBLITERATE); - # Restore the $rcstoken one. + # Restore the $token one. silentsystem('darcs', "pull", "--quiet", "--repodir", $config{srcdir}, - "--match", "hash " . $rcstoken, "--all") == 0 || + "--match", "hash " . $token, "--all") == 0 || error("'darcs pull' failed"); - # We're back at $rcstoken. Re-install the modified file. + # We're back at $token. Re-install the modified file. rename("$config{srcdir}/$file.save", "$config{srcdir}/$file") or error("failed to rename $file.save to $file: $!"); } # Record the changes. - my $author; - if (defined $user) { - $author = "$user\@web"; - } - elsif (defined $ipaddr) { - $author = "$ipaddr\@web"; - } - else { - $author = "anon\@web"; - } + my $author=commitauthor(%params); if (!defined $message || !length($message)) { $message = "empty message"; } @@ -211,13 +219,13 @@ sub rcs_commit ($$$;$$$) { # If this updating yields any conflicts, we'll record them now to resolve # them. If nothing is recorded, there are no conflicts. - $rcstoken = darcs_rev($file); + $token = darcs_rev($file); # TODO: Use only the first line here, i.e. only the patch name? writefile("$file.log", $config{srcdir}, 'resolve conflicts: ' . $message); silentsystem('darcs', 'record', '--repodir', $config{srcdir}, '--all', '-m', 'resolve conflicts: ' . $message, '--author', $author, $file) == 0 || error("'darcs record' failed"); - my $conflicts = darcs_rev($file) ne $rcstoken; + my $conflicts = darcs_rev($file) ne $token; unlink("$config{srcdir}/$file.log") or error("failed to remove '$file.log'"); @@ -239,25 +247,18 @@ sub rcs_commit ($$$;$$$) { } } -sub rcs_commit_staged ($$$;$) { - my ($message, $user, $ipaddr, $emailuser) = @_; +sub rcs_commit_staged (@) { + my %params=@_; - my $author; - if (defined $user) { - $author = "$user\@web"; - } - elsif (defined $ipaddr) { - $author = "$ipaddr\@web"; - } - else { - $author = "anon\@web"; - } - if (!defined $message || !length($message)) { - $message = "empty message"; + my $author=commitauthor(%params); + if (!defined $params{message} || !length($params{message})) { + $params{message} = "empty message"; } - silentsystem('darcs', "record", "--repodir", $config{srcdir}, "-a", "-A", $author, - "-m", $message) == 0 || error("'darcs record' failed"); + silentsystem('darcs', "record", "--repodir", $config{srcdir}, + "-a", "-A", $author, + "-m", $params{message}, + ) == 0 || error("'darcs record' failed"); # Push the changes to the main repository. silentsystem('darcs', 'push', '--quiet', '--repodir', $config{srcdir}, '--all') == 0 || diff --git a/IkiWiki/Plugin/editpage.pm b/IkiWiki/Plugin/editpage.pm index a8e75121f..1a04a72b5 100644 --- a/IkiWiki/Plugin/editpage.pm +++ b/IkiWiki/Plugin/editpage.pm @@ -401,10 +401,12 @@ sub cgi_editpage ($$) { # signaling to it that it should not try to # do anything. disable_commit_hook(); - $conflict=rcs_commit($file, $message, - $form->field("rcsinfo"), - $session->param("name"), - $session->remote_addr()); + $conflict=rcs_commit( + file => $file, + message => $message, + token => $form->field("rcsinfo"), + session => $session, + ); enable_commit_hook(); rcs_update(); } diff --git a/IkiWiki/Plugin/git.pm b/IkiWiki/Plugin/git.pm index b56f229d7..b02fc118d 100644 --- a/IkiWiki/Plugin/git.pm +++ b/IkiWiki/Plugin/git.pm @@ -464,43 +464,55 @@ sub rcs_prepedit ($) { return git_sha1($file); } -sub rcs_commit ($$$;$$$) { +sub rcs_commit (@) { # Try to commit the page; returns undef on _success_ and # a version of the page with the rcs's conflict markers on # failure. - - my ($file, $message, $rcstoken, $user, $ipaddr, $emailuser) = @_; + my %params=@_; # Check to see if the page has been changed by someone else since # rcs_prepedit was called. - my $cur = git_sha1($file); - my ($prev) = $rcstoken =~ /^($sha1_pattern)$/; # untaint + my $cur = git_sha1($params{file}); + my ($prev) = $params{token} =~ /^($sha1_pattern)$/; # untaint if (defined $cur && defined $prev && $cur ne $prev) { - my $conflict = merge_past($prev, $file, $dummy_commit_msg); + my $conflict = merge_past($prev, $params{file}, $dummy_commit_msg); return $conflict if defined $conflict; } - rcs_add($file); - return rcs_commit_staged($message, $user, $ipaddr); + rcs_add($params{file}); + return rcs_commit_staged( + message => $params{message}, + session => $params{session}, + ); } -sub rcs_commit_staged ($$$;$) { +sub rcs_commit_staged (@) { # Commits all staged changes. Changes can be staged using rcs_add, # rcs_remove, and rcs_rename. - my ($message, $user, $ipaddr, $emailuser)=@_; - - # Set the commit author and email to the web committer. + my %params=@_; + my %env=%ENV; - if (defined $user || defined $ipaddr) { - my $u=encode_utf8(defined $user ? $user : $ipaddr); - $ENV{GIT_AUTHOR_NAME}=$u; - $ENV{GIT_AUTHOR_EMAIL}="$u\@web"; + + if (defined $params{session}) { + # Set the commit author and email based on web session info. + my $u; + if (defined $params{session}->param("name")) { + $u=$params{session}->param("name"); + } + elsif (defined $params{session}->remote_addr()) { + $u=$params{session}->remote_addr(); + } + if (defined $u) { + $u=encode_utf8($u); + $ENV{GIT_AUTHOR_NAME}=$u; + $ENV{GIT_AUTHOR_EMAIL}="$u\@web"; + } } - $message = IkiWiki::possibly_foolish_untaint($message); + $params{message} = IkiWiki::possibly_foolish_untaint($params{message}); my @opts; - if ($message !~ /\S/) { + if ($params{message} !~ /\S/) { # Force git to allow empty commit messages. # (If this version of git supports it.) my ($version)=`git --version` =~ /git version (.*)/; @@ -508,13 +520,13 @@ sub rcs_commit_staged ($$$;$) { push @opts, '--cleanup=verbatim'; } else { - $message.="."; + $params{message}.="."; } } push @opts, '-q'; # git commit returns non-zero if file has not been really changed. # so we should ignore its exit status (hence run_or_non). - if (run_or_non('git', 'commit', @opts, '-m', $message)) { + if (run_or_non('git', 'commit', @opts, '-m', $params{message})) { if (length $config{gitorigin_branch}) { run_or_cry('git', 'push', $config{gitorigin_branch}); } diff --git a/IkiWiki/Plugin/mercurial.pm b/IkiWiki/Plugin/mercurial.pm index 1793ab4bb..edf915ae9 100644 --- a/IkiWiki/Plugin/mercurial.pm +++ b/IkiWiki/Plugin/mercurial.pm @@ -126,26 +126,26 @@ sub rcs_prepedit ($) { return ""; } -sub rcs_commit ($$$;$$$) { - my ($file, $message, $rcstoken, $user, $ipaddr, $emailuser) = @_; +sub rcs_commit (@) { + my %params=@_; - if (defined $user) { - $user = IkiWiki::possibly_foolish_untaint($user); - } - elsif (defined $ipaddr) { - $user = "Anonymous from ".IkiWiki::possibly_foolish_untaint($ipaddr); - } - else { - $user = "Anonymous"; + my $user="Anonymous"; + if (defined $params{session}) { + if (defined $params{session}->param("name")) { + $user = $params{session}->param("name"); + } + elsif (defined $params{session}->remote_addr()) { + $user = "Anonymous from ".$params{session}->remote_addr(); + } } - $message = IkiWiki::possibly_foolish_untaint($message); - if (! length $message) { - $message = "no message given"; + if (! length $params{message}) { + $params{message} = "no message given"; } my @cmdline = ("hg", "-q", "-R", $config{srcdir}, "commit", - "-m", $message, "-u", $user); + "-m", IkiWiki::possibly_foolish_untaint($params{message}), + "-u", IkiWiki::possibly_foolish_untaint($user)); if (system(@cmdline) != 0) { warn "'@cmdline' failed: $!"; } @@ -153,10 +153,10 @@ sub rcs_commit ($$$;$$$) { return undef; # success } -sub rcs_commit_staged ($$$;$) { +sub rcs_commit_staged (@) { # Commits all staged changes. Changes can be staged using rcs_add, # rcs_remove, and rcs_rename. - my ($message, $user, $ipaddr, $emailuser)=@_; + my %params=@_; error("rcs_commit_staged not implemented for mercurial"); # TODO } diff --git a/IkiWiki/Plugin/monotone.pm b/IkiWiki/Plugin/monotone.pm index 55d8039e0..95fbcee76 100644 --- a/IkiWiki/Plugin/monotone.pm +++ b/IkiWiki/Plugin/monotone.pm @@ -293,32 +293,33 @@ sub rcs_prepedit ($) { return get_rev(); } -sub rcs_commit ($$$;$$$) { +sub commitauthor (@) { + my %params=@_; + + if (defined $params{session}) { + if (defined $params{session}->param("name")) { + return "Web user: " . $params{session}->param("name"); + } + elsif (defined $params{session}->remote_addr()) { + return "Web IP: " . $params{session}->remote_addr(); + } + } + return "Web: Anonymous"; +} + + +sub rcs_commit (@) { # Tries to commit the page; returns undef on _success_ and # a version of the page with the rcs's conflict markers on failure. # The file is relative to the srcdir. - my $file=shift; - my $message=shift; - my $rcstoken=shift; - my $user=shift; - my $ipaddr=shift; - my $emailuser=shift; - my $author; + my %params=@_; - if (defined $user) { - $author="Web user: " . $user; - } - elsif (defined $ipaddr) { - $author="Web IP: " . $ipaddr; - } - else { - $author="Web: Anonymous"; - } + my $author=IkiWiki::possibly_foolish_untaint(commitauthor(%params)), chdir $config{srcdir} or error("Cannot chdir to $config{srcdir}: $!"); - my ($oldrev)= $rcstoken=~ m/^($sha1_pattern)$/; # untaint + my ($oldrev) = $params{token} =~ m/^($sha1_pattern)$/; # untaint my $rev = get_rev(); if (defined $rev && defined $oldrev && $rev ne $oldrev) { my $automator = Monotone->new(); @@ -327,8 +328,8 @@ sub rcs_commit ($$$;$$$) { # Something has been committed, has this file changed? my ($out, $err); $automator->setOpts("r", $oldrev, "r", $rev); - ($out, $err) = $automator->call("content_diff", $file); - debug("Problem committing $file") if ($err ne ""); + ($out, $err) = $automator->call("content_diff", $params{file}); + debug("Problem committing $params{file}") if ($err ne ""); my $diff = $out; if ($diff) { @@ -337,11 +338,11 @@ sub rcs_commit ($$$;$$$) { # # first get the contents debug("File changed: forming branch"); - my $newfile=readfile("$config{srcdir}/$file"); + my $newfile=readfile("$config{srcdir}/$params{file}"); # then get the old content ID from the diff - if ($diff !~ m/^---\s$file\s+($sha1_pattern)$/m) { - error("Unable to find previous file ID for $file"); + if ($diff !~ m/^---\s$params{file}\s+($sha1_pattern)$/m) { + error("Unable to find previous file ID for $params{file}"); } my $oldFileID = $1; @@ -352,13 +353,13 @@ sub rcs_commit ($$$;$$$) { my $branch = $1; # then put the new content into the DB (and record the new content ID) - my $newRevID = commit_file_to_new_rev($automator, $file, $oldFileID, $newfile, $oldrev, $branch, $author, $message); + my $newRevID = commit_file_to_new_rev($automator, $params{file}, $oldFileID, $newfile, $oldrev, $branch, $author, $params{message}); $automator->close(); # if we made it to here then the file has been committed... revert the local copy - if (system("mtn", "--root=$config{mtnrootdir}", "revert", $file) != 0) { - debug("Unable to revert $file after merge on conflicted commit!"); + if (system("mtn", "--root=$config{mtnrootdir}", "revert", $params{file}) != 0) { + debug("Unable to revert $params{file} after merge on conflicted commit!"); } debug("Divergence created! Attempting auto-merge."); @@ -407,7 +408,7 @@ sub rcs_commit ($$$;$$$) { # for cleanup note, this relies on the fact # that ikiwiki seems to call rcs_prepedit() # again after we return - return readfile("$config{srcdir}/$file"); + return readfile("$config{srcdir}/$params{file}"); } return undef; } @@ -419,11 +420,12 @@ sub rcs_commit ($$$;$$$) { if (system("mtn", "--root=$config{mtnrootdir}", "commit", "--quiet", "--author", $author, "--key", $config{mtnkey}, "-m", - IkiWiki::possibly_foolish_untaint($message), $file) != 0) { + IkiWiki::possibly_foolish_untaint($params{message}), + $params{file}) != 0) { debug("Traditional commit failed! Returning data as conflict."); - my $conflict=readfile("$config{srcdir}/$file"); + my $conflict=readfile("$config{srcdir}/$params{file}"); if (system("mtn", "--root=$config{mtnrootdir}", "revert", - "--quiet", $file) != 0) { + "--quiet", $params{file}) != 0) { debug("monotone revert failed"); } return $conflict; @@ -439,32 +441,21 @@ sub rcs_commit ($$$;$$$) { return undef # success } -sub rcs_commit_staged ($$$;$) { +sub rcs_commit_staged (@) { # Commits all staged changes. Changes can be staged using rcs_add, # rcs_remove, and rcs_rename. - my ($message, $user, $ipaddr, $emailuser)=@_; - + my %params=@_; + # Note - this will also commit any spurious changes that happen to be # lying around in the working copy. There shouldn't be any, but... chdir $config{srcdir} or error("Cannot chdir to $config{srcdir}: $!"); - my $author; - - if (defined $user) { - $author="Web user: " . $user; - } - elsif (defined $ipaddr) { - $author="Web IP: " . $ipaddr; - } - else { - $author="Web: Anonymous"; - } - if (system("mtn", "--root=$config{mtnrootdir}", "commit", "--quiet", - "--author", $author, "--key", $config{mtnkey}, "-m", - IkiWiki::possibly_foolish_untaint($message)) != 0) { + "--author", IkiWiki::possibly_foolish_untaint(commitauthor(%params)), + "--key", $config{mtnkey}, "-m", + IkiWiki::possibly_foolish_untaint($params{message})) != 0) { error("Monotone commit failed"); } } diff --git a/IkiWiki/Plugin/norcs.pm b/IkiWiki/Plugin/norcs.pm index 5131a1502..a3bb6240e 100644 --- a/IkiWiki/Plugin/norcs.pm +++ b/IkiWiki/Plugin/norcs.pm @@ -38,13 +38,11 @@ sub rcs_prepedit ($) { return "" } -sub rcs_commit ($$$;$$$) { - my ($file, $message, $rcstoken, $user, $ipaddr, $emailuser) = @_; +sub rcs_commit (@) { return undef # success } -sub rcs_commit_staged ($$$;$) { - my ($message, $user, $ipaddr, $emailuser)=@_; +sub rcs_commit_staged (@) { return undef # success } diff --git a/IkiWiki/Plugin/poll.pm b/IkiWiki/Plugin/poll.pm index e50efa5e0..b333e2cdc 100644 --- a/IkiWiki/Plugin/poll.pm +++ b/IkiWiki/Plugin/poll.pm @@ -134,10 +134,12 @@ sub sessioncgi ($$) { $oldchoice=$session->param($choice_param); if ($config{rcs}) { IkiWiki::disable_commit_hook(); - IkiWiki::rcs_commit($pagesources{$page}, "poll vote ($choice)", - IkiWiki::rcs_prepedit($pagesources{$page}), - $session->param("name"), - $session->remote_addr()); + IkiWiki::rcs_commit( + file => $pagesources{$page}, + message => "poll vote ($choice)", + token => IkiWiki::rcs_prepedit($pagesources{$page}), + session => $session, + ); IkiWiki::enable_commit_hook(); IkiWiki::rcs_update(); } diff --git a/IkiWiki/Plugin/remove.pm b/IkiWiki/Plugin/remove.pm index b664cbf74..95f148183 100644 --- a/IkiWiki/Plugin/remove.pm +++ b/IkiWiki/Plugin/remove.pm @@ -213,9 +213,10 @@ sub sessioncgi ($$) { foreach my $file (@files) { IkiWiki::rcs_remove($file); } - IkiWiki::rcs_commit_staged(gettext("removed"), - $session->param("name"), - $session->remote_addr()); + IkiWiki::rcs_commit_staged( + message => gettext("removed"), + session => $session, + ); IkiWiki::enable_commit_hook(); IkiWiki::rcs_update(); } diff --git a/IkiWiki/Plugin/rename.pm b/IkiWiki/Plugin/rename.pm index 977c09e67..61d39d4b5 100644 --- a/IkiWiki/Plugin/rename.pm +++ b/IkiWiki/Plugin/rename.pm @@ -349,9 +349,8 @@ sub sessioncgi ($$) { $pagesources{$rename->{src}}=$rename->{destfile}; } IkiWiki::rcs_commit_staged( - sprintf(gettext("rename %s to %s"), $srcfile, $destfile), - $session->param("name"), - $session->remote_addr(), + message => sprintf(gettext("rename %s to %s"), $srcfile, $destfile), + session => $session, ) if $config{rcs}; # Then link fixups. diff --git a/IkiWiki/Plugin/svn.pm b/IkiWiki/Plugin/svn.pm index ffacb8cf9..f1e608408 100644 --- a/IkiWiki/Plugin/svn.pm +++ b/IkiWiki/Plugin/svn.pm @@ -144,44 +144,50 @@ sub rcs_prepedit ($) { } } -sub rcs_commit ($$$;$$$) { +sub commitmessage (@) { + my %params=@_; + + if (defined $params{session}) { + if (defined $params{session}->param("name")) { + return "web commit by ". + $params{session}->param("name"). + (length $params{message} ? ": $params{message}" : ""); + } + elsif (defined $params{session}->remote_addr()) { + return "web commit from ". + $params{session}->remote_addr(). + (length $params{message} ? ": $params{message}" : ""); + } + } + return $params{message}; +} + +sub rcs_commit (@) { # Tries to commit the page; returns undef on _success_ and # a version of the page with the rcs's conflict markers on failure. # The file is relative to the srcdir. - my $file=shift; - my $message=shift; - my $rcstoken=shift; - my $user=shift; - my $ipaddr=shift; - my $emailuser=shift; - - if (defined $user) { - $message="web commit by $user".(length $message ? ": $message" : ""); - } - elsif (defined $ipaddr) { - $message="web commit from $ipaddr".(length $message ? ": $message" : ""); - } + my %params=@_; if (-d "$config{srcdir}/.svn") { # Check to see if the page has been changed by someone # else since rcs_prepedit was called. - my ($oldrev)=$rcstoken=~/^([0-9]+)$/; # untaint - my $rev=svn_info("Revision", "$config{srcdir}/$file"); + my ($oldrev)=$params{token}=~/^([0-9]+)$/; # untaint + my $rev=svn_info("Revision", "$config{srcdir}/$params{file}"); if (defined $rev && defined $oldrev && $rev != $oldrev) { # Merge their changes into the file that we've # changed. if (system("svn", "merge", "--quiet", "-r$oldrev:$rev", - "$config{srcdir}/$file", "$config{srcdir}/$file") != 0) { + "$config{srcdir}/$params{file}", "$config{srcdir}/$params{file}") != 0) { warn("svn merge -r$oldrev:$rev failed\n"); } } if (system("svn", "commit", "--quiet", "--encoding", "UTF-8", "-m", - IkiWiki::possibly_foolish_untaint($message), + IkiWiki::possibly_foolish_untaint(commitmessage(%params)), $config{srcdir}) != 0) { - my $conflict=readfile("$config{srcdir}/$file"); - if (system("svn", "revert", "--quiet", "$config{srcdir}/$file") != 0) { + my $conflict=readfile("$config{srcdir}/$params{file}"); + if (system("svn", "revert", "--quiet", "$config{srcdir}/$params{file}") != 0) { warn("svn revert failed\n"); } return $conflict; @@ -190,21 +196,14 @@ sub rcs_commit ($$$;$$$) { return undef # success } -sub rcs_commit_staged ($$$;$) { +sub rcs_commit_staged (@) { # Commits all staged changes. Changes can be staged using rcs_add, # rcs_remove, and rcs_rename. - my ($message, $user, $ipaddr, $emailuser)=@_; - - if (defined $user) { - $message="web commit by $user".(length $message ? ": $message" : ""); - } - elsif (defined $ipaddr) { - $message="web commit from $ipaddr".(length $message ? ": $message" : ""); - } + my %params=@_; if (system("svn", "commit", "--quiet", "--encoding", "UTF-8", "-m", - IkiWiki::possibly_foolish_untaint($message), + IkiWiki::possibly_foolish_untaint(commitmessage(%params)), $config{srcdir}) != 0) { warn("svn commit failed\n"); return 1; # failure diff --git a/IkiWiki/Plugin/tag.pm b/IkiWiki/Plugin/tag.pm index 62f030f4e..55064a9a3 100644 --- a/IkiWiki/Plugin/tag.pm +++ b/IkiWiki/Plugin/tag.pm @@ -90,7 +90,7 @@ sub gentag ($) { if ($config{rcs}) { IkiWiki::disable_commit_hook(); IkiWiki::rcs_add($tagfile); - IkiWiki::rcs_commit_staged($message, undef, undef); + IkiWiki::rcs_commit_staged(message => $message); IkiWiki::enable_commit_hook(); } }); diff --git a/IkiWiki/Plugin/tla.pm b/IkiWiki/Plugin/tla.pm index 80c015e3c..da4385446 100644 --- a/IkiWiki/Plugin/tla.pm +++ b/IkiWiki/Plugin/tla.pm @@ -98,19 +98,23 @@ sub rcs_prepedit ($) { } } -sub rcs_commit ($$$;$$$) { - my $file=shift; - my $message=shift; - my $rcstoken=shift; - my $user=shift; - my $ipaddr=shift; - my $emailuser=shift; - - if (defined $user) { - $message="web commit by $user".(length $message ? ": $message" : ""); - } - elsif (defined $ipaddr) { - $message="web commit from $ipaddr".(length $message ? ": $message" : ""); +sub rcs_commit (@) { + my %params=@_; + + my ($file, $message, $rcstoken)= + ($params{file}, $params{message}, $params{token}); + + if (defined $params{session}) { + if (defined $params{session}->param("name")) { + $message="web commit by ". + $params{session}->param("name"). + (length $message ? ": $message" : ""); + } + elsif (defined $params{session}->remote_addr()) { + $message="web commit from ". + $params{session}->remote_addr(). + (length $message ? ": $message" : ""); + } } if (-d "$config{srcdir}/{arch}") { @@ -140,10 +144,10 @@ sub rcs_commit ($$$;$$$) { return undef # success } -sub rcs_commit_staged ($$$;$) { +sub rcs_commit_staged (@) { # Commits all staged changes. Changes can be staged using rcs_add, # rcs_remove, and rcs_rename. - my ($message, $user, $ipaddr, $emailuser)=@_; + my %params=@_; error("rcs_commit_staged not implemented for tla"); # TODO } diff --git a/debian/changelog b/debian/changelog index 2dfebd0b2..f7452a070 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,9 +1,8 @@ ikiwiki (3.20100624) UNRELEASED; urgency=low * API: Add new optional field usershort to rcs_recentchanges. - * API: rcs_commit and rcs_commit_staged are passed a new parameter - that may contain the username component of the email address of - the user making the commit. + * API: rcs_commit and rcs_commit_staged are now passed named + parameters. -- Joey Hess Wed, 23 Jun 2010 15:30:04 -0400 diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn index ab2934bf1..a2b954bd4 100644 --- a/doc/plugins/write.mdwn +++ b/doc/plugins/write.mdwn @@ -1051,18 +1051,20 @@ token, that will be passed into `rcs_commit` when committing. For example, it might return the current revision ID of the file, and use that information later when merging changes. -#### `rcs_commit($$$;$$$)` +#### `rcs_commit(@)` + +Passed named parameters: `file`, `message`, `token` (from `rcs_prepedit`), +and `session` (optional). -Passed a file, message, token (from `rcs_prepedit`), user, ip address, -and optionally the username component of the committer's email address. Should try to commit the file. Returns `undef` on *success* and a version of the page with the rcs's conflict markers on failure. -#### `rcs_commit_staged($$$;$)` +#### `rcs_commit_staged(@)` + +Passed named parameters: `message`, and `session` (optional). -Passed a message, user, ip address, and optionally the username component of -the committer's email address. Should commit all staged changes. -Returns undef on success, and an error message on failure. +Should commit all staged changes. Returns undef on success, and an +error message on failure. Changes can be staged by calls to `rcs_add`, `rcs_remove`, and `rcs_rename`. diff --git a/ikiwiki-calendar.in b/ikiwiki-calendar.in index 04352b970..60df99855 100755 --- a/ikiwiki-calendar.in +++ b/ikiwiki-calendar.in @@ -59,7 +59,7 @@ foreach my $y ($startyear..$endyear) { } } -IkiWiki::rcs_commit_staged(gettext("calendar update"), undef, undef) +IkiWiki::rcs_commit_staged(message => gettext("calendar update")) if $config{rcs}; exec("ikiwiki", "-setup", $setup, "-refresh"); diff --git a/ikiwiki.spec b/ikiwiki.spec index 9a4bbf6bc..9644fd396 100644 --- a/ikiwiki.spec +++ b/ikiwiki.spec @@ -1,5 +1,5 @@ Name: ikiwiki -Version: 3.20100623 +Version: 3.20100624 Release: 1%{?dist} Summary: A wiki compiler diff --git a/po/ikiwiki.pot b/po/ikiwiki.pot index 437cdbdcc..e50fa0f68 100644 --- a/po/ikiwiki.pot +++ b/po/ikiwiki.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-06-10 15:02-0400\n" +"POT-Creation-Date: 2010-06-23 17:10-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -52,7 +52,7 @@ msgstr "" msgid "You are banned." msgstr "" -#: ../IkiWiki/CGI.pm:426 ../IkiWiki/CGI.pm:427 ../IkiWiki.pm:1316 +#: ../IkiWiki/CGI.pm:426 ../IkiWiki/CGI.pm:427 ../IkiWiki.pm:1317 msgid "Error" msgstr "" @@ -163,19 +163,19 @@ msgstr "" msgid "prohibited by allowed_attachments" msgstr "" -#: ../IkiWiki/Plugin/attachment.pm:141 +#: ../IkiWiki/Plugin/attachment.pm:144 msgid "bad attachment filename" msgstr "" -#: ../IkiWiki/Plugin/attachment.pm:183 +#: ../IkiWiki/Plugin/attachment.pm:188 msgid "attachment upload" msgstr "" -#: ../IkiWiki/Plugin/autoindex.pm:117 +#: ../IkiWiki/Plugin/autoindex.pm:120 msgid "automatic index generation" msgstr "" -#: ../IkiWiki/Plugin/blogspam.pm:109 +#: ../IkiWiki/Plugin/blogspam.pm:110 msgid "" "Sorry, but that looks like spam to blogspam: " @@ -248,19 +248,19 @@ msgstr "" msgid "Added a comment: %s" msgstr "" -#: ../IkiWiki/Plugin/comments.pm:552 ../IkiWiki/Plugin/websetup.pm:272 +#: ../IkiWiki/Plugin/comments.pm:554 ../IkiWiki/Plugin/websetup.pm:268 msgid "you are not logged in as an admin" msgstr "" -#: ../IkiWiki/Plugin/comments.pm:603 +#: ../IkiWiki/Plugin/comments.pm:605 msgid "Comment moderation" msgstr "" -#: ../IkiWiki/Plugin/comments.pm:641 +#: ../IkiWiki/Plugin/comments.pm:645 msgid "comment moderation" msgstr "" -#: ../IkiWiki/Plugin/comments.pm:790 +#: ../IkiWiki/Plugin/comments.pm:802 #, perl-format msgid "%i comment" msgid_plural "%i comments" @@ -270,7 +270,7 @@ msgstr[1] "" #. translators: Here "Comment" is a verb; #. translators: the user clicks on it to #. translators: post a comment. -#: ../IkiWiki/Plugin/comments.pm:800 +#: ../IkiWiki/Plugin/comments.pm:812 msgid "Comment" msgstr "" @@ -305,9 +305,9 @@ msgstr "" msgid "creating %s" msgstr "" -#: ../IkiWiki/Plugin/editpage.pm:312 ../IkiWiki/Plugin/editpage.pm:334 -#: ../IkiWiki/Plugin/editpage.pm:345 ../IkiWiki/Plugin/editpage.pm:390 -#: ../IkiWiki/Plugin/editpage.pm:429 +#: ../IkiWiki/Plugin/editpage.pm:312 ../IkiWiki/Plugin/editpage.pm:332 +#: ../IkiWiki/Plugin/editpage.pm:343 ../IkiWiki/Plugin/editpage.pm:388 +#: ../IkiWiki/Plugin/editpage.pm:430 #, perl-format msgid "editing %s" msgstr "" @@ -320,12 +320,12 @@ msgstr "" msgid "match not specified" msgstr "" -#: ../IkiWiki/Plugin/edittemplate.pm:64 +#: ../IkiWiki/Plugin/edittemplate.pm:70 #, perl-format msgid "edittemplate %s registered for %s" msgstr "" -#: ../IkiWiki/Plugin/edittemplate.pm:125 ../IkiWiki/Plugin/inline.pm:339 +#: ../IkiWiki/Plugin/edittemplate.pm:131 ../IkiWiki/Plugin/inline.pm:339 #: ../IkiWiki/Plugin/template.pm:44 msgid "failed to process template:" msgstr "" @@ -356,18 +356,18 @@ msgstr "" msgid "%s is an attachment, not a page." msgstr "" -#: ../IkiWiki/Plugin/git.pm:687 ../IkiWiki/Plugin/git.pm:705 -#: ../IkiWiki/Receive.pm:130 +#: ../IkiWiki/Plugin/git.pm:724 ../IkiWiki/Plugin/git.pm:742 +#: ../IkiWiki/Receive.pm:129 #, perl-format msgid "you are not allowed to change %s" msgstr "" -#: ../IkiWiki/Plugin/git.pm:727 +#: ../IkiWiki/Plugin/git.pm:764 #, perl-format msgid "you cannot act on a file with mode %s" msgstr "" -#: ../IkiWiki/Plugin/git.pm:731 +#: ../IkiWiki/Plugin/git.pm:768 msgid "you are not allowed to change file modes" msgstr "" @@ -513,7 +513,7 @@ msgstr "" msgid "more" msgstr "" -#: ../IkiWiki/Plugin/openid.pm:58 +#: ../IkiWiki/Plugin/openid.pm:70 #, perl-format msgid "failed to load openid module: " msgstr "" @@ -610,7 +610,7 @@ msgstr "" msgid "rebuilding all pages to fix meta titles" msgstr "" -#: ../IkiWiki/Plugin/po.pm:394 ../IkiWiki/Render.pm:769 +#: ../IkiWiki/Plugin/po.pm:394 ../IkiWiki/Render.pm:784 #, perl-format msgid "building %s" msgstr "" @@ -619,48 +619,48 @@ msgstr "" msgid "updated PO files" msgstr "" -#: ../IkiWiki/Plugin/po.pm:456 +#: ../IkiWiki/Plugin/po.pm:455 msgid "" "Can not remove a translation. If the master page is removed, however, its " "translations will be removed as well." msgstr "" -#: ../IkiWiki/Plugin/po.pm:476 +#: ../IkiWiki/Plugin/po.pm:475 msgid "" "Can not rename a translation. If the master page is renamed, however, its " "translations will be renamed as well." msgstr "" -#: ../IkiWiki/Plugin/po.pm:876 +#: ../IkiWiki/Plugin/po.pm:875 #, perl-format msgid "POT file (%s) does not exist" msgstr "" -#: ../IkiWiki/Plugin/po.pm:890 +#: ../IkiWiki/Plugin/po.pm:889 #, perl-format msgid "failed to copy underlay PO file to %s" msgstr "" -#: ../IkiWiki/Plugin/po.pm:899 +#: ../IkiWiki/Plugin/po.pm:898 #, perl-format msgid "failed to update %s" msgstr "" -#: ../IkiWiki/Plugin/po.pm:905 +#: ../IkiWiki/Plugin/po.pm:904 #, perl-format msgid "failed to copy the POT file to %s" msgstr "" -#: ../IkiWiki/Plugin/po.pm:941 +#: ../IkiWiki/Plugin/po.pm:940 msgid "N/A" msgstr "" -#: ../IkiWiki/Plugin/po.pm:954 +#: ../IkiWiki/Plugin/po.pm:953 #, perl-format msgid "failed to translate %s" msgstr "" -#: ../IkiWiki/Plugin/po.pm:1038 +#: ../IkiWiki/Plugin/po.pm:1037 msgid "removed obsolete PO files" msgstr "" @@ -803,7 +803,7 @@ msgstr "" msgid "Please select the attachments to remove." msgstr "" -#: ../IkiWiki/Plugin/remove.pm:216 +#: ../IkiWiki/Plugin/remove.pm:217 msgid "removed" msgstr "" @@ -853,7 +853,7 @@ msgstr "" msgid "rename %s to %s" msgstr "" -#: ../IkiWiki/Plugin/rename.pm:576 +#: ../IkiWiki/Plugin/rename.pm:577 #, perl-format msgid "update for rename of %s to %s" msgstr "" @@ -988,26 +988,26 @@ msgstr "" msgid "enable %s?" msgstr "" -#: ../IkiWiki/Plugin/websetup.pm:276 +#: ../IkiWiki/Plugin/websetup.pm:272 msgid "setup file for this wiki is not known" msgstr "" -#: ../IkiWiki/Plugin/websetup.pm:292 +#: ../IkiWiki/Plugin/websetup.pm:288 msgid "main" msgstr "" -#: ../IkiWiki/Plugin/websetup.pm:435 +#: ../IkiWiki/Plugin/websetup.pm:431 msgid "" "The configuration changes shown below require a wiki rebuild to take effect." msgstr "" -#: ../IkiWiki/Plugin/websetup.pm:439 +#: ../IkiWiki/Plugin/websetup.pm:435 msgid "" "For the configuration changes shown below to fully take effect, you may need " "to rebuild the wiki." msgstr "" -#: ../IkiWiki/Plugin/websetup.pm:476 +#: ../IkiWiki/Plugin/websetup.pm:472 #, perl-format msgid "Error: %s exited nonzero (%s). Discarding setup changes." msgstr "" @@ -1017,7 +1017,7 @@ msgstr "" msgid "cannot determine id of untrusted committer %s" msgstr "" -#: ../IkiWiki/Receive.pm:86 +#: ../IkiWiki/Receive.pm:85 #, perl-format msgid "bad file name %s" msgstr "" @@ -1034,47 +1034,47 @@ msgid "" "allow this" msgstr "" -#: ../IkiWiki/Render.pm:311 +#: ../IkiWiki/Render.pm:316 #, perl-format msgid "skipping bad filename %s" msgstr "" -#: ../IkiWiki/Render.pm:327 +#: ../IkiWiki/Render.pm:332 #, perl-format msgid "%s has multiple possible source pages" msgstr "" -#: ../IkiWiki/Render.pm:369 +#: ../IkiWiki/Render.pm:372 #, perl-format msgid "querying %s for file creation and modification times.." msgstr "" -#: ../IkiWiki/Render.pm:431 +#: ../IkiWiki/Render.pm:446 #, perl-format msgid "removing obsolete %s" msgstr "" -#: ../IkiWiki/Render.pm:505 +#: ../IkiWiki/Render.pm:520 #, perl-format msgid "building %s, which links to %s" msgstr "" -#: ../IkiWiki/Render.pm:514 +#: ../IkiWiki/Render.pm:529 #, perl-format msgid "removing %s, no longer built by %s" msgstr "" -#: ../IkiWiki/Render.pm:597 ../IkiWiki/Render.pm:679 +#: ../IkiWiki/Render.pm:612 ../IkiWiki/Render.pm:694 #, perl-format msgid "building %s, which depends on %s" msgstr "" -#: ../IkiWiki/Render.pm:692 +#: ../IkiWiki/Render.pm:707 #, perl-format msgid "building %s, to update its backlinks" msgstr "" -#: ../IkiWiki/Render.pm:821 +#: ../IkiWiki/Render.pm:836 #, perl-format msgid "ikiwiki: cannot build %s" msgstr "" @@ -1179,31 +1179,31 @@ msgstr "" msgid "cannot use multiple rcs plugins" msgstr "" -#: ../IkiWiki.pm:606 +#: ../IkiWiki.pm:607 #, perl-format msgid "failed to load external plugin needed for %s plugin: %s" msgstr "" -#: ../IkiWiki.pm:1298 +#: ../IkiWiki.pm:1299 #, perl-format msgid "preprocessing loop detected on %s at depth %i" msgstr "" -#: ../IkiWiki.pm:1993 +#: ../IkiWiki.pm:1994 msgid "yes" msgstr "" -#: ../IkiWiki.pm:2070 +#: ../IkiWiki.pm:2071 #, perl-format msgid "invalid sort type %s" msgstr "" -#: ../IkiWiki.pm:2091 +#: ../IkiWiki.pm:2092 #, perl-format msgid "unknown sort type %s" msgstr "" -#: ../IkiWiki.pm:2227 +#: ../IkiWiki.pm:2228 #, perl-format msgid "cannot match pages: %s" msgstr "" diff --git a/t/bazaar.t b/t/bazaar.t index 0bdd883d5..3e54ec4dc 100755 --- a/t/bazaar.t +++ b/t/bazaar.t @@ -24,11 +24,19 @@ IkiWiki::checkconfig(); system "bzr init $config{srcdir}"; +use CGI::Session; +my $session=CGI::Session->new; +$session->param("name", "Joe User"); + # Web commit my $test1 = readfile("t/test1.mdwn"); writefile('test1.mdwn', $config{srcdir}, $test1); IkiWiki::rcs_add("test1.mdwn"); -IkiWiki::rcs_commit("test1.mdwn", "Added the first page", "moo", "Joe User"); +IkiWiki::rcs_commit( + file => "test1.mdwn", + message => "Added the first page", + token => "moo", + session => $session); my @changes; @changes = IkiWiki::rcs_recentchanges(3); @@ -66,7 +74,10 @@ ok($mtime >= time() - 20); writefile('test3.mdwn', $config{srcdir}, $test1); IkiWiki::rcs_add("test3.mdwn"); IkiWiki::rcs_rename("test3.mdwn", "test4.mdwn"); -IkiWiki::rcs_commit_staged("Added the 4th page", "moo", "Joe User"); +IkiWiki::rcs_commit_staged( + message => "Added the 4th page", + session => $session, +); @changes = IkiWiki::rcs_recentchanges(4); @@ -75,7 +86,10 @@ is($changes[0]{pages}[0]{"page"}, "test4"); ok(mkdir($config{srcdir}."/newdir")); IkiWiki::rcs_rename("test4.mdwn", "newdir/test5.mdwn"); -IkiWiki::rcs_commit_staged("Added the 5th page", "moo", "Joe User"); +IkiWiki::rcs_commit_staged( + message => "Added the 5th page", + session => $session, +); @changes = IkiWiki::rcs_recentchanges(4); @@ -83,6 +97,9 @@ is($#changes, 3); is($changes[0]{pages}[0]{"page"}, "newdir/test5"); IkiWiki::rcs_remove("newdir/test5.mdwn"); -IkiWiki::rcs_commit_staged("Remove the 5th page", "moo", "Joe User"); +IkiWiki::rcs_commit_staged( + message => "Remove the 5th page", + session => $session, +); system "rm -rf $dir"; diff --git a/t/cvs.t b/t/cvs.t index 2808973be..96359ab6e 100755 --- a/t/cvs.t +++ b/t/cvs.t @@ -46,7 +46,11 @@ system "cvs -d $cvsrepo co -d $config{srcdir} ikiwiki >/dev/null"; my $test1 = readfile("t/test1.mdwn"); writefile('test1.mdwn', $config{srcdir}, $test1); IkiWiki::rcs_add("test1.mdwn"); -IkiWiki::rcs_commit("test1.mdwn", "Added the first page", "moo"); +IkiWiki::rcs_commit( + files => "test1.mdwn", + message => "Added the first page", + token => "moo" +); my @changes; @changes = IkiWiki::rcs_recentchanges(3); diff --git a/t/git.t b/t/git.t index f1c24b359..ee778ebf0 100755 --- a/t/git.t +++ b/t/git.t @@ -38,7 +38,11 @@ is($changes[0]{pages}[0]{"page"}, ".gitignore"); my $test1 = readfile("t/test1.mdwn"); writefile('test1.mdwn', $config{srcdir}, $test1); IkiWiki::rcs_add("test1.mdwn"); -IkiWiki::rcs_commit("test1.mdwn", "Added the first page", "moo"); +IkiWiki::rcs_commit( + file => "test1.mdwn", + message => "Added the first page", + token => "moo", +); @changes = IkiWiki::rcs_recentchanges(3); @@ -68,7 +72,7 @@ is($changes[1]{pages}[0]{"page"}, "test1"); writefile('test3.mdwn', $config{srcdir}, $test1); IkiWiki::rcs_add("test3.mdwn"); IkiWiki::rcs_rename("test3.mdwn", "test4.mdwn"); -IkiWiki::rcs_commit_staged("Added the 4th page", "moo", "Joe User"); +IkiWiki::rcs_commit_staged(message => "Added the 4th page"); @changes = IkiWiki::rcs_recentchanges(4); @@ -77,7 +81,7 @@ is($changes[0]{pages}[0]{"page"}, "test4"); ok(mkdir($config{srcdir}."/newdir")); IkiWiki::rcs_rename("test4.mdwn", "newdir/test5.mdwn"); -IkiWiki::rcs_commit_staged("Added the 5th page", "moo", "Joe User"); +IkiWiki::rcs_commit_staged(message => "Added the 5th page"); @changes = IkiWiki::rcs_recentchanges(4); @@ -85,6 +89,6 @@ is($#changes, 3); is($changes[0]{pages}[0]{"page"}, "newdir/test5"); IkiWiki::rcs_remove("newdir/test5.mdwn"); -IkiWiki::rcs_commit_staged("Remove the 5th page", "moo", "Joe User"); +IkiWiki::rcs_commit_staged(message => "Remove the 5th page"); system "rm -rf $dir"; diff --git a/t/mercurial.t b/t/mercurial.t index 954b17526..b64ea8e56 100755 --- a/t/mercurial.t +++ b/t/mercurial.t @@ -22,13 +22,22 @@ $config{srcdir} = "$dir/repo"; IkiWiki::loadplugins(); IkiWiki::checkconfig(); +use CGI::Session; +my $session=CGI::Session->new; +$session->param("name", "Joe User"); + system "hg init $config{srcdir}"; # Web commit my $test1 = readfile("t/test1.mdwn"); writefile('test1.mdwn', $config{srcdir}, $test1); IkiWiki::rcs_add("test1.mdwn"); -IkiWiki::rcs_commit("test1.mdwn", "Added the first page", "moo", "Joe User"); +IkiWiki::rcs_commit( + file => "test1.mdwn", + message => "Added the first page", + token => "moo", + session => $session, +); my @changes; @changes = IkiWiki::rcs_recentchanges(3); diff --git a/t/svn.t b/t/svn.t index 5223b4409..82b71b5fc 100755 --- a/t/svn.t +++ b/t/svn.t @@ -36,7 +36,11 @@ system "svn co file://$svnrepo/trunk $config{srcdir} >/dev/null"; my $test1 = readfile("t/test1.mdwn"); writefile('test1.mdwn', $config{srcdir}, $test1); IkiWiki::rcs_add("test1.mdwn"); -IkiWiki::rcs_commit("test1.mdwn", "Added the first page", "moo"); +IkiWiki::rcs_commit( + file => "test1.mdwn", + message => "Added the first page", + token => "moo", +); my @changes; @changes = IkiWiki::rcs_recentchanges(3); -- cgit v1.2.3 From 1f6ea9a626d9983ce84343e6a73604d58aa1df09 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Fri, 25 Jun 2010 00:30:12 -0400 Subject: clean up messages about unavailable vcs programs --- t/bazaar.t | 7 +++++-- t/cvs.t | 7 +++++-- t/git.t | 7 +++++-- t/mercurial.t | 7 +++++-- t/svn.t | 7 +++++-- 5 files changed, 25 insertions(+), 10 deletions(-) (limited to 't') diff --git a/t/bazaar.t b/t/bazaar.t index 3e54ec4dc..cd840fbe1 100755 --- a/t/bazaar.t +++ b/t/bazaar.t @@ -6,11 +6,14 @@ BEGIN { $dir = "/tmp/ikiwiki-test-bzr.$$"; my $bzr=`which bzr`; chomp $bzr; - if (! -x $bzr || ! mkdir($dir)) { + if (! -x $bzr) { eval q{ - use Test::More skip_all => "bzr not available or could not make test dir" + use Test::More skip_all => "bzr not available" } } + if (! mkdir($dir)) { + die $@; + } } use Test::More tests => 17; diff --git a/t/cvs.t b/t/cvs.t index 96359ab6e..5ed377ed5 100755 --- a/t/cvs.t +++ b/t/cvs.t @@ -8,11 +8,14 @@ BEGIN { chomp $cvs; my $cvsps=`which cvsps`; chomp $cvsps; - if (! -x $cvs || ! -x $cvsps || ! mkdir($dir)) { + if (! -x $cvs || ! -x $cvsps) { eval q{ - use Test::More skip_all => "cvs or cvsps not available or could not make test dir" + use Test::More skip_all => "cvs or cvsps not available" } } + if (! mkdir($dir)) { + die $@; + } foreach my $module ('File::ReadBackwards', 'File::MimeInfo') { eval qq{use $module}; if ($@) { diff --git a/t/git.t b/t/git.t index ee778ebf0..6d847dfb0 100755 --- a/t/git.t +++ b/t/git.t @@ -7,11 +7,14 @@ BEGIN { $dir="/tmp/ikiwiki-test-git.$$"; my $git=`which git`; chomp $git; - if (! -x $git || ! mkdir($dir)) { + if (! -x $git) { eval q{ - use Test::More skip_all => "git not available or could not make test dir" + use Test::More skip_all => "git not available" } } + if (! mkdir($dir)) { + die $@; + } } use Test::More tests => 18; diff --git a/t/mercurial.t b/t/mercurial.t index b64ea8e56..4918fc76e 100755 --- a/t/mercurial.t +++ b/t/mercurial.t @@ -6,11 +6,14 @@ BEGIN { $dir = "/tmp/ikiwiki-test-hg.$$"; my $hg=`which hg`; chomp $hg; - if (! -x $hg || ! mkdir($dir)) { + if (! -x $hg) { eval q{ - use Test::More skip_all => "hg not available or could not make test dir" + use Test::More skip_all => "hg not available" } } + if (! mkdir($dir)) { + die $@; + } } use Test::More tests => 11; diff --git a/t/svn.t b/t/svn.t index 82b71b5fc..cce8452a6 100755 --- a/t/svn.t +++ b/t/svn.t @@ -8,11 +8,14 @@ BEGIN { chomp $svn; my $svnadmin=`which svnadmin`; chomp $svnadmin; - if (! -x $svn || ! -x $svnadmin || ! mkdir($dir)) { + if (! -x $svn || ! -x $svnadmin) { eval q{ - use Test::More skip_all => "svn not available or could not make test dir" + use Test::More skip_all => "svn or svnadmin not available" } } + if (! mkdir($dir)) { + die $@; + } } use Test::More tests => 12; -- cgit v1.2.3 From 936dc6fc8ed7bbb05ad858c04894e317924fef8f Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Fri, 16 Jul 2010 16:05:59 -0400 Subject: add test of file replace --- t/replace.t | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100755 t/replace.t (limited to 't') diff --git a/t/replace.t b/t/replace.t new file mode 100755 index 000000000..7d91f772f --- /dev/null +++ b/t/replace.t @@ -0,0 +1,34 @@ +#!/usr/bin/perl +use warnings; +use strict; +use Test::More 'no_plan'; + +# setup +my $srcdir="t/tmp/src"; +my $destdir="t/tmp/dest"; +ok(! system("make -s ikiwiki.out")); +ok(! system("mkdir -p $srcdir")); + +# runs ikiwiki to build test site +sub runiki { + ok(! system("perl -I. ./ikiwiki.out -plugin html -underlaydir=underlays/basewiki -set underlaydirbase=underlays -templatedir=templates $srcdir $destdir @_")); +} +sub refreshiki { + runiki(); +} +sub setupiki { + ok(! system("rm -rf $srcdir/.ikiwiki $destdir")); + runiki("--rebuild"); +} + +# At one point, changing the extension of the source file of a page caused +# ikiwiki to fail. +ok(! system("touch $srcdir/foo.mdwn")); +setupiki(); +ok(! system("mv $srcdir/foo.mdwn $srcdir/foo.html")); +refreshiki(); + +# Changing a non-page file into a page could also cause ikiwiki to fail. + +# cleanup +ok(! system("rm -rf t/tmp")); -- cgit v1.2.3 From fd842e03e6a9d7b14bd161cc7ff6d9d48e8bf00c Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Fri, 16 Jul 2010 16:10:44 -0400 Subject: add 2 failing test cases --- t/replace.t | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 't') diff --git a/t/replace.t b/t/replace.t index 7d91f772f..04791d9f7 100755 --- a/t/replace.t +++ b/t/replace.t @@ -1,4 +1,5 @@ #!/usr/bin/perl +# Tests for bugs relating to replacing/renaming files in the srcdir. use warnings; use strict; use Test::More 'no_plan'; @@ -7,7 +8,6 @@ use Test::More 'no_plan'; my $srcdir="t/tmp/src"; my $destdir="t/tmp/dest"; ok(! system("make -s ikiwiki.out")); -ok(! system("mkdir -p $srcdir")); # runs ikiwiki to build test site sub runiki { @@ -20,15 +20,34 @@ sub setupiki { ok(! system("rm -rf $srcdir/.ikiwiki $destdir")); runiki("--rebuild"); } +sub newsrcdir { + ok(! system("rm -rf $srcdir $destdir")); + ok(! system("mkdir -p $srcdir")); +} # At one point, changing the extension of the source file of a page caused # ikiwiki to fail. +newsrcdir(); ok(! system("touch $srcdir/foo.mdwn")); setupiki(); ok(! system("mv $srcdir/foo.mdwn $srcdir/foo.html")); refreshiki(); +ok(! system("mv $srcdir/foo.html $srcdir/foo.mdwn")); +refreshiki(); # Changing a non-page file into a page could also cause ikiwiki to fail. +newsrcdir(); +ok(! system("touch $srcdir/foo")); +setupiki(); +ok(! system("mv $srcdir/foo $srcdir/foo.mdwn")); +refreshiki(); + +# Changing a page file into a non-page could also cause ikiwiki to fail. +newsrcdir(); +ok(! system("touch $srcdir/foo.mdwn")); +setupiki(); +ok(! system("mv $srcdir/foo.mdwn $srcdir/foo")); +refreshiki(); # cleanup ok(! system("rm -rf t/tmp")); -- cgit v1.2.3 From 41423084295f4b694cdb0ec208ea0a1807dc8a62 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sat, 17 Jul 2010 13:24:40 -0400 Subject: turning into complete source file conflict test suite --- t/conflicts.t | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ t/replace.t | 53 -------------------------------------- 2 files changed, 81 insertions(+), 53 deletions(-) create mode 100755 t/conflicts.t delete mode 100755 t/replace.t (limited to 't') diff --git a/t/conflicts.t b/t/conflicts.t new file mode 100755 index 000000000..7748c105f --- /dev/null +++ b/t/conflicts.t @@ -0,0 +1,81 @@ +#!/usr/bin/perl +# Tests for bugs relating to conflicting files in the srcdir +use warnings; +use strict; +use Test::More 'no_plan'; + +# setup +my $srcdir="t/tmp/src"; +my $destdir="t/tmp/dest"; +ok(! system("make -s ikiwiki.out")); + +# runs ikiwiki to build test site +sub runiki { + my $testdesc=shift; + ok((! system("perl -I. ./ikiwiki.out -plugin txt -plugin rawhtml -underlaydir=underlays/basewiki -set underlaydirbase=underlays -templatedir=templates $srcdir $destdir @_")), + $testdesc); +} +sub refreshiki { + runiki(shift); +} +sub setupiki { + ok(! system("rm -rf $srcdir/.ikiwiki $destdir")); + runiki(shift, "--rebuild"); +} +sub newsrcdir { + ok(! system("rm -rf $srcdir $destdir")); + ok(! system("mkdir -p $srcdir")); +} + +# At one point, changing the extension of the source file of a page caused +# ikiwiki to fail. +newsrcdir(); +ok(! system("touch $srcdir/foo.mdwn")); +setupiki("initial setup"); +ok(! system("mv $srcdir/foo.mdwn $srcdir/foo.txt")); +refreshiki("changed extension of source file of page"); +ok(! system("mv $srcdir/foo.txt $srcdir/foo.mdwn")); +refreshiki("changed extension of source file of page 2"); + +# Conflicting page sources is sorta undefined behavior, +# but should not crash ikiwiki. +# Added when refreshing +ok(! system("touch $srcdir/foo.txt")); +refreshiki("conflicting page sources in refresh"); +# Present during setup +newsrcdir(); +ok(! system("touch $srcdir/foo.mdwn")); +ok(! system("touch $srcdir/foo.txt")); +setupiki("conflicting page sources in setup"); + +# Changing a page file into a non-page could also cause ikiwiki to fail. +newsrcdir(); +ok(! system("touch $srcdir/foo.mdwn")); +setupiki("initial setup"); +ok(! system("mv $srcdir/foo.mdwn $srcdir/foo")); +refreshiki("page file turned into non-page"); + +# Changing a non-page file into a page could also cause ikiwiki to fail. +newsrcdir(); +ok(! system("touch $srcdir/foo")); +setupiki("initial setup"); +ok(! system("mv $srcdir/foo $srcdir/foo.mdwn")); +refreshiki("non-page file turned into page"); + +# What if a page renders to the same html file that a rawhtml file provides? +# Added when refreshing +newsrcdir(); +ok(! system("touch $srcdir/foo.mdwn")); +setupiki("initial setup"); +ok(! system("mkdir -p $srcdir/foo")); +ok(! system("touch $srcdir/foo/index.html")); +refreshiki("rawhtml file rendered same as existing page in refresh"); +# Present during setup +newsrcdir(); +ok(! system("touch $srcdir/foo.mdwn")); +ok(! system("mkdir -p $srcdir/foo")); +ok(! system("touch $srcdir/foo/index.html")); +setupiki("rawhtml file rendered same as existing page in setup"); + +# cleanup +ok(! system("rm -rf t/tmp")); diff --git a/t/replace.t b/t/replace.t deleted file mode 100755 index 04791d9f7..000000000 --- a/t/replace.t +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/perl -# Tests for bugs relating to replacing/renaming files in the srcdir. -use warnings; -use strict; -use Test::More 'no_plan'; - -# setup -my $srcdir="t/tmp/src"; -my $destdir="t/tmp/dest"; -ok(! system("make -s ikiwiki.out")); - -# runs ikiwiki to build test site -sub runiki { - ok(! system("perl -I. ./ikiwiki.out -plugin html -underlaydir=underlays/basewiki -set underlaydirbase=underlays -templatedir=templates $srcdir $destdir @_")); -} -sub refreshiki { - runiki(); -} -sub setupiki { - ok(! system("rm -rf $srcdir/.ikiwiki $destdir")); - runiki("--rebuild"); -} -sub newsrcdir { - ok(! system("rm -rf $srcdir $destdir")); - ok(! system("mkdir -p $srcdir")); -} - -# At one point, changing the extension of the source file of a page caused -# ikiwiki to fail. -newsrcdir(); -ok(! system("touch $srcdir/foo.mdwn")); -setupiki(); -ok(! system("mv $srcdir/foo.mdwn $srcdir/foo.html")); -refreshiki(); -ok(! system("mv $srcdir/foo.html $srcdir/foo.mdwn")); -refreshiki(); - -# Changing a non-page file into a page could also cause ikiwiki to fail. -newsrcdir(); -ok(! system("touch $srcdir/foo")); -setupiki(); -ok(! system("mv $srcdir/foo $srcdir/foo.mdwn")); -refreshiki(); - -# Changing a page file into a non-page could also cause ikiwiki to fail. -newsrcdir(); -ok(! system("touch $srcdir/foo.mdwn")); -setupiki(); -ok(! system("mv $srcdir/foo.mdwn $srcdir/foo")); -refreshiki(); - -# cleanup -ok(! system("rm -rf t/tmp")); -- cgit v1.2.3 From bfa4660df78aaae5f7c2b41caeccf6732ed4d74e Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sat, 17 Jul 2010 13:30:08 -0400 Subject: update --- t/conflicts.t | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 't') diff --git a/t/conflicts.t b/t/conflicts.t index 7748c105f..ac5f93b03 100755 --- a/t/conflicts.t +++ b/t/conflicts.t @@ -2,7 +2,7 @@ # Tests for bugs relating to conflicting files in the srcdir use warnings; use strict; -use Test::More 'no_plan'; +use Test::More tests => 48; # setup my $srcdir="t/tmp/src"; @@ -70,6 +70,13 @@ setupiki("initial setup"); ok(! system("mkdir -p $srcdir/foo")); ok(! system("touch $srcdir/foo/index.html")); refreshiki("rawhtml file rendered same as existing page in refresh"); +# Inverse added when refreshing +newsrcdir(); +ok(! system("mkdir -p $srcdir/foo")); +ok(! system("touch $srcdir/foo/index.html")); +setupiki("initial setup"); +ok(! system("touch $srcdir/foo.mdwn")); +refreshiki("page rendered same as existing rawhtml file in refresh"); # Present during setup newsrcdir(); ok(! system("touch $srcdir/foo.mdwn")); -- cgit v1.2.3 From a291f24be6af3f08573efa72f8809868120cfab4 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 18 Jul 2010 13:50:06 -0400 Subject: some other (similar) failure cases --- t/conflicts.t | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 't') diff --git a/t/conflicts.t b/t/conflicts.t index ac5f93b03..ac270e806 100755 --- a/t/conflicts.t +++ b/t/conflicts.t @@ -48,6 +48,22 @@ ok(! system("touch $srcdir/foo.mdwn")); ok(! system("touch $srcdir/foo.txt")); setupiki("conflicting page sources in setup"); +# Page and non-page file with same pagename. +newsrcdir(); +ok(! system("touch $srcdir/foo.mdwn")); +ok(! system("touch $srcdir/foo")); +setupiki("conflicting page and non-page in setup"); +newsrcdir(); +ok(! system("touch $srcdir/foo.mdwn")); +setupiki("initial setup"); +ok(! system("touch $srcdir/foo")); +refreshiki("conflicting page added (non-page already existing) in refresh"); +newsrcdir(); +ok(! system("touch $srcdir/foo")); +setupiki("initial setup"); +ok(! system("touch $srcdir/foo.mdwn")); +refreshiki("conflicting non-page added (page already existing) in refresh"); + # Changing a page file into a non-page could also cause ikiwiki to fail. newsrcdir(); ok(! system("touch $srcdir/foo.mdwn")); -- cgit v1.2.3 From 773db5a35e5b81fee3f1f8bf48dffc1dd4f3e2fe Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 18 Jul 2010 16:28:39 -0400 Subject: avoid error if two source files render the same destination file There are two sub-caces. If both source files still exist, the winner that renders the destination file is undefined. If one source file is deleted and the other added, in a refresh, the new file will take over the destination file. --- IkiWiki.pm | 14 +++++++++++++- t/conflicts.t | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) (limited to 't') diff --git a/IkiWiki.pm b/IkiWiki.pm index 701f7137d..4acc5508a 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -879,7 +879,19 @@ sub will_render ($$;$) { # Important security check. if (-e "$config{destdir}/$dest" && ! $config{rebuild} && ! grep { $_ eq $dest } (@{$renderedfiles{$page}}, @{$oldrenderedfiles{$page}}, @{$wikistate{editpage}{previews}})) { - error("$config{destdir}/$dest independently created, not overwriting with version from $page"); + my $from_other_page=0; + foreach my $p (keys %renderedfiles) { + if (grep { + $_ eq $dest || + dirname($_) eq $dest + } @{$renderedfiles{$p}}) { + $from_other_page=1; + last; + } + } + + error("$config{destdir}/$dest independently created, not overwriting with version from $page") + unless $from_other_page; } if (! $clear || $cleared{$page}) { diff --git a/t/conflicts.t b/t/conflicts.t index ac270e806..81b2c12cb 100755 --- a/t/conflicts.t +++ b/t/conflicts.t @@ -2,7 +2,7 @@ # Tests for bugs relating to conflicting files in the srcdir use warnings; use strict; -use Test::More tests => 48; +use Test::More tests => 76; # setup my $srcdir="t/tmp/src"; -- cgit v1.2.3 From d6cb4436bd93d0c49f629f46eb0f0ada0fa0ef3d Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 18 Jul 2010 16:45:44 -0400 Subject: 2 more cases --- t/conflicts.t | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 't') diff --git a/t/conflicts.t b/t/conflicts.t index 81b2c12cb..4664e1b6e 100755 --- a/t/conflicts.t +++ b/t/conflicts.t @@ -2,7 +2,7 @@ # Tests for bugs relating to conflicting files in the srcdir use warnings; use strict; -use Test::More tests => 76; +use Test::More tests => 92; # setup my $srcdir="t/tmp/src"; @@ -86,6 +86,13 @@ setupiki("initial setup"); ok(! system("mkdir -p $srcdir/foo")); ok(! system("touch $srcdir/foo/index.html")); refreshiki("rawhtml file rendered same as existing page in refresh"); +# Moved when refreshing +newsrcdir(); +ok(! system("touch $srcdir/foo.mdwn")); +setupiki("initial setup"); +ok(! system("mkdir -p $srcdir/foo")); +ok(! system("mv $srcdir/foo.mdwn $srcdir/foo/index.html")); +refreshiki("existing page moved to rawhtml file in refresh"); # Inverse added when refreshing newsrcdir(); ok(! system("mkdir -p $srcdir/foo")); @@ -93,6 +100,13 @@ ok(! system("touch $srcdir/foo/index.html")); setupiki("initial setup"); ok(! system("touch $srcdir/foo.mdwn")); refreshiki("page rendered same as existing rawhtml file in refresh"); +# Inverse moved when refreshing +newsrcdir(); +ok(! system("mkdir -p $srcdir/foo")); +ok(! system("touch $srcdir/foo/index.html")); +setupiki("initial setup"); +ok(! system("mv $srcdir/foo/index.html $srcdir/foo.mdwn")); +refreshiki("rawhtml file moved to page in refresh"); # Present during setup newsrcdir(); ok(! system("touch $srcdir/foo.mdwn")); -- cgit v1.2.3 From 1dbb2632ef5f7eaa1f5745d587b1373b45b09e56 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 18 Jul 2010 17:16:39 -0400 Subject: another class of conflicts: subdir/file conflicts --- t/conflicts.t | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 't') diff --git a/t/conflicts.t b/t/conflicts.t index 4664e1b6e..d7e04d3ae 100755 --- a/t/conflicts.t +++ b/t/conflicts.t @@ -2,7 +2,7 @@ # Tests for bugs relating to conflicting files in the srcdir use warnings; use strict; -use Test::More tests => 92; +use Test::More tests => 106; # setup my $srcdir="t/tmp/src"; @@ -64,6 +64,19 @@ setupiki("initial setup"); ok(! system("touch $srcdir/foo.mdwn")); refreshiki("conflicting non-page added (page already existing) in refresh"); +# Page that renders to a file that is also a subdirectory holding another +# file. +newsrcdir(); +ok(! system("touch $srcdir/foo.mdwn")); +ok(! system("mkdir -p $srcdir/foo/index.html")); +ok(! system("touch $srcdir/foo/index.html/bar.mdwn")); +setupiki("conflicting page file and subdirectory"); +newsrcdir(); +ok(! system("touch $srcdir/foo.mdwn")); +ok(! system("mkdir -p $srcdir/foo/index.html")); +ok(! system("touch $srcdir/foo/index.html/bar")); +setupiki("conflicting page file and subdirectory 2"); + # Changing a page file into a non-page could also cause ikiwiki to fail. newsrcdir(); ok(! system("touch $srcdir/foo.mdwn")); -- cgit v1.2.3 From 38014f0cad580c9f2468c1b72475b876209fb3e6 Mon Sep 17 00:00:00 2001 From: intrigeri Date: Tue, 20 Jul 2010 13:24:25 +0200 Subject: Run checkconfig after loading the po plugin, else state is not populated. The pofiles function relies on @slavelanguages to have been populated by po's checkconfig hook. --- t/po.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 't') diff --git a/t/po.t b/t/po.t index b6885a37e..495eef6b2 100755 --- a/t/po.t +++ b/t/po.t @@ -42,8 +42,8 @@ $config{po_slave_languages} = { $config{po_translatable_pages}='index or test1 or test2 or translatable'; $config{po_link_to}='negotiated'; IkiWiki::loadplugins(); -IkiWiki::checkconfig(); ok(IkiWiki::loadplugin('po'), "po plugin loaded"); +IkiWiki::checkconfig(); ### seed %pagesources and %pagecase $pagesources{'index'}='index.mdwn'; -- cgit v1.2.3 From c66fae8b16099fa8b0c3c86d38de0733b561d13f Mon Sep 17 00:00:00 2001 From: intrigeri Date: Tue, 20 Jul 2010 13:39:47 +0200 Subject: Added tests for po::pofiles. --- t/po.t | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 't') diff --git a/t/po.t b/t/po.t index 495eef6b2..de5ace3a0 100755 --- a/t/po.t +++ b/t/po.t @@ -17,7 +17,7 @@ BEGIN { } } -use Test::More tests => 65; +use Test::More tests => 68; BEGIN { use_ok("IkiWiki"); } @@ -91,6 +91,13 @@ ok(! IkiWiki::Plugin::po::istranslatable('test3'), "test3 is not translatable"); ok(! IkiWiki::Plugin::po::istranslation('test3'), "test3 is not a translation"); } +### pofiles + +my @pofiles = IkiWiki::Plugin::po::pofiles(srcfile("index.mdwn")); +ok( @pofiles, "pofiles is defined"); +ok( @pofiles == 2, "pofiles has correct size"); +is_deeply(\@pofiles, ["$config{srcdir}/index.es.po", "$config{srcdir}/index.fr.po"], "pofiles content is correct"); + ### links require IkiWiki::Render; -- cgit v1.2.3 From d98296d1db02febfa7cc4fbe7f304ca2a9858fef Mon Sep 17 00:00:00 2001 From: intrigeri Date: Tue, 20 Jul 2010 13:40:34 +0200 Subject: po.t: fix expected results As index.{es,fr} don't exist, po::refreshpofiles copies them from the basewiki underlay before running msgmerge. msgmerge marks as obsolete the translation strings that came from the basewiki po files, but the link plugin does not make the difference between obsolete and up-to-date links. $links{'index.fr'} and $links{'index.es'} are therefore expected to contain SandBox and ikiwiki. --- t/po.t | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 't') diff --git a/t/po.t b/t/po.t index de5ace3a0..a52611935 100755 --- a/t/po.t +++ b/t/po.t @@ -118,8 +118,8 @@ $config{po_link_to}='negotiated'; $msgprefix="links (po_link_to=negotiated)"; refresh_n_scan('index.mdwn', 'translatable.mdwn', 'nontranslatable.mdwn'); is_deeply(\@{$links{'index'}}, ['translatable', 'nontranslatable'], "$msgprefix index"); -is_deeply(\@{$links{'index.es'}}, ['translatable.es', 'nontranslatable'], "$msgprefix index.es"); -is_deeply(\@{$links{'index.fr'}}, ['translatable.fr', 'nontranslatable'], "$msgprefix index.fr"); +is_deeply(\@{$links{'index.es'}}, ['translatable.es', 'nontranslatable', 'SandBox', 'ikiwiki'], "$msgprefix index.es"); +is_deeply(\@{$links{'index.fr'}}, ['translatable.fr', 'nontranslatable', 'SandBox', 'ikiwiki'], "$msgprefix index.fr"); is_deeply(\@{$links{'translatable'}}, ['nontranslatable'], "$msgprefix translatable"); is_deeply(\@{$links{'translatable.es'}}, ['nontranslatable'], "$msgprefix translatable.es"); is_deeply(\@{$links{'translatable.fr'}}, ['nontranslatable'], "$msgprefix translatable.fr"); @@ -129,8 +129,8 @@ $config{po_link_to}='current'; $msgprefix="links (po_link_to=current)"; refresh_n_scan('index.mdwn', 'translatable.mdwn', 'nontranslatable.mdwn'); is_deeply(\@{$links{'index'}}, ['translatable', 'nontranslatable'], "$msgprefix index"); -is_deeply(\@{$links{'index.es'}}, [ map bestlink('index.es', $_), ('translatable.es', 'nontranslatable')], "$msgprefix index.es"); -is_deeply(\@{$links{'index.fr'}}, [ map bestlink('index.fr', $_), ('translatable.fr', 'nontranslatable')], "$msgprefix index.fr"); +is_deeply(\@{$links{'index.es'}}, [ (map bestlink('index.es', $_), ('translatable.es', 'nontranslatable')), 'SandBox', 'ikiwiki'], "$msgprefix index.es"); +is_deeply(\@{$links{'index.fr'}}, [ (map bestlink('index.fr', $_), ('translatable.fr', 'nontranslatable')), 'SandBox', 'ikiwiki'], "$msgprefix index.fr"); is_deeply(\@{$links{'translatable'}}, [bestlink('translatable', 'nontranslatable')], "$msgprefix translatable"); is_deeply(\@{$links{'translatable.es'}}, ['nontranslatable'], "$msgprefix translatable.es"); is_deeply(\@{$links{'translatable.fr'}}, ['nontranslatable'], "$msgprefix translatable.fr"); -- cgit v1.2.3 From 855fad15e50217ea768768ad1a3a68bb9422eae1 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Fri, 23 Jul 2010 14:30:15 -0400 Subject: whitespace --- t/po.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 't') diff --git a/t/po.t b/t/po.t index b6885a37e..0155b5303 100755 --- a/t/po.t +++ b/t/po.t @@ -61,7 +61,7 @@ $pagesources{'translatable.fr'}='translatable.fr.po'; $pagesources{'translatable.es'}='translatable.es.po'; $pagesources{'nontranslatable'}='nontranslatable.mdwn'; foreach my $page (keys %pagesources) { - $IkiWiki::pagecase{lc $page}=$page; + $IkiWiki::pagecase{lc $page}=$page; } ### populate srcdir -- cgit v1.2.3 From 5664401fbcd0694539ec8274d2df11b3e5db1e48 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 15 Aug 2010 11:38:39 -0400 Subject: Fix po test suite to not assume ikiwiki's underlay is already installed. Closes: #593047 --- debian/changelog | 2 ++ t/po.t | 11 +++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) (limited to 't') diff --git a/debian/changelog b/debian/changelog index 834dbd265..7f3ea7290 100644 --- a/debian/changelog +++ b/debian/changelog @@ -5,6 +5,8 @@ ikiwiki (3.20100805) UNRELEASED; urgency=low * flattr: New plugin. (Thanks to jaywalk for the initial implementation at a flattr plugin! This one is less configurable, but simpler.) * smiley: warn instead of error for missing smileys (Giuseppe Bilotta) + * Fix po test suite to not assume ikiwiki's underlay is already installed. + Closes: #593047 -- Joey Hess Mon, 09 Aug 2010 13:18:28 -0400 diff --git a/t/po.t b/t/po.t index 444743cac..1cd18c317 100755 --- a/t/po.t +++ b/t/po.t @@ -31,6 +31,9 @@ my $dir = tempdir("ikiwiki-test-po.XXXXXXXXXX", %config=IkiWiki::defaultconfig(); $config{srcdir} = "$dir/src"; $config{destdir} = "$dir/dst"; +$config{destdir} = "$dir/dst"; +$config{underlaydirbase} = "/dev/null"; +$config{underlaydir} = "/dev/null"; $config{discussion} = 0; $config{po_master_language} = { code => 'en', name => 'English' @@ -118,8 +121,8 @@ $config{po_link_to}='negotiated'; $msgprefix="links (po_link_to=negotiated)"; refresh_n_scan('index.mdwn', 'translatable.mdwn', 'nontranslatable.mdwn'); is_deeply(\@{$links{'index'}}, ['translatable', 'nontranslatable'], "$msgprefix index"); -is_deeply(\@{$links{'index.es'}}, ['translatable.es', 'nontranslatable', 'SandBox', 'ikiwiki'], "$msgprefix index.es"); -is_deeply(\@{$links{'index.fr'}}, ['translatable.fr', 'nontranslatable', 'SandBox', 'ikiwiki'], "$msgprefix index.fr"); +is_deeply(\@{$links{'index.es'}}, ['translatable.es', 'nontranslatable'], "$msgprefix index.es"); +is_deeply(\@{$links{'index.fr'}}, ['translatable.fr', 'nontranslatable'], "$msgprefix index.fr"); is_deeply(\@{$links{'translatable'}}, ['nontranslatable'], "$msgprefix translatable"); is_deeply(\@{$links{'translatable.es'}}, ['nontranslatable'], "$msgprefix translatable.es"); is_deeply(\@{$links{'translatable.fr'}}, ['nontranslatable'], "$msgprefix translatable.fr"); @@ -129,8 +132,8 @@ $config{po_link_to}='current'; $msgprefix="links (po_link_to=current)"; refresh_n_scan('index.mdwn', 'translatable.mdwn', 'nontranslatable.mdwn'); is_deeply(\@{$links{'index'}}, ['translatable', 'nontranslatable'], "$msgprefix index"); -is_deeply(\@{$links{'index.es'}}, [ (map bestlink('index.es', $_), ('translatable.es', 'nontranslatable')), 'SandBox', 'ikiwiki'], "$msgprefix index.es"); -is_deeply(\@{$links{'index.fr'}}, [ (map bestlink('index.fr', $_), ('translatable.fr', 'nontranslatable')), 'SandBox', 'ikiwiki'], "$msgprefix index.fr"); +is_deeply(\@{$links{'index.es'}}, [ (map bestlink('index.es', $_), ('translatable.es', 'nontranslatable'))], "$msgprefix index.es"); +is_deeply(\@{$links{'index.fr'}}, [ (map bestlink('index.fr', $_), ('translatable.fr', 'nontranslatable'))], "$msgprefix index.fr"); is_deeply(\@{$links{'translatable'}}, [bestlink('translatable', 'nontranslatable')], "$msgprefix translatable"); is_deeply(\@{$links{'translatable.es'}}, ['nontranslatable'], "$msgprefix translatable.es"); is_deeply(\@{$links{'translatable.fr'}}, ['nontranslatable'], "$msgprefix translatable.fr"); -- cgit v1.2.3 From 7d29ba2fc77d1e2b902b3742de6671a87b4d31e2 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Thu, 19 Aug 2010 14:04:55 -0400 Subject: add htmlscrubber colon in url cases to test suite --- t/htmlize.t | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) (limited to 't') diff --git a/t/htmlize.t b/t/htmlize.t index a7e7f8c39..a436748f9 100755 --- a/t/htmlize.t +++ b/t/htmlize.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More tests => 26; +use Test::More tests => 32; use Encode; BEGIN { use_ok("IkiWiki"); } @@ -68,3 +68,21 @@ is(IkiWiki::htmlize("foo", "foo", "mdwn", is(IkiWiki::htmlize("foo", "foo", "mdwn", q{bar}), q{bar}, "class attribute allowed"); +is(IkiWiki::htmlize("foo", "foo", "mdwn", + q{}), + q{}, "simple anchor allowed"); +is(IkiWiki::htmlize("foo", "foo", "mdwn", + q{}), + q{}, "colon allowed in anchor"); +is(IkiWiki::htmlize("foo", "foo", "mdwn", + q{}), + q{}, "colon allowed in query string"); +is(IkiWiki::htmlize("foo", "foo", "mdwn", + q{}), + q{}, "unknown protocol blocked"); +is(IkiWiki::htmlize("foo", "foo", "mdwn", + q{}), + q{}, "simple relative anchor allowed"); +is(IkiWiki::htmlize("foo", "foo", "mdwn", + q{}), + q{}, "colon in simple relative anchor allowed"); -- cgit v1.2.3