[Moin-user] Strange macro behaviour (1.8.0rc1)

Greg Ward gerg.ward+moin at gmail.com
Thu Oct 16 11:03:36 EDT 2008


Hi all --

as I mentioned a few days ago, I have written a custom IncludeRevision
macro to include a specific revision of another page.  Basically I stole
the bits of Include.py that looked relevant, hacked in revision
handling, and ignored the whole "what if the included page includes
other pages?" issue.

The heart of my new macro is this code, which should look familiar to
anyone who knows Include.py:

    def includePage(self, this_page, inc_name, inc_revision, result):
        [...access check...]

        fmt = self.macro.formatter.__class__(self.request, is_included=True)
        fmt._base_depth = self.macro.formatter._base_depth
        inc_page = Page(self.request, inc_name, formatter=fmt, rev=inc_revision)
        [...existence check...]

        # Format the included page and add to result.
        strfile = StringIO()
        self.request.redirect(strfile)
        try:
            inc_page.send_page(content_only=True,
                               omit_footnotes=True,
                               count_hit=False)
            result.append(strfile.getvalue())
            logging.debug("just included page %s (revision %r): content = %r",
                          inc_page.page_name, inc_page.rev, result[-1])
        finally:
            self.request.redirect()

In particular, the call to inc_page.send_page() with output redirected
to a StringIO is straight out of Include.py.

Now here's what's weird: the output of this macro is correct when I
preview a change, but mangled when I save the change.  However, if I
suppress caching the macro output with

  Dependencies = ["foo"]

the problem disappears and it behaves correctly in either circumstance.

In particular, I am inserting a page whose markup is:

"""
= XYZ =

This is revision 3 of a silly page to test macro `<<IncludeRevision>>`.

insert intelligent text here
"""

When everything is correct, that "= XYZ =" header line renders to HTML
as:

"""
<div dir="ltr" id="AuditTrail.2BAC8-04XYZ.content" lang="en"><span class="anchor" id="AuditTrail.2BAC8-04XYZ.top"></span>
<span class="anchor" id="AuditTrail.2BAC8-04XYZ.line-1"></span><p class="line867">
<h1 id="AuditTrail.2BAC8-04XYZ.XYZ">XYZ</h1>
"""

But when caching is enabled and I view the page normally, it renders as

"""
<<<>>><p class="line867"><<<>>>XYZ</h1>
<<<>>><<<>>>
"""

Yes, that is what I get from "View Source" in my browser -- MoinMoin is
sending "</h1> without a matching "<h1>", for starters.  Obviously it
doesn't render too well.

Any clues?  I'll attach my complete IncludeRevision.py in case anyone is
interested.  I don't see how my code could be causing this, but on the
other hand I don't see this behaviour with the stock Include macro.  And
again, I don't see it if I disable caching of my macro's output.  Weird.

Thanks --

        Greg
-------------- next part --------------
"""
Simplified version of the Include macro that lets you specify a page
revision number.  (Because of that, you can only include a single
page: it's unlikely that the same revision number would be meaningful
for a whole set of pages.)
"""

# XXX there is a lot of overlap here with MoinMoin's stock Include
# macro.  The original Include.py should, IMHO, be refactored to
# support reusing its code and/or extending it more easily.

from cStringIO import StringIO

from MoinMoin import wikiutil
from MoinMoin.Page import Page
from MoinMoin import log

logging = log.getLogger("MoinMoin.macro.IncludeRevision")

#Dependencies = ["foo"]                  # disable caching for the time being
Dependencies = []
generates_headings = True

logging.debug("module loading")

def macro_IncludeRevision(macro, pagename, revision=None):
    # urghh: if the requested page includes other pages, what revision
    # should we apply? this is where we need tags, not
    # revisions... sigh...
    return IncludeRevisionMacro(macro, macro.request).execute(pagename, revision)

class IncludeRevisionMacro(object):
    __slots__ = ['macro',
                 'request',
                 '_',                   # callable to translate messages
                ]

    def __init__(self, macro, request):
        # context
        logging.debug("__init__(): macro=%r, request=%r", macro, request)
        self.macro = macro
        self.request = request
        self._ = self.request.getText

    def execute(self, pagename, revision):
        # Just getting links for the current page -- do nothing.
        if self.request.mode_getpagelinks:
            return ""

        result = []                         # list of strings (or formatted things?)

        if revision is None:
            revision = 0                # latest, according Page class
        else:
            revision = int(revision)

        this_page = self.macro.formatter.page
        pagename = wikiutil.AbsPageName(this_page.page_name, pagename)
        self.includePage(this_page, pagename, revision, result)
        return ''.join(result)

    def includePage(self, this_page, inc_name, inc_revision, result):

        logging.debug("including page %r (revision %r) into %r",
                      inc_name, inc_revision, this_page.page_name)
        if not self.request.user.may.read(inc_name):
            # XXX stock Include macro is silent about access error
            result.append(self._sysmsg('error', 'Access denied: %s', inc_name))
            return

        fmt = self.macro.formatter.__class__(self.request, is_included=True)
        fmt._base_depth = self.macro.formatter._base_depth
        inc_page = Page(self.request, inc_name, formatter=fmt, rev=inc_revision)
        if not inc_page.exists():
            # XXX showing error message here is also inconsistent with
            # stock Include
            result.append(self._sysmsg('error', 'No such page: %s', inc_name))
            return

        # Format the included page and add to result.
        strfile = StringIO()
        self.request.redirect(strfile)
        try:
            inc_page.send_page(content_only=True,
                               omit_footnotes=True,
                               count_hit=False)
            result.append(strfile.getvalue())
            logging.debug("just included page %s (revision %r): content = %r",
                          inc_page.page_name, inc_page.rev, result[-1])
        finally:
            self.request.redirect()

    def _sysmsg(self, errtype, msg_fmt, *msg_args):
        # XXX what about HTML escaping?
        msg_fmt = self.__class__.__name__ + ': ' + self._(msg_fmt)
        html_msg_fmt = ('<p><strong class="%s">%s</strong></p>'
                        % (errtype, msg_fmt))
        return html_msg_fmt % msg_args


More information about the Moin-user mailing list