Back to index

moin  1.9.0~rc2
__init__.py
Go to the documentation of this file.
00001 # -*- coding: iso-8859-1 -*-
00002 """
00003     MoinMoin - Macro Implementation
00004 
00005     These macros are used by the wiki parser module to implement complex
00006     and/or dynamic page content.
00007 
00008     The canonical interface to plugin macros is their execute() function,
00009     which gets passed an instance of the Macro class.
00010 
00011     @copyright: 2000-2004 Juergen Hermann <jh@web.de>,
00012                 2006-2009 MoinMoin:ThomasWaldmann,
00013                 2007 MoinMoin:JohannesBerg
00014     @license: GNU GPL, see COPYING for details.
00015 """
00016 
00017 from MoinMoin.util import pysupport
00018 modules = pysupport.getPackageModules(__file__)
00019 
00020 from MoinMoin import log
00021 logging = log.getLogger(__name__)
00022 
00023 import re, time, os
00024 from MoinMoin import action, config, util
00025 from MoinMoin import wikiutil, i18n
00026 from MoinMoin.Page import Page
00027 from MoinMoin.datastruct.backends.wiki_dicts import WikiDict
00028 
00029 
00030 names = ["TitleSearch", "WordIndex", "TitleIndex", "GoTo",
00031          # Macros with arguments
00032          "Icon", "Date", "DateTime", "Anchor", "MailTo", "GetVal", "TemplateList",
00033 ]
00034 
00035 #############################################################################
00036 ### Helpers
00037 #############################################################################
00038 
00039 def getNames(cfg):
00040     if not hasattr(cfg.cache, 'macro_names'):
00041         lnames = names[:]
00042         lnames.extend(i18n.wikiLanguages().keys())
00043         lnames.extend(wikiutil.getPlugins('macro', cfg))
00044         cfg.cache.macro_names = lnames # remember it
00045     return cfg.cache.macro_names
00046 
00047 
00048 #############################################################################
00049 ### Macros - Handlers for <<macroname>> markup
00050 #############################################################################
00051 
00052 class Macro:
00053     """ Macro handler
00054 
00055     There are three kinds of macros:
00056      * Builtin Macros - implemented in this file and named macro_[name]
00057      * Language Pseudo Macros - any lang the wiki knows can be use as
00058        macro and is implemented here by _m_lang()
00059      * External macros - implemented in either MoinMoin.macro package, or
00060        in the specific wiki instance in the plugin/macro directory
00061     """
00062     defaultDependency = ["time"]
00063 
00064     Dependencies = {
00065         "TitleSearch": ["namespace"],
00066         "TemplateList": ["namespace"],
00067         "WordIndex": ["namespace"],
00068         "TitleIndex": ["namespace"],
00069         "Goto": [],
00070         "Icon": ["user"], # users have different themes and user prefs
00071         "Date": ["time"],
00072         "DateTime": ["time"],
00073         "Anchor": [],
00074         "Mailto": ["user"],
00075         "GetVal": ["pages"],
00076         }
00077 
00078     # we need the lang macros to execute when html is generated,
00079     # to have correct dir and lang html attributes
00080     for lang in i18n.wikiLanguages():
00081         Dependencies[lang] = []
00082 
00083 
00084     def __init__(self, parser):
00085         self.parser = parser
00086         #self.form --> gone, please use self.request.{form,args,values}
00087         self.request = self.parser.request
00088         self.formatter = self.request.formatter
00089         self._ = self.request.getText
00090         self.cfg = self.request.cfg
00091 
00092         # Initialized on execute
00093         self.name = None
00094 
00095     def execute(self, macro_name, args):
00096         """ Get and execute a macro
00097 
00098         Try to get a plugin macro, or a builtin macro or a language
00099         macro, or just raise ImportError.
00100         """
00101         self.name = macro_name
00102         try:
00103             call = wikiutil.importPlugin(self.cfg, 'macro', macro_name,
00104                                          function='macro_%s' % macro_name)
00105             execute = lambda _self, _args: wikiutil.invoke_extension_function(
00106                                                _self.request, call, _args, [_self])
00107         except wikiutil.PluginAttributeError:
00108             # fall back to old execute() method, no longer recommended
00109             execute = wikiutil.importPlugin(self.cfg, 'macro', macro_name)
00110         except wikiutil.PluginMissingError:
00111             try:
00112                 call = getattr(self, 'macro_%s' % macro_name)
00113                 execute = lambda _self, _args: wikiutil.invoke_extension_function(
00114                                                    _self.request, call, _args, [])
00115             except AttributeError:
00116                 if macro_name in i18n.wikiLanguages():
00117                     execute = self.__class__._m_lang
00118                 else:
00119                     raise ImportError("Cannot load macro %s" % macro_name)
00120         try:
00121             return execute(self, args)
00122         except Exception, err:
00123             # we do not want that a faulty macro aborts rendering of the page
00124             # and makes the wiki UI unusable (by emitting a Server Error),
00125             # thus, in case of exceptions, we just log the problem and return
00126             # some standard text.
00127             try:
00128                 page_spec = " (page: '%s')" % self.formatter.page.page_name
00129             except:
00130                 page_spec = ""
00131             logging.exception("Macro %s%s raised an exception:" % (self.name, page_spec))
00132             _ = self.request.getText
00133             return self.formatter.text(_('<<%(macro_name)s: execution failed [%(error_msg)s] (see also the log)>>') % {
00134                    'macro_name': self.name,
00135                    'error_msg': err.args[0], # note: str(err) or unicode(err) does not work for py2.4/5/6
00136                  })
00137 
00138     def _m_lang(self, text):
00139         """ Set the current language for page content.
00140 
00141             Language macro are used in two ways:
00142              * [lang] - set the current language until next lang macro
00143              * [lang(text)] - insert text with specific lang inside page
00144         """
00145         if text:
00146             return (self.formatter.lang(1, self.name) +
00147                     self.formatter.text(text) +
00148                     self.formatter.lang(0, self.name))
00149 
00150         self.request.current_lang = self.name
00151         return ''
00152 
00153     def get_dependencies(self, macro_name):
00154         if macro_name in self.Dependencies:
00155             return self.Dependencies[macro_name]
00156         try:
00157             return wikiutil.importPlugin(self.request.cfg, 'macro',
00158                                          macro_name, 'Dependencies')
00159         except wikiutil.PluginError:
00160             return self.defaultDependency
00161 
00162     def macro_TitleSearch(self):
00163         from MoinMoin.macro.FullSearch import search_box
00164         return search_box("titlesearch", self)
00165 
00166     def macro_TemplateList(self, needle=u'.+'):
00167         # TODO: this should be renamed (RegExPageNameList?), it does not list only Templates...
00168         _ = self._
00169         try:
00170             needle_re = re.compile(needle, re.IGNORECASE)
00171         except re.error, err:
00172             raise ValueError("Error in regex %r: %s" % (needle, err))
00173 
00174         # Get page list readable by current user, filtered by needle
00175         hits = self.request.rootpage.getPageList(filter=needle_re.search)
00176         hits.sort()
00177 
00178         result = []
00179         result.append(self.formatter.bullet_list(1))
00180         for pagename in hits:
00181             result.append(self.formatter.listitem(1))
00182             result.append(self.formatter.pagelink(1, pagename, generated=1))
00183             result.append(self.formatter.text(pagename))
00184             result.append(self.formatter.pagelink(0, pagename))
00185             result.append(self.formatter.listitem(0))
00186         result.append(self.formatter.bullet_list(0))
00187         return ''.join(result)
00188 
00189     def _make_index(self, word_re=u'.+'):
00190         """ make an index page (used for TitleIndex and WordIndex macro)
00191 
00192             word_re is a regex used for splitting a pagename into fragments
00193             matched by it (used for WordIndex). For TitleIndex, we just match
00194             the whole page name, so we only get one fragment that is the same
00195             as the pagename.
00196 
00197             TODO: _make_index could get a macro on its own, more powerful / less special than WordIndex and TitleIndex.
00198                   It should be able to filter for specific mimetypes, maybe match pagenames by regex (replace PageList?), etc.
00199         """
00200         _ = self._
00201         request = self.request
00202         fmt = self.formatter
00203         allpages = int(request.values.get('allpages', 0)) != 0
00204         # Get page list readable by current user, filter by isSystemPage if needed
00205         if allpages:
00206             pages = request.rootpage.getPageList()
00207         else:
00208             def nosyspage(name):
00209                 return not wikiutil.isSystemPage(request, name)
00210             pages = request.rootpage.getPageList(filter=nosyspage)
00211 
00212         word_re = re.compile(word_re, re.UNICODE)
00213         wordmap = {}
00214         for name in pages:
00215             for word in word_re.findall(name):
00216                 try:
00217                     if not wordmap[word].count(name):
00218                         wordmap[word].append(name)
00219                 except KeyError:
00220                     wordmap[word] = [name]
00221 
00222         # Sort ignoring case
00223         tmp = [(word.upper(), word) for word in wordmap]
00224         tmp.sort()
00225         all_words = [item[1] for item in tmp]
00226 
00227         index_letters = []
00228         current_letter = None
00229         output = []
00230         for word in all_words:
00231             letter = wikiutil.getUnicodeIndexGroup(word)
00232             if letter != current_letter:
00233                 anchor = "idx-%s" % letter
00234                 output.append(fmt.anchordef(anchor))
00235                 output.append(fmt.heading(1, 2))
00236                 output.append(fmt.text(letter.replace('~', 'Others')))
00237                 output.append(fmt.heading(0, 2))
00238                 current_letter = letter
00239             if letter not in index_letters:
00240                 index_letters.append(letter)
00241             links = wordmap[word]
00242             if len(links) and links[0] != word: # show word fragment as on WordIndex
00243                 output.append(fmt.strong(1))
00244                 output.append(word)
00245                 output.append(fmt.strong(0))
00246 
00247             output.append(fmt.bullet_list(1))
00248             links.sort()
00249             last_page = None
00250             for name in links:
00251                 if name == last_page:
00252                     continue
00253                 output.append(fmt.listitem(1))
00254                 output.append(Page(request, name).link_to(request, attachment_indicator=1))
00255                 output.append(fmt.listitem(0))
00256             output.append(fmt.bullet_list(0))
00257 
00258         def _make_index_key(index_letters):
00259             index_letters.sort()
00260             def letter_link(ch):
00261                 anchor = "idx-%s" % ch
00262                 return fmt.anchorlink(1, anchor) + fmt.text(ch.replace('~', 'Others')) + fmt.anchorlink(0)
00263             links = [letter_link(letter) for letter in index_letters]
00264             return ' | '.join(links)
00265 
00266         page = fmt.page
00267         allpages_txt = (_('Include system pages'), _('Exclude system pages'))[allpages]
00268         allpages_url = page.url(request, querystr={'allpages': allpages and '0' or '1'})
00269 
00270         output = [fmt.paragraph(1), _make_index_key(index_letters), fmt.linebreak(0),
00271                   fmt.url(1, allpages_url), fmt.text(allpages_txt), fmt.url(0), fmt.paragraph(0)] + output
00272         return u''.join(output)
00273 
00274 
00275     def macro_TitleIndex(self):
00276         return self._make_index()
00277 
00278     def macro_WordIndex(self):
00279         if self.request.isSpiderAgent: # reduce bot cpu usage
00280             return ''
00281         word_re = u'[%s][%s]+' % (config.chars_upper, config.chars_lower)
00282         return self._make_index(word_re=word_re)
00283 
00284     def macro_GoTo(self):
00285         """ Make a goto box
00286 
00287         @rtype: unicode
00288         @return: goto box html fragment
00289         """
00290         _ = self._
00291         html = [
00292             u'<form method="get" action="%s"><div>' % self.request.href(self.formatter.page.page_name),
00293             u'<div>',
00294             u'<input type="hidden" name="action" value="goto">',
00295             u'<input type="text" name="target" size="30">',
00296             u'<input type="submit" value="%s">' % _("Go To Page"),
00297             u'</div>',
00298             u'</form>',
00299             ]
00300         html = u'\n'.join(html)
00301         return self.formatter.rawHTML(html)
00302 
00303     def macro_Icon(self, icon=u''):
00304         # empty icon name isn't valid either
00305         if not icon:
00306             raise ValueError("You need to give a non-empty icon name")
00307         return self.formatter.icon(icon.lower())
00308 
00309     def __get_Date(self, args, format_date):
00310         _ = self._
00311         if args is None:
00312             tm = time.time() # always UTC
00313         elif len(args) >= 19 and args[4] == '-' and args[7] == '-' \
00314                 and args[10] == 'T' and args[13] == ':' and args[16] == ':':
00315             # we ignore any time zone offsets here, assume UTC,
00316             # and accept (and ignore) any trailing stuff
00317             try:
00318                 year, month, day = int(args[0:4]), int(args[5:7]), int(args[8:10])
00319                 hour, minute, second = int(args[11:13]), int(args[14:16]), int(args[17:19])
00320                 tz = args[19:] # +HHMM, -HHMM or Z or nothing (then we assume Z)
00321                 tzoffset = 0 # we assume UTC no matter if there is a Z
00322                 if tz:
00323                     sign = tz[0]
00324                     if sign in '+-':
00325                         tzh, tzm = int(tz[1:3]), int(tz[3:])
00326                         tzoffset = (tzh*60+tzm)*60
00327                         if sign == '-':
00328                             tzoffset = -tzoffset
00329                 tm = (year, month, day, hour, minute, second, 0, 0, 0)
00330             except ValueError, err:
00331                 raise ValueError("Bad timestamp %r: %s" % (args, err))
00332             # as mktime wants a localtime argument (but we only have UTC),
00333             # we adjust by our local timezone's offset
00334             try:
00335                 tm = time.mktime(tm) - time.timezone - tzoffset
00336             except (OverflowError, ValueError):
00337                 tm = 0 # incorrect, but we avoid an ugly backtrace
00338         else:
00339             # try raw seconds since epoch in UTC
00340             try:
00341                 tm = float(args)
00342             except ValueError, err:
00343                 raise ValueError("Bad timestamp %r: %s" % (args, err))
00344         return format_date(tm)
00345 
00346     def macro_Date(self, stamp=None):
00347         return self.__get_Date(stamp, self.request.user.getFormattedDate)
00348 
00349     def macro_DateTime(self, stamp=None):
00350         return self.__get_Date(stamp, self.request.user.getFormattedDateTime)
00351 
00352     def macro_Anchor(self, anchor=None):
00353         anchor = wikiutil.get_unicode(self.request, anchor, 'anchor', u'anchor')
00354         return self.formatter.anchordef(anchor)
00355 
00356     def macro_MailTo(self, email=unicode, text=u''):
00357         if not email:
00358             raise ValueError("You need to give an (obfuscated) email address")
00359 
00360         from MoinMoin.mail.sendmail import decodeSpamSafeEmail
00361 
00362         if self.request.user.valid:
00363             # decode address and generate mailto: link
00364             email = decodeSpamSafeEmail(email)
00365             result = (self.formatter.url(1, 'mailto:' + email, css='mailto') +
00366                       self.formatter.text(text or email) +
00367                       self.formatter.url(0))
00368         else:
00369             # unknown user, maybe even a spambot, so
00370             # just return text as given in macro args
00371 
00372             if text:
00373                 result = self.formatter.text(text + " ")
00374             else:
00375                 result = ''
00376 
00377             result += (self.formatter.code(1) +
00378                        self.formatter.text("<%s>" % email) +
00379                        self.formatter.code(0))
00380 
00381         return result
00382 
00383     def macro_GetVal(self, page=None, key=None):
00384         page = wikiutil.get_unicode(self.request, page, 'page')
00385 
00386         key = wikiutil.get_unicode(self.request, key, 'key')
00387         if page is None or key is None:
00388             raise ValueError("You need to give: pagename, key")
00389 
00390         d = self.request.dicts.get(page, {})
00391 
00392         # Check acl only if dictionary is defined on a wiki page.
00393         if isinstance(d, WikiDict) and not self.request.user.may.read(page):
00394             raise ValueError("You don't have enough rights on this page")
00395 
00396         result = d.get(key, '')
00397 
00398         return self.formatter.text(result)
00399