function injection overhaul

Add an inject function, that can be used by plugins that want to replace
one of ikiwiki's functions with their own version. (This is a scary thing
that grubs through the symbol table, and replaces all exported occurances
of a function with the injected version.)

external: RPC functions can be injected to replace exported functions.

Removed the stupid displaytime hook, and use injection instead.
master
Joey Hess 2008-10-21 17:57:19 -04:00
parent 92a43d5d38
commit e75818572f
8 changed files with 111 additions and 31 deletions

View File

@ -21,6 +21,7 @@ our @EXPORT = qw(hook debug error template htmlpage add_depends pagespec_match
bestlink htmllink readfile writefile pagetype srcfile pagename bestlink htmllink readfile writefile pagetype srcfile pagename
displaytime will_render gettext urlto targetpage displaytime will_render gettext urlto targetpage
add_underlay pagetitle titlepage linkpage newpagefile add_underlay pagetitle titlepage linkpage newpagefile
inject
%config %links %pagestate %wikistate %renderedfiles %config %links %pagestate %wikistate %renderedfiles
%pagesources %destsources); %pagesources %destsources);
our $VERSION = 2.00; # plugin interface version, next is ikiwiki version our $VERSION = 2.00; # plugin interface version, next is ikiwiki version
@ -898,23 +899,13 @@ sub abs2rel ($$) { #{{{
} #}}} } #}}}
sub displaytime ($;$) { #{{{ sub displaytime ($;$) { #{{{
my $time=shift; # Plugins can override this function to mark up the time to
my $format=shift; # display.
if (exists $hooks{displaytime}) { return '<span class="date">'.formattime(@_).'</span>';
my $ret;
run_hooks(displaytime => sub {
$ret=shift->($time, $format)
});
return $ret;
}
else {
return formattime($time, $format);
}
} #}}} } #}}}
sub formattime ($;$) { #{{{ sub formattime ($;$) { #{{{
# Plugins can override this function to mark up the time for # Plugins can override this function to format the time.
# display.
my $time=shift; my $time=shift;
my $format=shift; my $format=shift;
if (! defined $format) { if (! defined $format) {
@ -1676,6 +1667,31 @@ sub yesno ($) { #{{{
return (defined $val && lc($val) eq gettext("yes")); return (defined $val && lc($val) eq gettext("yes"));
} #}}} } #}}}
sub inject { #{{{
# Injects a new function into the symbol table to replace an
# exported function.
my %params=@_;
# This is deep ugly perl foo, beware.
no strict;
no warnings;
if (! defined $params{parent}) {
$params{parent}='::';
$params{old}=\&{$params{name}};
$params{name}=~s/.*:://;
}
my $parent=$params{parent};
foreach my $ns (grep /^\w+::/, keys %{$parent}) {
$ns = $params{parent} . $ns;
inject(%params, parent => $ns) unless $ns eq '::main::';
*{$ns . $params{name}} = $params{call}
if exists ${$ns}{$params{name}} &&
\&{${$ns}{$params{name}}} == $params{old};
}
use strict;
use warnings;
} #}}}
sub pagespec_merge ($$) { #{{{ sub pagespec_merge ($$) { #{{{
my $a=shift; my $a=shift;
my $b=shift; my $b=shift;

View File

@ -202,10 +202,16 @@ sub inject ($@) { #{{{
my $sub = sub { my $sub = sub {
IkiWiki::Plugin::external::rpc_call($plugin, $params{call}, @_) IkiWiki::Plugin::external::rpc_call($plugin, $params{call}, @_)
}; };
$sub=memoize($sub) if $params{memoize};
# This will add it to the symbol table even if not present.
no warnings; no warnings;
eval qq{*$params{name}=\$sub}; eval qq{*$params{name}=\$sub};
use warnings; use warnings;
memoize($params{name}) if $params{memoize};
# This will ensure that everywhere it was exported to sees
# the injected version.
IkiWiki::inject(name => $params{name}, call => $sub);
return 1; return 1;
} #}}} } #}}}

View File

@ -12,7 +12,7 @@ sub import { #{{{
add_underlay("javascript"); add_underlay("javascript");
hook(type => "getsetup", id => "relativedate", call => \&getsetup); hook(type => "getsetup", id => "relativedate", call => \&getsetup);
hook(type => "format", id => "relativedate", call => \&format); hook(type => "format", id => "relativedate", call => \&format);
hook(type => "displaytime", id => "relativedate", call => \&display); inject(name => "IkiWiki::displaytime", call => \&mydisplaytime);
} # }}} } # }}}
sub getsetup () { #{{{ sub getsetup () { #{{{
@ -43,7 +43,7 @@ sub include_javascript ($;$) { #{{{
'" type="text/javascript" charset="utf-8"></script>'; '" type="text/javascript" charset="utf-8"></script>';
} #}}} } #}}}
sub display ($;$) { #{{{ sub mydisplaytime ($;$) { #{{{
my $time=shift; my $time=shift;
my $format=shift; my $format=shift;

5
debian/changelog vendored
View File

@ -24,6 +24,11 @@ ikiwiki (2.68) UNRELEASED; urgency=low
the toplevel tagpage, and not closer subpages. The html links already went the toplevel tagpage, and not closer subpages. The html links already went
there, but internally the links were not recorded as absolute, which could there, but internally the links were not recorded as absolute, which could
cause confusing backlinks etc. cause confusing backlinks etc.
* Add an inject function, that can be used by plugins that want to
replace one of ikiwiki's functions with their own version.
(This is a scary thing that grubs through the symbol table, and replaces
all exported occurances of a function with the injected version.)
* external: RPC functions can be injected to replace exported functions.
-- Joey Hess <joeyh@debian.org> Fri, 17 Oct 2008 20:11:02 -0400 -- Joey Hess <joeyh@debian.org> Fri, 17 Oct 2008 20:11:02 -0400

View File

@ -47,6 +47,11 @@ Any thoughts on this?
>>> `targetpage`, `bestlink`, and `beautify_urlpath`. But, I noticed >>> `targetpage`, `bestlink`, and `beautify_urlpath`. But, I noticed
>>> the other day that such wrappers around exported functions are only visible by >>> the other day that such wrappers around exported functions are only visible by
>>> plugins loaded after the plugin that defines them. >>> plugins loaded after the plugin that defines them.
>>>
>>> Update: Take a look at the new "Function overriding" section of
>>> [[plugins/write]]. I think you can just inject wrappers about a few ikiwiki
>>> functions, rather than adding hooks. The `inject` function is pretty
>>> insane^Wlow level, but seems to work great. --[[Joey]]
>> >>
>> The Discussion pages issue is something I am not sure about yet. But I will >> The Discussion pages issue is something I am not sure about yet. But I will
>> probably decide that "slave" pages, being only translations, don't deserve >> probably decide that "slave" pages, being only translations, don't deserve

View File

@ -854,6 +854,56 @@ By the way, to parse a ikiwiki setup file and populate `%config`, a
program just needs to do something like: program just needs to do something like:
`use IkiWiki::Setup; IkiWiki::Setup::load($filename)` `use IkiWiki::Setup; IkiWiki::Setup::load($filename)`
### Function overriding
Sometimes using ikiwiki's pre-defined hooks is not enough. Your plugin
may need to replace one of ikiwiki's own functions with a modified version,
or wrap one of the functions.
For example, your plugin might want to override `displaytime`, to change
the html markup used when displaying a date. Or it might want to override
`IkiWiki::formattime`, to change how a date is formatted. Or perhaps you
want to override `bestlink` and change how ikiwiki deals with WikiLinks.
By venturing into this territory, your plugin is becoming tightly tied to
ikiwiki's internals. And it might break if those internals change. But
don't let that stop you, if you're brave.
Ikiwiki provides an `inject()` function, that is a powerful way to replace
any function with one of your own. This even allows you to inject a
replacement for an exported function, like `bestlink`. Everything that
imports that function will get your version instead. Pass it the name of
the function to replace, and a new function to call.
For example, here's how to replace `displaytime` with a version using HTML 5
markup:
inject(name => 'IkiWiki::displaytime', call => sub {
return "<time>".formattime(@_)."</time>";
});
Here's how to wrap `bestlink` with a version that tries to handle
plural words:
my $origbestlink=\&bestlink;
inject(name => 'IkiWiki::bestlink', call => \&mybestlink);
sub deplural ($) {
my $word=shift;
$word =~ s/e?s$//; # just an example :-)
return $word;
}
sub mybestlink ($$) {
my $page=shift;
my $link=shift;
my $ret=$origbestlink->($page, $link);
if (! length $ret) {
$ret=$origbestlink->($page, deplural($link));
}
return $ret;
}
### Javascript ### Javascript
Some plugins use javascript to make ikiwiki look a bit more web-2.0-ish. Some plugins use javascript to make ikiwiki look a bit more web-2.0-ish.

View File

@ -106,9 +106,8 @@ sub import {
rpc_call("getvar", "config", "url")."\n"; rpc_call("getvar", "config", "url")."\n";
# Here's an example of how to inject an arbitrary function into # Here's an example of how to inject an arbitrary function into
# ikiwiki, replacing a core function. # ikiwiki. Note use of automatic memoization.
# Note use of automatic memoization. rpc_call("inject", name => "IkiWiki::bob",
rpc_call("inject", name => "IkiWiki::formattime",
call => "formattime", memoize => 1); call => "formattime", memoize => 1);
print STDERR "externaldemo plugin successfully imported\n"; print STDERR "externaldemo plugin successfully imported\n";
@ -126,9 +125,8 @@ sub preprocess {
return "externaldemo plugin preprocessing on $title!"; return "externaldemo plugin preprocessing on $title!";
} }
sub formattime { sub bob {
print STDERR "externaldemo plugin's formattime called via RPC"; print STDERR "externaldemo plugin's bob called via RPC";
return scalar "formatted time: ".localtime(shift);
} }
# Now all that's left to do is loop and handle each incoming RPC request. # Now all that's left to do is loop and handle each incoming RPC request.

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2008-10-19 20:06-0400\n" "POT-Creation-Date: 2008-10-21 17:51-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -48,7 +48,7 @@ msgstr ""
msgid "You are banned." msgid "You are banned."
msgstr "" msgstr ""
#: ../IkiWiki/CGI.pm:385 ../IkiWiki/CGI.pm:386 ../IkiWiki.pm:1182 #: ../IkiWiki/CGI.pm:385 ../IkiWiki/CGI.pm:386 ../IkiWiki.pm:1175
msgid "Error" msgid "Error"
msgstr "" msgstr ""
@ -913,25 +913,25 @@ msgstr ""
msgid "refreshing wiki.." msgid "refreshing wiki.."
msgstr "" msgstr ""
#: ../IkiWiki.pm:458 #: ../IkiWiki.pm:459
msgid "Must specify url to wiki with --url when using --cgi" msgid "Must specify url to wiki with --url when using --cgi"
msgstr "" msgstr ""
#: ../IkiWiki.pm:504 #: ../IkiWiki.pm:505
msgid "cannot use multiple rcs plugins" msgid "cannot use multiple rcs plugins"
msgstr "" msgstr ""
#: ../IkiWiki.pm:533 #: ../IkiWiki.pm:534
#, perl-format #, perl-format
msgid "failed to load external plugin needed for %s plugin: %s" msgid "failed to load external plugin needed for %s plugin: %s"
msgstr "" msgstr ""
#: ../IkiWiki.pm:1165 #: ../IkiWiki.pm:1158
#, perl-format #, perl-format
msgid "preprocessing loop detected on %s at depth %i" msgid "preprocessing loop detected on %s at depth %i"
msgstr "" msgstr ""
#: ../IkiWiki.pm:1674 #: ../IkiWiki.pm:1667
msgid "yes" msgid "yes"
msgstr "" msgstr ""