]> git.vanrenterghem.biz Git - git.ikiwiki.info.git/commitdiff
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
                  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
                  %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 ($;$) { #{{{
 } #}}}
 
 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 ($;$) { #{{{
 } #}}}
 
 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) {
        my $time=shift;
        my $format=shift;
        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 ($$) { #{{{
        my $a=shift;
        my $b=shift;
 sub pagespec_merge ($$) { #{{{
        my $a=shift;
        my $b=shift;
@@ -1770,7 +1786,7 @@ sub pagespec_valid ($) { #{{{
        my $sub=pagespec_translate($spec);
        return ! $@;
 } #}}}
        my $sub=pagespec_translate($spec);
        return ! $@;
 } #}}}
-       
+
 sub glob2re ($) { #{{{
        my $re=quotemeta(shift);
        $re=~s/\\\*/.*/g;
 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}, @_)
        };
        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;
        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;
 } #}}}
 
        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);
        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 () { #{{{
 } # }}}
 
 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 $format=shift;
 
        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.
     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
 
 
  -- 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.
 >>> `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
 >>
 >> 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)`
 
 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.
 ### 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
                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";
                call => "formattime", memoize => 1);
 
        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 {
-       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.
 }
 
 # 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"
 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"
 "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 ""
 
 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 ""
 
 msgid "Error"
 msgstr ""
 
@@ -913,25 +913,25 @@ msgstr ""
 msgid "refreshing wiki.."
 msgstr ""
 
 msgid "refreshing wiki.."
 msgstr ""
 
-#: ../IkiWiki.pm:458
+#: ../IkiWiki.pm:459
 msgid "Must specify url to wiki with --url when using --cgi"
 msgstr ""
 
 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 ""
 
 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 ""
 
 #, 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 ""
 
 #, perl-format
 msgid "preprocessing loop detected on %s at depth %i"
 msgstr ""
 
-#: ../IkiWiki.pm:1674
+#: ../IkiWiki.pm:1667
 msgid "yes"
 msgstr ""
 
 msgid "yes"
 msgstr ""