function injection overhaul
authorJoey Hess <joey@kodama.kitenet.net>
Tue, 21 Oct 2008 21:57:19 +0000 (17:57 -0400)
committerJoey Hess <joey@kodama.kitenet.net>
Tue, 21 Oct 2008 21:57:19 +0000 (17:57 -0400)
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.

IkiWiki.pm
IkiWiki/Plugin/external.pm
IkiWiki/Plugin/relativedate.pm
debian/changelog
doc/plugins/contrib/po.mdwn
doc/plugins/write.mdwn
plugins/externaldemo
po/ikiwiki.pot

index 207ca87fb4d96dbecacddc897015647559d91a62..e0454963dd6bb2a2ef98815be1fb25fa30157185 100644 (file)
@@ -21,6 +21,7 @@ our @EXPORT = qw(hook debug error template htmlpage add_depends pagespec_match
                  bestlink htmllink readfile writefile pagetype srcfile pagename
                  displaytime will_render gettext urlto targetpage
                 add_underlay pagetitle titlepage linkpage newpagefile
+                inject
                  %config %links %pagestate %wikistate %renderedfiles
                  %pagesources %destsources);
 our $VERSION = 2.00; # plugin interface version, next is ikiwiki version
@@ -898,23 +899,13 @@ sub abs2rel ($$) { #{{{
 } #}}}
 
 sub displaytime ($;$) { #{{{
-       my $time=shift;
-       my $format=shift;
-       if (exists $hooks{displaytime}) {
-               my $ret;
-               run_hooks(displaytime => sub {
-                       $ret=shift->($time, $format)
-               });
-               return $ret;
-       }
-       else {
-               return formattime($time, $format);
-       }
+       # Plugins can override this function to mark up the time to
+       # display.
+       return '<span class="date">'.formattime(@_).'</span>';
 } #}}}
 
 sub formattime ($;$) { #{{{
-       # Plugins can override this function to mark up the time for
-       # display.
+       # Plugins can override this function to format the time.
        my $time=shift;
        my $format=shift;
        if (! defined $format) {
@@ -1676,6 +1667,31 @@ sub yesno ($) { #{{{
        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 ($$) { #{{{
        my $a=shift;
        my $b=shift;
@@ -1770,7 +1786,7 @@ sub pagespec_valid ($) { #{{{
        my $sub=pagespec_translate($spec);
        return ! $@;
 } #}}}
-       
+
 sub glob2re ($) { #{{{
        my $re=quotemeta(shift);
        $re=~s/\\\*/.*/g;
index 287e118f143b3474d5b3fb6d96b9df14504ffb23..4ce9c8babb62d45bacb3bb76a4773bd27cb09b5c 100644 (file)
@@ -202,10 +202,16 @@ sub inject ($@) { #{{{
        my $sub = sub {
                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;
        eval qq{*$params{name}=\$sub};
        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;
 } #}}}
 
index f4dba61a4e8e1511dff8cd9053c1c514e321be92..dc8f7d538a726f5689b6c9b51ddeb4691cc1750f 100644 (file)
@@ -12,7 +12,7 @@ sub import { #{{{
        add_underlay("javascript");
        hook(type => "getsetup", id => "relativedate", call => \&getsetup);
        hook(type => "format", id => "relativedate", call => \&format);
-       hook(type => "displaytime", id => "relativedate", call => \&display);
+       inject(name => "IkiWiki::displaytime", call => \&mydisplaytime);
 } # }}}
 
 sub getsetup () { #{{{
@@ -43,7 +43,7 @@ sub include_javascript ($;$) { #{{{
                '" type="text/javascript" charset="utf-8"></script>';
 } #}}}
 
-sub display ($;$) { #{{{
+sub mydisplaytime ($;$) { #{{{
        my $time=shift;
        my $format=shift;
 
index 928cd86665559a0bc20c0c5536c2b16cdfcfaaed..e1baea8ce5be355de99089f1ea795f1f83710a23 100644 (file)
@@ -24,6 +24,11 @@ ikiwiki (2.68) UNRELEASED; urgency=low
     the toplevel tagpage, and not closer subpages. The html links already went
     there, but internally the links were not recorded as absolute, which could
     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
 
index 30ede95a638956c87f95a5a296f42d6adfa22ac7..c4b7f9ee9ad4dfc73b5c062c2625e9a9758c3729 100644 (file)
@@ -47,6 +47,11 @@ Any thoughts on this?
 >>> `targetpage`, `bestlink`, and `beautify_urlpath`. But, I noticed
 >>> the other day that such wrappers around exported functions are only visible by
 >>> 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
 >> probably decide that "slave" pages, being only translations, don't deserve
index 856b34ba1e1a262794c54a5684d78acdf2cd00f1..2e11e6234ef24b1e7ca64df3b09c950f25772a18 100644 (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:
 `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
 
 Some plugins use javascript to make ikiwiki look a bit more web-2.0-ish.
index 4d13f244453badfb4eaaa406611d5b64047b2fe6..be7aba8b929807db3a6b56a6846e7d34b3c79a25 100755 (executable)
@@ -106,9 +106,8 @@ sub import {
                rpc_call("getvar", "config", "url")."\n";
 
        # Here's an example of how to inject an arbitrary function into
-       # ikiwiki, replacing a core function.
-       # Note use of automatic memoization.
-       rpc_call("inject", name => "IkiWiki::formattime",
+       # ikiwiki. Note use of automatic memoization.
+       rpc_call("inject", name => "IkiWiki::bob",
                call => "formattime", memoize => 1);
 
        print STDERR "externaldemo plugin successfully imported\n";
@@ -126,9 +125,8 @@ sub preprocess {
        return "externaldemo plugin preprocessing on $title!";
 }
 
-sub formattime {
-       print STDERR "externaldemo plugin's formattime called via RPC";
-       return scalar "formatted time: ".localtime(shift);
+sub bob {
+       print STDERR "externaldemo plugin's bob called via RPC";
 }
 
 # Now all that's left to do is loop and handle each incoming RPC request.
index 335575f02713f069bc85fa3566cc770b5e4746c2..4452ea8dce40281efa990a11148269a8cba7f3c3 100644 (file)
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\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"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -48,7 +48,7 @@ msgstr ""
 msgid "You are banned."
 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"
 msgstr ""
 
@@ -913,25 +913,25 @@ msgstr ""
 msgid "refreshing wiki.."
 msgstr ""
 
-#: ../IkiWiki.pm:458
+#: ../IkiWiki.pm:459
 msgid "Must specify url to wiki with --url when using --cgi"
 msgstr ""
 
-#: ../IkiWiki.pm:504
+#: ../IkiWiki.pm:505
 msgid "cannot use multiple rcs plugins"
 msgstr ""
 
-#: ../IkiWiki.pm:533
+#: ../IkiWiki.pm:534
 #, perl-format
 msgid "failed to load external plugin needed for %s plugin: %s"
 msgstr ""
 
-#: ../IkiWiki.pm:1165
+#: ../IkiWiki.pm:1158
 #, perl-format
 msgid "preprocessing loop detected on %s at depth %i"
 msgstr ""
 
-#: ../IkiWiki.pm:1674
+#: ../IkiWiki.pm:1667
 msgid "yes"
 msgstr ""