(no commit message)
[ikiwiki] / plugins / pythondemo
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # pythondemo — demo Python ikiwiki plugin
5 #
6 # Copyright © martin f. krafft <madduck@madduck.net>
7
8 #  Redistribution and use in source and binary forms, with or without
9 # modification, are permitted provided that the following conditions
10 # are met:
11 # 1. Redistributions of source code must retain the above copyright
12 #    notice, this list of conditions and the following disclaimer.
13 # 2. Redistributions in binary form must reproduce the above copyright
14 #    notice, this list of conditions and the following disclaimer in the
15 #    documentation and/or other materials provided with the distribution.
16 # .
17 # THIS SOFTWARE IS PROVIDED BY IKIWIKI AND CONTRIBUTORS ``AS IS''
18 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20 # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
21 # OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
24 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
27 # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 # SUCH DAMAGE.
29
30 __name__ = 'pythondemo'
31 __description__ = 'demo Python ikiwiki plugin'
32 __version__ = '0.1'
33 __author__ = 'martin f. krafft <madduck@madduck.net>'
34 __copyright__ = 'Copyright © ' + __author__
35 __licence__ = 'BSD-2-clause'
36
37 from proxy import IkiWikiProcedureProxy
38
39 import sys
40 def debug(s):
41     sys.stderr.write(__name__ + ':DEBUG:%s\n' % s)
42     sys.stderr.flush()
43
44 proxy = IkiWikiProcedureProxy(__name__, debug_fn=None)
45
46 def _arglist_to_dict(args):
47     if len(args) % 2 != 0:
48         raise ValueError, 'odd number of arguments, cannot convert to dict'
49     return dict([args[i:i+2] for i in xrange(0, len(args), 2)])
50
51 def getopt_demo(proxy, *args):
52     # This allows for plugins to perform their own processing of command-line
53     # options and so add options to the ikiwiki command line. It's called
54     # during command line processing, with @ARGV full of any options that
55     # ikiwiki was not able to process on its own. The function should process
56     # any options it can, removing them from @ARGV, and probably recording the
57     # configuration settings in %config. It should take care not to abort if
58     # it sees an option it cannot process, and should just skip over those
59     # options and leave them in @ARGV.
60     #
61     debug("hook `getopt' called with arguments %s" % str(args))
62     args = proxy.getargv()
63     if '--demo' in args:
64         args = [i for i in args if i != '--demo']
65     proxy.setargv(args)
66 proxy.hook('getopt', getopt_demo)
67
68 def checkconfig_demo(proxy, *args):
69     # This is useful if the plugin needs to check for or modify ikiwiki's
70     # configuration. It's called early in the startup process. The function is
71     # passed no values. It's ok for the function to call error() if something
72     # isn't configured right.
73     debug("hook `checkconfig' called with arguments %s" % str(args))
74     # check that --url has been set
75     url = proxy.getvar('config', 'url')
76     if url is None or len(url) == 0:
77         proxy.error('--url has not been set')
78 proxy.hook('checkconfig', checkconfig_demo)
79
80 def refresh_demo(proxy, *args):
81     # This hook is called just before ikiwiki scans the wiki for changed
82     # files. It's useful for plugins that need to create or modify a source
83     # page. The function is passed no values.
84     debug("hook `refresh' called with arguments %s" % str(args))
85 proxy.hook('refresh', refresh_demo)
86
87 def needsbuild_demo(proxy, *args):
88     # This allows a plugin to manipulate the list of files that need to be
89     # built when the wiki is refreshed. The function is passed a reference to
90     # an array of pages that will be rebuilt, and can modify the array, either
91     # adding or removing files from it.
92     # TODO: how do we modify the array? Joey sees no solution...
93     # we could just return the array and expect ikiwiki to use that...
94     debug("hook `needsbuild' called with arguments %s" % str(args))
95     raise NotImplementedError
96 #proxy.hook('needsbuild', needsbuild_demo)
97
98 def filter_demo(proxy, *args):
99     # Runs on the raw source of a page, before anything else touches it, and
100     # can make arbitrary changes. The function is passed named parameters
101     # "page", "destpage", and "content". It should return the filtered
102     # content.
103     kwargs = _arglist_to_dict(args)
104     debug("hook `filter' called with arguments %s" % kwargs);
105     return kwargs['content']
106 proxy.hook('filter', filter_demo)
107
108 def preprocess_demo(proxy, *args):
109     # Each time the directive is processed, the referenced function
110     # (preprocess in the example above) is called, and is passed named
111     # parameters. A "page" parameter gives the name of the page that embedded
112     # the preprocessor directive, while a "destpage" parameter gives the name
113     # of the page the content is going to (different for inlined pages), and
114     # a "preview" parameter is set to a true value if the page is being
115     # previewed. All parameters included in the directive are included as
116     # named parameters as well. Whatever the function returns goes onto the
117     # page in place of the directive.
118     #
119     # An optional "scan" parameter, if set to a true value, makes the hook be
120     # called during the preliminary scan that ikiwiki makes of updated pages,
121     # before begining to render pages. This parameter should be set to true if
122     # the hook modifies data in %links. Note that doing so will make the hook
123     # be run twice per page build, so avoid doing it for expensive hooks. (As
124     # an optimisation, if your preprocessor hook is called in a void contets,
125     # you can assume it's being run in scan mode.)
126     #
127     # Note that if the htmlscrubber is enabled, html in PreProcessorDirective
128     # output is sanitised, which may limit what your plugin can do. Also, the
129     # rest of the page content is not in html format at preprocessor time.
130     # Text output by a preprocessor directive will be linkified and passed
131     # through markdown (or whatever engine is used to htmlize the page) along
132     # with the rest of the page.
133     #
134     kwargs = _arglist_to_dict(args)
135     debug("hook `preprocess' called with arguments %s" % kwargs)
136     del kwargs['preview']
137     del kwargs['page']
138     del kwargs['destpage']
139     ret = 'foobar preprocessor called with arguments:'
140     for i in kwargs.iteritems():
141         ret += ' %s=%s' % i
142     return ret
143 # put [[!foobar ...]] somewhere to try this
144 proxy.hook('preprocess', preprocess_demo, id='foobar')
145
146 def linkify_demo(proxy, *args):
147     # This hook is called to convert WikiLinks on the page into html links.
148     # The function is passed named parameters "page", "destpage", and
149     # "content". It should return the linkified content.
150     #
151     # Plugins that implement linkify must also implement a scan hook, that
152     # scans for the links on the page and adds them to %links.
153     kwargs = _arglist_to_dict(args)
154     debug("hook `linkify' called with arguments %s" % kwargs)
155     return kwargs['content']
156 proxy.hook('linkify', linkify_demo)
157
158 def scan_demo(proxy, *args):
159     # This hook is called early in the process of building the wiki, and is
160     # used as a first pass scan of the page, to collect metadata about the
161     # page. It's mostly used to scan the page for WikiLinks, and add them to
162     # %links.
163     #
164     # The function is passed named parameters "page" and "content". Its return
165     # value is ignored.
166     #
167     kwargs = _arglist_to_dict(args)
168     debug("hook `scan' called with arguments %s" % kwargs)
169     links = proxy.getvar('links', kwargs['page'])
170     debug("links for page `%s' are: %s" % (kwargs['page'], links))
171     proxy.setvar('links', kwargs['page'], links)
172 proxy.hook('scan', scan_demo)
173
174 def htmlize_demo(proxy, *args):
175     # Runs on the raw source of a page and turns it into html. The id
176     # parameter specifies the filename extension that a file must have to be
177     # htmlized using this plugin. This is how you can add support for new and
178     # exciting markup languages to ikiwiki.
179     #
180     # The function is passed named parameters: "page" and "content" and should
181     # return the htmlized content.
182     kwargs = _arglist_to_dict(args)
183     debug("hook `htmlize' called with arguments %s" % kwargs)
184     return kwargs['content']
185 proxy.hook('htmlize', htmlize_demo)
186
187 def pagetemplate_demo(proxy, *args):
188     # Templates are filled out for many different things in ikiwiki, like
189     # generating a page, or part of a blog page, or an rss feed, or a cgi.
190     # This hook allows modifying the variables available on those templates.
191     # The function is passed named parameters. The "page" and "destpage"
192     # parameters are the same as for a preprocess hook. The "template"
193     # parameter is a HTML::Template object that is the template that will be
194     # used to generate the page. The function can manipulate that template
195     # object.
196     #
197     # The most common thing to do is probably to call $template->param() to
198     # add a new custom parameter to the template.
199     # TODO: how do we call $template->param()?
200     kwargs = _arglist_to_dict(args)
201     debug("hook `pagetemplate' called with arguments %s" % kwargs)
202     raise NotImplementedError
203 #proxy.hook('pagetemplate', pagetemplate_demo)
204
205 def templatefile_demo(proxy, *args):
206     # This hook allows plugins to change the template that is used for a page
207     # in the wiki. The hook is passed a "page" parameter, and should return
208     # the name of the template file to use, or undef if it doesn't want to
209     # change the default ("page.tmpl"). Template files are looked for in
210     # /usr/share/ikiwiki/templates by default.
211     #
212     kwargs = _arglist_to_dict(args)
213     debug("hook `templatefile' called with arguments %s" % kwargs)
214     return None #leave the default
215 proxy.hook('templatefile', templatefile_demo)
216
217 def sanitize_demo(proxy, *args):
218     # Use this to implement html sanitization or anything else that needs to
219     # modify the body of a page after it has been fully converted to html.
220     #
221     # The function is passed named parameters: "page" and "content", and
222     # should return the sanitized content.
223     kwargs = _arglist_to_dict(args)
224     debug("hook `sanitize' called with arguments %s" % kwargs)
225     return kwargs['content']
226 proxy.hook('sanitize', sanitize_demo)
227
228 def format_demo(proxy, *args):
229     # The difference between format and sanitize is that sanitize only acts on
230     # the page body, while format can modify the entire html page including
231     # the header and footer inserted by ikiwiki, the html document type, etc.
232     #
233     # The function is passed named parameters: "page" and "content", and
234     # should return the formatted content.
235     kwargs = _arglist_to_dict(args)
236     debug("hook `format' called with arguments %s" % kwargs)
237     return kwargs['content']
238 proxy.hook('format', format_demo)
239
240 def delete_demo(proxy, *args):
241     # Each time a page or pages is removed from the wiki, the referenced
242     # function is called, and passed the names of the source files that were
243     # removed.
244     debug("hook `delete' called with arguments %s" % str(args))
245 proxy.hook('delete', delete_demo)
246
247 def change_demo(proxy, *args):
248     # Each time ikiwiki renders a change or addition (but not deletion) to the
249     # wiki, the referenced function is called, and passed the names of the
250     # source files that were rendered.
251     debug("hook `change' called with arguments %s" % str(args))
252 proxy.hook('change', change_demo)
253
254 def cgi_demo(proxy, *args):
255     # Use this to hook into ikiwiki's cgi script. Each registered cgi hook is
256     # called in turn, and passed a CGI object. The hook should examine the
257     # parameters, and if it will handle this CGI request, output a page
258     # (including the http headers) and terminate the program.
259     #
260     # Note that cgi hooks are called as early as possible, before any ikiwiki
261     # state is loaded, and with no session information.
262     debug("hook `cgi' called with arguments %s" % str(args))
263     raise NotImplementedError
264 #proxy.hook('cgi', cgi_demo)
265
266 def auth_demo(proxy, *args):
267     # This hook can be used to implement a different authentication method
268     # than the standard web form. When a user needs to be authenticated, each
269     # registered auth hook is called in turn, and passed a CGI object and
270     # a session object.
271     #
272     # If the hook is able to authenticate the user, it should set the session
273     # object's "name" parameter to the authenticated user's name. Note that if
274     # the name is set to the name of a user who is not registered, a basic
275     # registration of the user will be automatically performed.
276     #
277     # TODO: how do we set the session parameter?
278     debug("hook `auth' called with arguments %s" % str(args))
279     raise NotImplementedError
280 #proxy.hook('auth', auth_demo)
281
282 def sessioncgi_demo(proxy, *args):
283     # Unlike the cgi hook, which is run as soon as possible, the sessioncgi
284     # hook is only run once a session object is available. It is passed both
285     # a CGI object and a session object. To check if the user is in fact
286     # signed in, you can check if the session object has a "name" parameter
287     # set.
288     debug("hook `sessioncgi' called with arguments %s" % str(args))
289     raise NotImplementedError
290 #proxy.hook('sessioncgi', sessioncgi_demo)
291
292 def canedit_demo(proxy, *args):
293     # This hook can be used to implement arbitrary access methods to control
294     # when a page can be edited using the web interface (commits from revision
295     # control bypass it). When a page is edited, each registered canedit hook
296     # is called in turn, and passed the page name, a CGI object, and a session
297     # object.
298     #
299     # If the hook has no opinion about whether the edit can proceed, return
300     # undef, and the next plugin will be asked to decide. If edit can proceed,
301     # the hook should return "". If the edit is not allowed by this hook, the
302     # hook should return an error message for the user to see, or a function
303     # that can be run to log the user in or perform other action necessary for
304     # them to be able to edit the page.
305     #
306     # This hook should avoid directly redirecting the user to a signin page,
307     # since it's sometimes used to test to see which pages in a set of pages
308     # a user can edit.
309     #
310     # TODO: how do we return a function?
311     debug("hook `canedit' called with arguments %s" % str(args))
312     raise NotImplementedError
313 #proxy.hook('canedit', canedit_demo)
314
315 def editcontent_demo(proxy, *args):
316     # This hook is called when a page is saved (or previewed) using the web
317     # interface. It is passed named parameters: content, page, cgi, and
318     # session. These are, respectively, the new page content as entered by the
319     # user, the page name, a CGI object, and the user's CGI::Session.
320     #
321     # It can modify the content as desired, and should return the content.
322     kwargs = _arglist_to_dict(args)
323     debug("hook `editcontent' called with arguments %s" % kwargs)
324     return kwargs['content']
325 proxy.hook('editcontent', editcontent_demo)
326
327 def formbuilder_setup_demo(proxy, *args):
328     # These hooks allow tapping into the parts of ikiwiki that use
329     # CGI::FormBuilder to generate web forms. These hooks are passed named
330     # parameters: cgi, session, form, and buttons. These are, respectively,
331     # the CGI object, the user's CGI::Session, a CGI::FormBuilder, and
332     # a reference to an array of names of buttons to go on the form.
333     #
334     # Each time a form is set up, the formbuilder_setup hook is called.
335     # Typically the formbuilder_setup hook will check the form's title, and if
336     # it's a form that it needs to modify, will call various methods to
337     # add/remove/change fields, tweak the validation code for the fields, etc.
338     # It will not validate or display the form.
339     #
340     # Just before a form is displayed to the user, the formbuilder hook is
341     # called. It can be used to validate the form, but should not display it.
342     #
343     # TODO: how do we modify the form?
344     kwargs = _arglist_to_dict(args)
345     debug("hook `formbuilder_setup' called with arguments %s" % kwargs)
346     raise NotImplementedError
347     return kwargs['content']
348 #proxy.hook('formbuilder_setup', formbuilder_setup_demo)
349
350 def formbuilder_demo(proxy, *args):
351     # These hooks allow tapping into the parts of ikiwiki that use
352     # CGI::FormBuilder to generate web forms. These hooks are passed named
353     # parameters: cgi, session, form, and buttons. These are, respectively,
354     # the CGI object, the user's CGI::Session, a CGI::FormBuilder, and
355     # a reference to an array of names of buttons to go on the form.
356     #
357     # Each time a form is set up, the formbuilder_setup hook is called.
358     # Typically the formbuilder_setup hook will check the form's title, and if
359     # it's a form that it needs to modify, will call various methods to
360     # add/remove/change fields, tweak the validation code for the fields, etc.
361     # It will not validate or display the form.
362     #
363     # Just before a form is displayed to the user, the formbuilder hook is
364     # called. It can be used to validate the form, but should not display it.
365     # TODO: how do we modify the form?
366     kwargs = _arglist_to_dict(args)
367     debug("hook `formbuilder' called with arguments %s" % kwargs)
368     raise NotImplementedError
369     return kwargs['content']
370 #proxy.hook('formbuilder', formbuilder_demo)
371
372 def savestate_demo(proxy, *args):
373     # This hook is called wheneven ikiwiki normally saves its state, just
374     # before the state is saved. The function can save other state, modify
375     # values before they're saved, etc.
376     #
377     # TODO: how?
378     debug("hook `savestate' called with arguments %s" % str(args))
379     raise NotImplementedError
380 #proxy.hook('savestate', savestate_demo)
381
382 proxy.run()