changeset 2636:e76789b0e537

Merge main.
author Karol 'grzywacz' Nowak <grzywacz@sul.uni.lodz.pl>
date Thu, 26 Jul 2007 04:39:03 +0200
parents 8ec6cc1a909f (current diff) 80eac6fc152a (diff)
children edf9a449fe9d
files
diffstat 11 files changed, 403 insertions(+), 233 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Thu Jul 26 04:38:12 2007 +0200
+++ b/.hgignore	Thu Jul 26 04:39:03 2007 +0200
@@ -1,10 +1,7 @@
-\{arch\}
-.*\.arch-ids/.*
 .*\.py[co]
-sa
-cover
 tests/wiki
 wiki/underlay
 wiki/data/edit-log
 wiki/data/event-log
 wiki/data/cache
+
--- a/.hgtags	Thu Jul 26 04:38:12 2007 +0200
+++ b/.hgtags	Thu Jul 26 04:39:03 2007 +0200
@@ -14,3 +14,4 @@
 e5bd284ca29e5ce69a4593c45ba4cf4d1b74183d 1.5.3
 cdfb01bec1224c9ac9c83ef9bae75ed48ca66340 1.6a
 fbe43f9574f1221baea7b1f4077476fdbc155d8f SOC2006-START
+8f130438d8e8146262900e37bdf9d687c9690424 SOC2007-START
--- a/MoinMoin/_tests/test_wikiutil.py	Thu Jul 26 04:38:12 2007 +0200
+++ b/MoinMoin/_tests/test_wikiutil.py	Thu Jul 26 04:39:03 2007 +0200
@@ -452,6 +452,53 @@
         py.test.raises(ValueError, wikiutil.get_float, self.request, u'wrong')
         py.test.raises(ValueError, wikiutil.get_float, self.request, u'"47.11"') # must not be quoted!
 
+    def testGetComplex(self):
+        tests = [
+            # default testing for None value
+            (None, None, None, None),
+            (None, None, -23.42, -23.42),
+            (None, None, 42.23, 42.23),
+
+            # some real values
+            (u'0', None, None, 0),
+            (u'42.23', None, None, 42.23),
+            (u'-23.42', None, None, -23.42),
+            (u'-23.42E3', None, None, -23.42E3),
+            (u'23.42E-3', None, None, 23.42E-3),
+            (u'23.42E-3+3.04j', None, None, 23.42E-3+3.04j),
+            (u'3.04j', None, None, 3.04j),
+            (u'-3.04j', None, None, -3.04j),
+            (u'23.42E-3+3.04i', None, None, 23.42E-3+3.04j),
+            (u'3.04i', None, None, 3.04j),
+            (u'-3.04i', None, None, -3.04j),
+            (u'-3', None, None, -3L),
+            (u'-300000000000000000000', None, None, -300000000000000000000L),
+        ]
+        for arg, name, default, expected in tests:
+            assert wikiutil.get_complex(self.request, arg, name, default) == expected
+
+    def testGetComplexRaising(self):
+        # wrong default type
+        py.test.raises(AssertionError, wikiutil.get_complex, self.request, None, None, u'42')
+
+        # anything except None or unicode raises TypeError
+        py.test.raises(TypeError, wikiutil.get_complex, self.request, True)
+        py.test.raises(TypeError, wikiutil.get_complex, self.request, 42)
+        py.test.raises(TypeError, wikiutil.get_complex, self.request, 42.0)
+        py.test.raises(TypeError, wikiutil.get_complex, self.request, 3j)
+        py.test.raises(TypeError, wikiutil.get_complex, self.request, '')
+        py.test.raises(TypeError, wikiutil.get_complex, self.request, tuple())
+        py.test.raises(TypeError, wikiutil.get_complex, self.request, [])
+        py.test.raises(TypeError, wikiutil.get_complex, self.request, {})
+
+        # any value not convertable to int raises ValueError
+        py.test.raises(ValueError, wikiutil.get_complex, self.request, u'')
+        py.test.raises(ValueError, wikiutil.get_complex, self.request, u'3jj')
+        py.test.raises(ValueError, wikiutil.get_complex, self.request, u'3Ij')
+        py.test.raises(ValueError, wikiutil.get_complex, self.request, u'3i-3i')
+        py.test.raises(ValueError, wikiutil.get_complex, self.request, u'wrong')
+        py.test.raises(ValueError, wikiutil.get_complex, self.request, u'"47.11"') # must not be quoted!
+
     def testGetUnicode(self):
         tests = [
             # default testing for None value
@@ -481,16 +528,6 @@
         py.test.raises(TypeError, wikiutil.get_unicode, self.request, {})
 
 
-def _test_invoke_int(i=int):
-    assert i == 1
-
-
-def _test_invoke_int_fixed(a, b, i=int):
-    assert a == 7
-    assert b == 8
-    assert i == 1 or i is None
-
-
 class TestExtensionInvoking:
     def _test_invoke_bool(self, b=bool):
         assert b is False
@@ -524,6 +561,14 @@
         assert _kwargs == expect
 
     def testInvoke(self):
+        def _test_invoke_int(i=int):
+            assert i == 1
+
+        def _test_invoke_int_fixed(a, b, i=int):
+            assert a == 7
+            assert b == 8
+            assert i == 1 or i is None
+
         ief = wikiutil.invoke_extension_function
         ief(self.request, self._test_invoke_bool, u'False')
         ief(self.request, self._test_invoke_bool, u'b=False')
@@ -587,4 +632,105 @@
         py.test.raises(ValueError, ief, self.request,
                        self._test_invoke_float_required, u',')
 
+    def testConstructors(self):
+        ief = wikiutil.invoke_extension_function
+
+        # new style class
+        class TEST1(object):
+            def __init__(self, a=int):
+                self.constructed = True
+                assert a == 7
+
+        class TEST2(TEST1):
+            pass
+
+        obj = ief(self.request, TEST1, u'a=7')
+        assert isinstance(obj, TEST1)
+        assert obj.constructed
+        py.test.raises(ValueError, ief, self.request, TEST1, u'b')
+
+        obj = ief(self.request, TEST2, u'a=7')
+        assert isinstance(obj, TEST1)
+        assert isinstance(obj, TEST2)
+        assert obj.constructed
+        py.test.raises(ValueError, ief, self.request, TEST2, u'b')
+
+        # old style class
+        class TEST3:
+            def __init__(self, a=int):
+                self.constructed = True
+                assert a == 7
+
+        class TEST4(TEST3):
+            pass
+
+        obj = ief(self.request, TEST3, u'a=7')
+        assert isinstance(obj, TEST3)
+        assert obj.constructed
+        py.test.raises(ValueError, ief, self.request, TEST3, u'b')
+
+        obj = ief(self.request, TEST4, u'a=7')
+        assert isinstance(obj, TEST3)
+        assert isinstance(obj, TEST4)
+        assert obj.constructed
+        py.test.raises(ValueError, ief, self.request, TEST4, u'b')
+
+    def testFailing(self):
+        ief = wikiutil.invoke_extension_function
+
+        py.test.raises(TypeError, ief, self.request, hex, u'15')
+        py.test.raises(TypeError, ief, self.request, cmp, u'15')
+        py.test.raises(AttributeError, ief, self.request, unicode, u'15')
+
+    def testAllDefault(self):
+        ief = wikiutil.invoke_extension_function
+
+        def has_many_defaults(a=1, b=2, c=3, d=4):
+            assert a == 1
+            assert b == 2
+            assert c == 3
+            assert d == 4
+            return True
+
+        assert ief(self.request, has_many_defaults, u'1, 2, 3, 4')
+        assert ief(self.request, has_many_defaults, u'2, 3, 4', [1])
+        assert ief(self.request, has_many_defaults, u'3, 4', [1, 2])
+        assert ief(self.request, has_many_defaults, u'4', [1, 2, 3])
+        assert ief(self.request, has_many_defaults, u'', [1, 2, 3, 4])
+        assert ief(self.request, has_many_defaults, u'd=4,c=3,b=2,a=1')
+        assert ief(self.request, has_many_defaults, u'd=4,c=3,b=2', [1])
+        assert ief(self.request, has_many_defaults, u'd=4,c=3', [1, 2])
+        assert ief(self.request, has_many_defaults, u'd=4', [1, 2, 3])
+
+    def testInvokeComplex(self):
+        ief = wikiutil.invoke_extension_function
+
+        def has_complex(a=complex, b=complex):
+            assert a == b
+            return True
+
+        assert ief(self.request, has_complex, u'3-3i, 3-3j')
+        assert ief(self.request, has_complex, u'2i, 2j')
+        assert ief(self.request, has_complex, u'b=2i, a=2j')
+        assert ief(self.request, has_complex, u'2.007, 2.007')
+        assert ief(self.request, has_complex, u'2.007', [2.007])
+        assert ief(self.request, has_complex, u'b=2.007', [2.007])
+
+
+class TestAnchorNames:
+    def test_anchor_name_encoding(self):
+        tests = [
+            # text                    expected output
+            (u'\xf6\xf6ll\xdf\xdf',   'A.2BAPYA9g-ll.2BAN8A3w-'),
+            (u'level 2',              'level_2'),
+            (u'',                     'A'),
+            (u'123',                  'A123'),
+        ]
+        for text, expected in tests:
+            yield self._check, text, expected
+
+    def _check(self, text, expected):
+        encoded = wikiutil.anchor_name_from_text(text)
+        assert expected == encoded
+
 coverage_modules = ['MoinMoin.wikiutil']
--- a/MoinMoin/formatter/text_html.py	Thu Jul 26 04:38:12 2007 +0200
+++ b/MoinMoin/formatter/text_html.py	Thu Jul 26 04:39:03 2007 +0200
@@ -947,7 +947,7 @@
         """
         _ = self.request.getText
         res = []
-        ci = self.request.makeUniqueID('CA-%s_%03d' % (code_id, self._code_area_num))
+        ci = self.request.make_unique_id('CA-%s_%03d' % (code_id, self._code_area_num))
         if on:
             # Open a code area
             self._in_code_area = 1
@@ -1207,6 +1207,10 @@
             number = '.'.join([str(x) for x in self.request._fmt_hd_counters[self._show_section_numbers-1:]])
             if number: number += ". "
 
+        # make ID unique
+        if 'id' in kw:
+            kw['id'] = self.request.make_unique_id(kw['id'])
+
         # Add space before heading, easier to check source code
         result = '\n' + self._open('h%d' % heading_depth, **kw)
 
--- a/MoinMoin/macro/BR.py	Thu Jul 26 04:38:12 2007 +0200
+++ b/MoinMoin/macro/BR.py	Thu Jul 26 04:39:03 2007 +0200
@@ -10,6 +10,8 @@
 
 Dependencies = []
 
-def execute(macro, args):
+def macro_BR(macro):
+    """
+    Creates a linebreak.
+    """
     return macro.formatter.linebreak(0)
-
--- a/MoinMoin/macro/Include.py	Thu Jul 26 04:38:12 2007 +0200
+++ b/MoinMoin/macro/Include.py	Thu Jul 26 04:39:03 2007 +0200
@@ -16,10 +16,13 @@
 #Dependencies = ["pages"] # included page
 Dependencies = ["time"] # works around MoinMoinBugs/TableOfContentsLacksLinks
 
+generates_headings = True
+
 import re, StringIO
-from MoinMoin import wikiutil
+from MoinMoin import wikiutil, config
 from MoinMoin.Page import Page
 
+
 _sysmsg = '<p><strong class="%s">%s</strong></p>'
 
 ## keep in sync with TableOfContents macro!
@@ -188,22 +191,13 @@
                               macro.formatter.text(heading) +
                               macro.formatter.heading(0, level))
             else:
-                import sha
-                from MoinMoin import config
-                # this heading id might produce duplicate ids,
-                # if the same page is included multiple times
-                # Encode stuf we feed into sha module.
-                pntt = (inc_name + heading).encode(config.charset)
-                hid = "head-" + sha.new(pntt).hexdigest()
-                request._page_headings.setdefault(pntt, 0)
-                request._page_headings[pntt] += 1
-                if request._page_headings[pntt] > 1:
-                    hid += '-%d' % (request._page_headings[pntt], )
-                result.append(
-                    macro.formatter.heading(1, level, id=hid) +
-                    inc_page.link_to(request, heading, css_class="include-heading-link") +
-                    macro.formatter.heading(0, level)
-                )
+                hid = wikiutil.anchor_name_from_text(heading)
+                link = inc_page.link_to(request, heading, css_class="include-heading-link")
+                result.extend([
+                    macro.formatter.heading(1, level, id=hid),
+                    macro.formatter.rawHTML(link),
+                    macro.formatter.heading(0, level),
+                ])
 
         # set or increment include marker
         this_page._macroInclude_pagelist[inc_name] = \
@@ -213,7 +207,7 @@
         strfile = StringIO.StringIO()
         request.redirect(strfile)
         try:
-            cid = request.makeUniqueID("Include_%s" % wikiutil.quoteWikinameURL(inc_page.page_name))
+            cid = request.make_unique_id("Include_%s" % wikiutil.quoteWikinameURL(inc_page.page_name))
             inc_page.send_page(content_only=1, content_id=cid,
                                omit_footnotes=True)
             result.append(strfile.getvalue())
--- a/MoinMoin/macro/TableOfContents.py	Thu Jul 26 04:38:12 2007 +0200
+++ b/MoinMoin/macro/TableOfContents.py	Thu Jul 26 04:39:03 2007 +0200
@@ -2,172 +2,155 @@
 """
     MoinMoin - TableOfContents Macro
 
-    Optional integer argument: maximal depth of listing.
-
-    @copyright: 2000-2002 Juergen Hermann <jh@web.de>
+    @copyright: 2007 MoinMoin:JohannesBerg
     @license: GNU GPL, see COPYING for details.
 """
 
-import re, sha
-from MoinMoin import config, wikiutil
-
-#Dependencies = ["page"]
-Dependencies = ["time"] # works around MoinMoinBugs/TableOfContentsLacksLinks
-
-# from macro Include (keep in sync!)
-_arg_heading = r'(?P<heading>,)\s*(|(?P<hquote>[\'"])(?P<htext>.+?)(?P=hquote))'
-_arg_level = r',\s*(?P<level>\d*)'
-_arg_from = r'(,\s*from=(?P<fquote>[\'"])(?P<from>.+?)(?P=fquote))?'
-_arg_to = r'(,\s*to=(?P<tquote>[\'"])(?P<to>.+?)(?P=tquote))?'
-_arg_sort = r'(,\s*sort=(?P<sort>(ascending|descending)))?'
-_arg_items = r'(,\s*items=(?P<items>\d+))?'
-_arg_skipitems = r'(,\s*skipitems=(?P<skipitems>\d+))?'
-_arg_titlesonly = r'(,\s*(?P<titlesonly>titlesonly))?'
-_arg_editlink = r'(,\s*(?P<editlink>editlink))?'
-_args_re_pattern = r'^(?P<name>[^,]+)(%s(%s)?%s%s%s%s%s%s%s)?$' % (
-    _arg_heading, _arg_level, _arg_from, _arg_to, _arg_sort, _arg_items,
-    _arg_skipitems, _arg_titlesonly, _arg_editlink)
-
-# from Include, too, but with extra htext group around header text
-_title_re = r"^(?P<heading>(?P<hmarker>=+)\s(?P<htext>.*)\s(?P=hmarker))$"
-
-class TableOfContents:
-    """
-    TOC Macro wraps all global variables without disturbing threads
-    """
-
-    def __init__(self, macro, args):
-        self.macro = macro
-        self._ = self.macro.request.getText
-
-        self.inc_re = re.compile(r"^\[\[Include\((.*)\)\]\]")
-        self.arg_re = re.compile(_args_re_pattern)
-        self.head_re = re.compile(_title_re) # single lines only
-        self.pre_re = re.compile(r'\{\{\{.+?\}\}\}', re.S)
-
-        self.result = []
-        self.baseindent = 0
-        self.indent = 0
-        self.lineno = 0
-        self.titles = {}
-
-        self.include_macro = None
-
-        try:
-            self.mindepth = int(macro.request.getPragma('section-numbers', 1))
-        except (ValueError, TypeError):
-            self.mindepth = 1
-
-        try:
-            self.maxdepth = max(int(args), 1)
-        except (ValueError, TypeError):
-            self.maxdepth = 99
-
-    def IncludeMacro(self, *args, **kwargs):
-        if self.include_macro is None:
-            self.include_macro = wikiutil.importPlugin(self.macro.request.cfg,
-                                                       'macro', "Include")
-        return self.pre_re.sub('', self.include_macro(*args, **kwargs)).split('\n')
-
-    def run(self):
-        _ = self._
-        self.result.append(self.macro.formatter.div(1, css_class="table-of-contents"))
-        self.result.append(self.macro.formatter.paragraph(1, css_class="table-of-contents-heading"))
-        self.result.append(self.macro.formatter.escapedText(_('Contents')))
-        self.result.append(self.macro.formatter.paragraph(0))
-
-        self.process_lines(self.pre_re.sub('', self.macro.parser.raw).split('\n'),
-                           self.macro.formatter.page.page_name)
-        # Close pending lists
-        for dummy in range(self.baseindent, self.indent):
-            self.result.append(self.macro.formatter.listitem(0))
-            self.result.append(self.macro.formatter.number_list(0))
-        self.result.append(self.macro.formatter.div(0))
-        return ''.join(self.result)
+import re
+from MoinMoin.formatter import FormatterBase
+from MoinMoin.Page import Page
+from MoinMoin import wikiutil
 
-    def process_lines(self, lines, pagename):
-        for line in lines:
-            # Filter out the headings
-            self.lineno = self.lineno + 1
-            match = self.inc_re.match(line)
-            if match:
-                # this is an [[Include()]] line.
-                # now parse the included page and do the work on it.
-
-                ## get heading and level from Include() line.
-                tmp = self.arg_re.match(match.group(1))
-                if tmp and tmp.group("name"):
-                    inc_pagename = tmp.group("name")
-                else:
-                    # no pagename?  ignore it
-                    continue
-                if tmp.group("heading") and tmp.group("hquote"):
-                    if tmp.group("htext"):
-                        heading = tmp.group("htext")
-                    else:
-                        heading = inc_pagename
-                    if tmp.group("level"):
-                        level = int(tmp.group("level"))
-                    else:
-                        level = 1
-                    inc_page_lines = ["%s %s %s" % ("=" * level, heading, "=" * level)]
-                else:
-                    inc_page_lines = []
-
-                inc_page_lines = inc_page_lines + self.IncludeMacro(self.macro, match.group(1), called_by_toc=1)
-
-                self.process_lines(inc_page_lines, inc_pagename)
-            else:
-                self.parse_line(line, pagename)
 
-    def parse_line(self, line, pagename):
-        # FIXME this also finds "headlines" in {{{ code sections }}}:
-        match = self.head_re.match(line)
-        if not match:
-            return
-        title_text = match.group('htext').strip()
-        pntt = pagename + title_text
-        self.titles.setdefault(pntt, 0)
-        self.titles[pntt] += 1
-
-        # Get new indent level
-        newindent = len(match.group('hmarker'))
-        if newindent > self.maxdepth or newindent < self.mindepth:
-            return
-        if not self.indent:
-            self.baseindent = newindent - 1
-            self.indent = self.baseindent
-
-        # Close lists
-        for dummy in range(0, self.indent-newindent):
-            self.result.append(self.macro.formatter.listitem(0))
-            self.result.append(self.macro.formatter.number_list(0))
+Dependencies = ['page']
 
-        # Open Lists
-        for dummy in range(0, newindent-self.indent):
-            self.result.append(self.macro.formatter.number_list(1))
-            self.result.append(self.macro.formatter.listitem(1))
-
-        # Add the heading
-        unique_id = ''
-        if self.titles[pntt] > 1:
-            unique_id = '-%d' % (self.titles[pntt], )
-
-        # close last listitem if same level
-        if self.indent == newindent:
-            self.result.append(self.macro.formatter.listitem(0))
+class TOCFormatter(FormatterBase):
+    def __init__(self, request, **kw):
+        FormatterBase.__init__(self, request, **kw)
+        self.in_heading = False
+        self.collected_headings = request._tocfm_collected_headings
 
-        if self.indent >= newindent:
-            self.result.append(self.macro.formatter.listitem(1))
-        self.result.append(self.macro.formatter.anchorlink(1,
-            "head-" + sha.new(pntt.encode(config.charset)).hexdigest() + unique_id) +
-                           self.macro.formatter.text(title_text) +
-                           self.macro.formatter.anchorlink(0))
+    def _text(self, text):
+        if self.in_heading:
+            self.collected_headings[-1][2] += text
+        return text
 
-        # Set new indent level
-        self.indent = newindent
+    def heading(self, on, depth, **kw):
+        id = kw.get('id', None)
+        if not id is None:
+            id = self.request.make_unique_id(kw['id'])
+        self.in_heading = on
+        if on:
+            self.collected_headings.append([depth, id, u''])
+        return ''
 
-def execute(macro, args):
-    toc = TableOfContents(macro, args)
-    return toc.run()
+    def macro(self, macro_obj, name, args):
+        try:
+            # plugins that are defined in the macro class itself
+            # can't generate headings this way, but that's fine
+            gen_headings = wikiutil.importPlugin(self.request.cfg, 'macro',
+                                                 name, 'generates_headings')
+            if gen_headings:
+                return FormatterBase.macro(self, macro_obj, name, args)
+        except (wikiutil.PluginMissingError, wikiutil.PluginAttributeError):
+            pass
+        return ''
 
+    def _anything_return_empty(self, *args, **kw):
+        return ''
+
+    lang = _anything_return_empty
+    sysmsg = _anything_return_empty
+    startDocument = _anything_return_empty
+    endDocument = _anything_return_empty
+    startContent = _anything_return_empty
+    endContent = _anything_return_empty
+    pagelink = _anything_return_empty
+    interwikilink = _anything_return_empty
+    url = _anything_return_empty
+    attachment_link = _anything_return_empty
+    attachment_image = _anything_return_empty
+    attachment_drawing = _anything_return_empty
+    attachment_inlined = _anything_return_empty
+    anchordef = _anything_return_empty
+    line_anchordef = _anything_return_empty
+    anchorlink = _anything_return_empty
+    line_anchorlink = _anything_return_empty
+    image = _anything_return_empty
+    smiley = _anything_return_empty
+    nowikiword = _anything_return_empty
+    strong = _anything_return_empty
+    emphasis = _anything_return_empty
+    underline = _anything_return_empty
+    highlight = _anything_return_empty
+    sup = _anything_return_empty
+    sub = _anything_return_empty
+    strike = _anything_return_empty
+    code = _anything_return_empty
+    preformatted = _anything_return_empty
+    small = _anything_return_empty
+    big = _anything_return_empty
+    code_area = _anything_return_empty
+    code_line = _anything_return_empty
+    code_token = _anything_return_empty
+    linebreak = _anything_return_empty
+    paragraph = _anything_return_empty
+    rule = _anything_return_empty
+    icon = _anything_return_empty
+    number_list = _anything_return_empty
+    bullet_list = _anything_return_empty
+    listitem = _anything_return_empty
+    definition_list = _anything_return_empty
+    definition_term = _anything_return_empty
+    definition_desc = _anything_return_empty
+    table = _anything_return_empty
+    table_row = _anything_return_empty
+    table_cell = _anything_return_empty
+    _get_bang_args = _anything_return_empty
+    parser = _anything_return_empty
+    div = _anything_return_empty
+    span = _anything_return_empty
+    escapedText = _anything_return_empty
+    comment = _anything_return_empty
+
+def macro_TableOfContents(macro, maxdepth=int):
+    """
+Prints a table of contents.
+
+ maxdepth:: maximum depth the table of contents is generated for (defaults to unlimited)
+    """
+    if maxdepth is None:
+        maxdepth = 99
+
+    pname = macro.formatter.page.page_name
+
+    macro.request._tocfm_collected_headings = []
+    tocfm = TOCFormatter(macro.request)
+    p = Page(macro.request, pname, formatter=tocfm, rev=macro.request.rev)
+    output = macro.request.redirectedOutput(p.send_page,
+                                            content_only=True,
+                                            count_hit=False,
+                                            omit_footnotes=True)
+
+    macro.request.reset_unique_ids()
+
+    _ = macro.request.getText
+
+    result = [
+        macro.formatter.div(1, css_class="table-of-contents"),
+        macro.formatter.paragraph(1, css_class="table-of-contents-heading"),
+        macro.formatter.text(_('Contents', formatted=False)),
+        macro.formatter.paragraph(0),
+    ]
+
+    lastlvl = 0
+
+    for lvl, id, txt in macro.request._tocfm_collected_headings:
+        if lvl > maxdepth or not id:
+            continue
+        while lastlvl > lvl:
+            result.append(macro.formatter.number_list(0))
+            lastlvl -= 1
+        while lastlvl < lvl:
+            result.append(macro.formatter.number_list(1))
+            lastlvl += 1
+        result.extend([
+            macro.formatter.listitem(1),
+            macro.formatter.anchorlink(1, id),
+            macro.formatter.text(txt),
+            macro.formatter.anchorlink(0),
+            macro.formatter.listitem(0),
+        ])
+
+    result.append(macro.formatter.div(0))
+    return ''.join(result)
--- a/MoinMoin/parser/text_moin_wiki.py	Thu Jul 26 04:38:12 2007 +0200
+++ b/MoinMoin/parser/text_moin_wiki.py	Thu Jul 26 04:39:03 2007 +0200
@@ -11,6 +11,7 @@
 import re
 from MoinMoin import config, wikiutil, macro
 
+
 Dependencies = ['user'] # {{{#!wiki comment ... }}} has different output depending on the user's profile settings
 
 class Parser:
@@ -157,7 +158,6 @@
         self.no_862 = False
         self.in_table = 0
         self.inhibit_p = 0 # if set, do not auto-create a <p>aragraph
-        self.titles = request._page_headings
 
         # holds the nesting level (in chars) of open lists
         self.list_indents = []
@@ -755,29 +755,21 @@
 
     def _heading_repl(self, word):
         """Handle section headings."""
-        import sha
-
         h = word.strip()
         level = 1
         while h[level:level+1] == '=':
             level += 1
         depth = min(5, level)
 
-        # FIXME: needed for Included pages but might still result in unpredictable results
-        # when included the same page multiple times
         title_text = h[level:-level].strip()
-        pntt = self.formatter.page.page_name + title_text
-        self.titles.setdefault(pntt, 0)
-        self.titles[pntt] += 1
+        id = wikiutil.anchor_name_from_text(title_text)
 
-        unique_id = ''
-        if self.titles[pntt] > 1:
-            unique_id = '-%d' % self.titles[pntt]
-        result = self._closeP()
-        result += self.formatter.heading(1, depth, id="head-"+sha.new(pntt.encode(config.charset)).hexdigest()+unique_id)
-
-        return (result + self.formatter.text(title_text) +
-                self.formatter.heading(0, depth))
+        return ''.join([
+            self._closeP(),
+            self.formatter.heading(1, depth, id=id),
+            self.formatter.text(title_text),
+            self.formatter.heading(0, depth),
+        ])
 
     def _parser_repl(self, word):
         """Handle parsed code displays."""
--- a/MoinMoin/request/__init__.py	Thu Jul 26 04:38:12 2007 +0200
+++ b/MoinMoin/request/__init__.py	Thu Jul 26 04:39:03 2007 +0200
@@ -152,8 +152,6 @@
         # Pages meta data that we collect in one request
         self.pages = {}
 
-        self._page_headings = {}
-
         self.user_headers = []
         self.cacheable = 0 # may this output get cached by http proxies/caches?
         self.http_caching_disabled = 0 # see disableHttpCaching()
@@ -695,10 +693,6 @@
 
         # caches unique ids
         self._page_ids = {}
-        # keeps track of pagename/heading combinations
-        # parsers should use this dict and not a local one, so that
-        # macros like TableOfContents in combination with Include can work
-        self._page_headings = {}
 
         if hasattr(self, "_fmt_hd_counters"):
             del self._fmt_hd_counters
@@ -1386,10 +1380,12 @@
         from MoinMoin import failure
         failure.handle(self, err)
 
-    def makeUniqueID(self, base):
+    def make_unique_id(self, base):
         """
         Generates a unique ID using a given base name. Appends a running count to the base.
 
+        Needs to stay deterministic!
+
         @param base: the base of the id
         @type base: unicode
 
@@ -1402,7 +1398,10 @@
         self._page_ids[base] = count
         if count == 0:
             return base
-        return u'%s_%04d' % (base, count)
+        return u'%s-%d' % (base, count)
+
+    def reset_unique_ids(self):
+        self._page_ids = {}
 
     def httpDate(self, when=None, rfc='1123'):
         """ Returns http date string, according to rfc2068
--- a/MoinMoin/wikiutil.py	Thu Jul 26 04:38:12 2007 +0200
+++ b/MoinMoin/wikiutil.py	Thu Jul 26 04:39:03 2007 +0200
@@ -19,8 +19,7 @@
 
 from MoinMoin import config
 from MoinMoin.util import pysupport, lock
-from inspect import getargspec
-from types import MethodType
+from inspect import getargspec, isfunction, isclass, ismethod
 
 
 # Exceptions
@@ -1383,7 +1382,7 @@
     @returns: the integer value of the string (or default value)
     """
     _ = request.getText
-    assert default is None or isinstance(default, int)
+    assert default is None or isinstance(default, (int, long))
     if arg is None:
         return default
     elif not isinstance(arg, unicode):
@@ -1414,7 +1413,7 @@
     @returns: the float value of the string (or default value)
     """
     _ = request.getText
-    assert default is None or isinstance(default, float)
+    assert default is None or isinstance(default, (int, long, float))
     if arg is None:
         return default
     elif not isinstance(arg, unicode):
@@ -1428,7 +1427,40 @@
                     name, arg))
         else:
             raise ValueError(
-                _('Argument must be a boolean value, not "%s"') % arg)
+                _('Argument must be a floating point value, not "%s"') % arg)
+
+
+def get_complex(request, arg, name=None, default=None):
+    """
+    For use with values returned from parse_quoted_separated or given
+    as macro parameters, return a complex from a unicode string.
+    None is a valid input and yields the default value.
+
+    @param request: A request instance
+    @param arg: The argument, may be None or a unicode string
+    @param name: Name of the argument, for error messages
+    @param default: default return value if arg is None
+    @rtype: complex or None
+    @returns: the complex value of the string (or default value)
+    """
+    _ = request.getText
+    assert default is None or isinstance(default, (int, long, float, complex))
+    if arg is None:
+        return default
+    elif not isinstance(arg, unicode):
+        raise TypeError('Argument must be None or unicode')
+    try:
+        # allow writing 'i' instead of 'j'
+        arg = arg.replace('i', 'j').replace('I', 'j')
+        return complex(arg)
+    except ValueError:
+        if name:
+            raise ValueError(
+                _('Argument "%s" must be a complex value, not "%s"') % (
+                    name, arg))
+        else:
+            raise ValueError(
+                _('Argument must be a complex value, not "%s"') % arg)
 
 
 def get_unicode(request, arg, name=None, default=None):
@@ -1467,7 +1499,7 @@
     @rtype: unicode or None
     @returns: the unicode string (or default value)
     """
-    assert isinstance(choices, tuple) or isinstance(choices, list)
+    assert isinstance(choices, (tuple, list))
     if arg is None:
         return choices[0]
     elif not isinstance(arg, unicode):
@@ -1497,8 +1529,8 @@
         Initialise a required_arg
         @param argtype: the type the argument should have
         """
-        if not isinstance(argtype, type):
-            raise TypeError("argtype must be a type")
+        if not argtype in (bool, int, long, float, complex, unicode):
+            raise TypeError("argtype must be a valid type")
         self.argtype = argtype
 
 
@@ -1529,15 +1561,18 @@
 
         In other cases return the value itself.
         """
+        # if extending this, extend required_arg as well!
         if isinstance(default, bool):
             return get_bool(request, value, name, default)
-        elif isinstance(default, int) or isinstance(default, long):
+        elif isinstance(default, (int, long)):
             return get_int(request, value, name, default)
         elif isinstance(default, float):
             return get_float(request, value, name, default)
+        elif isinstance(default, complex):
+            return get_complex(request, value, name, default)
         elif isinstance(default, unicode):
             return get_unicode(request, value, name, default)
-        elif isinstance(default, tuple) or isinstance(default, list):
+        elif isinstance(default, (tuple, list)):
             return get_choice(request, value, name, default)
         elif default is bool:
             return get_bool(request, value, name)
@@ -1545,11 +1580,13 @@
             return get_int(request, value, name)
         elif default is float:
             return get_float(request, value, name)
+        elif default is complex:
+            return get_complex(request, value, name)
         elif isinstance(default, required_arg):
             return _convert_arg(request, value, default.argtype, name)
         return value
 
-    assert isinstance(fixed_args, list) or isinstance(fixed_args, tuple)
+    assert isinstance(fixed_args, (list, tuple))
 
     _ = request.getText
 
@@ -1573,10 +1610,18 @@
     else:
         positional = []
 
-    argnames, varargs, varkw, defaultlist = getargspec(function)
+    if isfunction(function) or ismethod(function):
+        argnames, varargs, varkw, defaultlist = getargspec(function)
+    elif isclass(function):
+        (argnames, varargs,
+         varkw, defaultlist) = getargspec(function.__init__.im_func)
+    else:
+        raise TypeError('function must be a function, method or class')
+
     # self is implicit!
-    if isinstance(function, MethodType):
+    if ismethod(function) or isclass(function):
         argnames = argnames[1:]
+
     fixed_argc = len(fixed_args)
     argnames = argnames[fixed_argc:]
     argc = len(argnames)
@@ -2054,6 +2099,13 @@
     lines = diff_text.diff(lines1, lines2, **kw)
     return lines
 
+def anchor_name_from_text(text):
+    quoted = urllib.quote_plus(text.encode('utf-7'))
+    res = quoted.replace('%', '.').replace('+', '_')
+    if not res[:1].isalpha():
+        return 'A%s' % res
+    return res
+
 
 ########################################################################
 ### Tickets - used by RenamePage and DeletePage
--- a/README	Thu Jul 26 04:38:12 2007 +0200
+++ b/README	Thu Jul 26 04:39:03 2007 +0200
@@ -3,7 +3,7 @@
 
 Copyright (c) 2000-2006 by Juergen Hermann <jh@web.de>
 Copyright (c) 2006-2007 The MoinMoin development team, see
-                        http://moinmoin.wikiwikiweb.de/MoinCoreTeamGroup
+                        http://moinmo.in/MoinCoreTeamGroup
 
 All rights reserved, see docs/licenses/COPYING for details.
 
@@ -28,7 +28,7 @@
 On the Web:
 
 http://purl.net/wiki/moin/       MoinMoin homepage, last seen at:
-                                 http://moinmoin.wikiwikiweb.de/
+                                 http://moinmo.in/
 
 This page also points to support resources and informations about MoinMoin
 development status and plans.