diff --git a/AUTHORS b/AUTHORS index f408718..60bdeb3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -23,6 +23,7 @@ CONTRIBUTORS are and/or have been: * Eric O'Connell -Special thanks to the python-forum.de guys, particularly (in alphabetic order): +Special thanks to the python-forum.de guys, particularly: +* EyDu * sma diff --git a/README.creole b/README.creole index 6659eee..c1a24b0 100644 --- a/README.creole +++ b/README.creole @@ -210,6 +210,7 @@ Note: In this case you must install **docutils**! See above. ** NEW: Add {{{<>}}} macro to create a table of contents list ** Bugfix for: AttributeError: 'CreoleParser' object has no attribute '_escaped_char_repl' ** Bugfix for: AttributeError: 'CreoleParser' object has no attribute '_escaped_url_repl' +** API Change: Callable macros will raise a TypeError instead of create a DeprecationWarning (Was removed in v0.5) * v1.1.1 - 2013-11-08 ** Bugfix: Setup script exited with error: can't copy 'README.creole': doesn't exist or not a regular file * v1.1.0 - 2013-10-28 diff --git a/creole/creole2html/emitter.py b/creole/creole2html/emitter.py index 1e7b30f..71fc314 100644 --- a/creole/creole2html/emitter.py +++ b/creole/creole2html/emitter.py @@ -20,6 +20,83 @@ from creole.shared.utils import string2dict + +class TableOfContent(object): + def __init__(self): + self.max_depth = None + self.headlines = [] + self._created = False + self._current_level = 0 + + def __call__(self, depth=None, **kwargs): + """Called when if the macro <> is defined when it is emitted.""" + if self._created: + return "<<toc>>" + + self._created = True + if depth is not None: + self.max_depth = depth + + return '<>' + + def add_headline(self, level, content): + """Add the current header to the toc.""" + if self.max_depth is None or level <= self.max_depth: + self.headlines.append( + (level, content) + ) + + def flat_list2nest_list(self, flat_list): + # this func code based on borrowed code from EyDu, Thanks! + # http://www.python-forum.de/viewtopic.php?p=258121#p258121 + tree = [] + stack = [tree] + + for index, element in flat_list: + stack_length = len(stack) + + if index > stack_length: + for _ in range(stack_length, index): + l = [] + stack[-1].append(l) + stack.append(l) + elif index < stack_length: + stack = stack[:index] + + stack[-1].append(element) + + return tree + + def nested_headlines2html(self, nested_headlines, level=0): + """Convert a python nested list like the one representing the toc to an html equivalent.""" + indent = "\t"*level + if isinstance(nested_headlines, TEXT_TYPE): + return '%s
  • %s
  • \n' % (indent, nested_headlines, nested_headlines) + elif isinstance(nested_headlines, list): + html = '%s
      \n' % indent + for elt in nested_headlines: + html += self.nested_headlines2html(elt, level + 1) + html += '%s
    ' % indent + if level > 0: + html += "\n" + return html + + def emit(self, document): + """Emit the toc where the <> macro was.""" + nested_headlines = self.flat_list2nest_list(self.headlines) + html = self.nested_headlines2html(nested_headlines) + + # FIXME: We should not use

    here, because it doesn't match + # if no newline was made before <> + if "

    <>

    " in document: + document = document.replace("

    <>

    ", html, 1) + else: + document = document.replace("<>", html, 1) + + return document + + + class HtmlEmitter(object): """ Generate HTML output for the document @@ -27,18 +104,34 @@ class HtmlEmitter(object): """ def __init__(self, root, macros=None, verbose=None, stderr=None): self.root = root - self.macros = macros + + + if callable(macros) == True: + # was a DeprecationWarning in the past + raise TypeError("Callable macros are not supported anymore!") + + if macros is None: + self.macros = {} + else: + self.macros = macros if not "toc" in root.used_macros: - self.has_toc = False + # The document has no <> + self.toc = None else: - self.has_toc = True - self.toc_max_depth = None - self.toc = ['root', []] - if self.macros is None: - self.macros = {'toc': self.create_toc} - elif isinstance(self.macros, dict) and "toc" not in self.macros: - self.macros['toc'] = self.create_toc + if isinstance(self.macros, dict): + if "toc" in self.macros: + self.toc = self.macros["toc"] + else: + self.toc = TableOfContent() + self.macros["toc"] = self.toc + else: + try: + self.toc = getattr(self.macros, "toc") + except AttributeError: + self.toc = TableOfContent() + self.macros.toc = self.toc + if verbose is None: self.verbose = 1 @@ -63,32 +156,6 @@ def html_escape(self, text): def attr_escape(self, text): return self.html_escape(text).replace('"', '"') - _toc_created = False - def create_toc(self, depth=None, **kwargs): - """Called when if the macro <> is defined when it is emitted.""" - if self._toc_created: - return "<<toc>>" - - self._toc_created = True - self.toc_max_depth = depth - - return '<>' - - def update_toc(self, level, content): - """Add the current header to the toc.""" - if self.toc_max_depth is None or level < self.toc_max_depth: - current_level = 0 - toc_node = self.toc - while current_level != level: - try: - toc_node = toc_node[-1] - except IndexError: # FIXME - toc_node = self.toc - break - else: - current_level += 1 - toc_node.extend([self.html_escape(content), []]) - # *_emit methods for emitting nodes of the document: def document_emit(self, node): @@ -178,8 +245,8 @@ def header_emit(self, node): header = '%s' % ( node.level, self.html_escape(node.content), node.level ) - if self.has_toc: - self.update_toc(node.level, node.content) + if self.toc is not None: + self.toc.add_headline(node.level, node.content) # add link attribute for toc navigation header = '%s' % ( self.html_escape(node.content), header @@ -228,10 +295,6 @@ def macro_emit(self, node): macro_kwargs["text"] = text - if callable(self.macros) == True: - raise DeprecationWarning("Callable macros are not supported anymore!") - return - exc_info = None if isinstance(self.macros, dict): try: @@ -256,17 +319,24 @@ def macro_emit(self, node): msg = "Macro '%s' error: %s" % (macro_name, err) exc_info = sys.exc_info() if self.verbose > 1: + if self.verbose > 2: + raise + # Inject more information about the macro in traceback etype, evalue, etb = exc_info import inspect - filename = inspect.getfile(macro) try: - sourceline = inspect.getsourcelines(macro)[0][0].strip() - except IOError as err: - evalue = etype("%s (error getting sourceline: %s from %s)" % (evalue, err, filename)) + filename = inspect.getfile(macro) + except TypeError: + pass else: - evalue = etype("%s (sourceline: %r from %s)" % (evalue, sourceline, filename)) - exc_info = etype, evalue, etb + try: + sourceline = inspect.getsourcelines(macro)[0][0].strip() + except IOError as err: + evalue = etype("%s (error getting sourceline: %s from %s)" % (evalue, err, filename)) + else: + evalue = etype("%s (sourceline: %r from %s)" % (evalue, sourceline, filename)) + exc_info = etype, evalue, etb return self.error(msg, exc_info) except Exception as err: @@ -321,38 +391,11 @@ def emit_node(self, node): emit = getattr(self, '%s_emit' % node.kind, self.default_emit) return emit(node) - def toc_list2html(self, toc_list, level=0): - """Convert a python nested list like the one representing the toc to an html equivalent.""" - if toc_list: - indent = "\t"*level - if isinstance(toc_list, TEXT_TYPE): - return '%s
  • %s
  • \n' % (indent, toc_list, toc_list) - elif isinstance(toc_list, list): - html = '%s
      \n' % indent - for elt in toc_list: - html += self.toc_list2html(elt, level + 1) - html += '%s
    ' % indent - if level > 0: - html += "\n" - return html - else: - return '' - - def toc_emit(self, document): - """Emit the toc where the <> macro was.""" - html_toc = self.toc_list2html(self.toc[-1]) - - # FIXME: We should not use

    here, because it doesn't match - # if no newline was made before <> - document = document.replace('

    <>

    ', html_toc, 1) - - return document - def emit(self): """Emit the document represented by self.root DOM tree.""" document = self.emit_node(self.root).strip() - if self.has_toc: - return self.toc_emit(document) + if self.toc is not None: + return self.toc.emit(document) else: return document @@ -373,7 +416,15 @@ def error(self, text, exc_info=None): if __name__ == "__main__": - txt = """A <>bar1<> in a line...""" + txt = """Local test +<> += headline 1 level 1 +== headline 2 level 2 +== headline 3 level 2 +==== headline 4 level 4 += headline 5 level 1 +=== headline 6 level 3 +""" print("-" * 80) # from creole_alt.creole import CreoleParser @@ -381,10 +432,7 @@ def error(self, text, exc_info=None): document = p.parse() p.debug() - from creole.shared.unknown_tags import escape_unknown_nodes - html = HtmlEmitter(document, - macros=escape_unknown_nodes - ).emit() + html = HtmlEmitter(document, verbose=999).emit() print(html) print("-" * 79) print(html.replace(" ", ".").replace("\n", "\\n\n")) diff --git a/creole/tests/test_creole2html.py b/creole/tests/test_creole2html.py index b0fc8a7..24c7775 100644 --- a/creole/tests/test_creole2html.py +++ b/creole/tests/test_creole2html.py @@ -141,7 +141,7 @@ def test_macro_callable(self): def testmacro(): pass - self.assertRaises(DeprecationWarning, + self.assertRaises(TypeError, creole2html, markup_string="<>bar<>", emitter_kwargs={ @@ -511,6 +511,144 @@ def test_toc_more_headlines(self):

    Sub-Headline 2.2

    """) + def test_toc_chaotic_headlines(self): + self.assert_creole2html(r""" + <> + = level 1 + === level 3 + == level 2 + ==== level 4 + = level 1 + """, """ + +

    level 1

    +

    level 3

    +

    level 2

    +

    level 4

    +

    level 1

    + """) + + def test_toc_depth_1(self): + self.assert_creole2html(r""" + <> + = Headline 1 + == Sub-Headline 1.1 + === Sub-Sub-Headline 1.1.1 + === Sub-Sub-Headline 1.1.2 + == Sub-Headline 1.2 + = Headline 2 + == Sub-Headline 2.1 + == Sub-Headline 2.2 + === Sub-Sub-Headline 2.2.1 + """, """ + +

    Headline 1

    +

    Sub-Headline 1.1

    +

    Sub-Sub-Headline 1.1.1

    +

    Sub-Sub-Headline 1.1.2

    +

    Sub-Headline 1.2

    +

    Headline 2

    +

    Sub-Headline 2.1

    +

    Sub-Headline 2.2

    +

    Sub-Sub-Headline 2.2.1

    + """) + + def test_toc_depth_2(self): + self.assert_creole2html(r""" + <> + = Headline 1 + == Sub-Headline 1.1 + === Sub-Sub-Headline 1.1.1 + === Sub-Sub-Headline 1.1.2 + == Sub-Headline 1.2 + = Headline 2 + == Sub-Headline 2.1 + == Sub-Headline 2.2 + === Sub-Sub-Headline 2.2.1 + """, """ + +

    Headline 1

    +

    Sub-Headline 1.1

    +

    Sub-Sub-Headline 1.1.1

    +

    Sub-Sub-Headline 1.1.2

    +

    Sub-Headline 1.2

    +

    Headline 2

    +

    Sub-Headline 2.1

    +

    Sub-Headline 2.2

    +

    Sub-Sub-Headline 2.2.1

    + """) + + def test_toc_depth_3(self): + self.assert_creole2html(r""" + <> + = Headline 1 + == Sub-Headline 1.1 + === Sub-Sub-Headline 1.1.1 + === Sub-Sub-Headline 1.1.2 + == Sub-Headline 1.2 + = Headline 2 + == Sub-Headline 2.1 + == Sub-Headline 2.2 + === Sub-Sub-Headline 2.2.1 + """, """ + +

    Headline 1

    +

    Sub-Headline 1.1

    +

    Sub-Sub-Headline 1.1.1

    +

    Sub-Sub-Headline 1.1.2

    +

    Sub-Headline 1.2

    +

    Headline 2

    +

    Sub-Headline 2.1

    +

    Sub-Headline 2.2

    +

    Sub-Sub-Headline 2.2.1

    + """) + def test_toc_with_no_toc(self): self.assert_creole2html(r""" <>