From 05fa28617cbfb8812a0e8486ec5091be2ea15599 Mon Sep 17 00:00:00 2001 From: Julien Enselme Date: Sun, 11 May 2014 19:48:40 +0200 Subject: [PATCH 1/4] Add a basic support for automatic table of content generation thanks to a macro-like syntax: <> I added a toc_test methods to TestCreole2htmlMarkup to perform unittest. I pached the existing tests so that they are correct despite the modification I made. It supports: - max depth: depth=NUMBER - links to the correct section --- creole/creole2html/emitter.py | 62 +++++++++++++++++++++-- creole/tests/test_creole2html.py | 87 ++++++++++++++++++-------------- 2 files changed, 107 insertions(+), 42 deletions(-) diff --git a/creole/creole2html/emitter.py b/creole/creole2html/emitter.py index ab5b57b..bbc3f38 100644 --- a/creole/creole2html/emitter.py +++ b/creole/creole2html/emitter.py @@ -28,6 +28,13 @@ class HtmlEmitter(object): def __init__(self, root, macros=None, verbose=None, stderr=None): self.root = root self.macros = macros + self.has_toc = False + self.toc_max_depth = float('inf') + + if self.macros is None: + self.macros = {'toc': self.create_toc} + elif isinstance(self.macros, dict): + self.macros['toc'] = self.create_toc if verbose is None: self.verbose = 1 @@ -52,6 +59,24 @@ def html_escape(self, text): def attr_escape(self, text): return self.html_escape(text).replace('"', '"') + def create_toc(self, depth=float('inf'), **kwargs): + """Called when if the macro <> is defined when it is emitted.""" + self.has_toc = True + self.toc_max_depth = depth + self.toc = ['root', []] + + return u'<>' + + def update_toc(self, level, content): + """Add the current header to the toc.""" + if level <= self.toc_max_depth: + current_level = 0 + toc_node = self.toc + while current_level != level: + toc_node = toc_node[-1] + current_level += 1 + toc_node.extend([self.html_escape(content), []]) + # *_emit methods for emitting nodes of the document: def document_emit(self, node): @@ -138,8 +163,16 @@ def delete_emit(self, node): #-------------------------------------------------------------------------- def header_emit(self, node): - return '%s\n' % ( - node.level, self.html_escape(node.content), node.level) + header = '%s\n' % ( + node.level, self.html_escape(node.content), node.level) + if self.has_toc: + self.update_toc(node.level, node.content) + # add link attribute for toc navigation + header = '%s' % ( + self.html_escape(node.content), header) + return header + else: + return header def preformatted_emit(self, node): return '
%s
' % self.html_escape(node.content) @@ -274,9 +307,32 @@ def emit_node(self, node): emit = getattr(self, '%s_emit' % node.kind, self.default_emit) return emit(node) + def python_list2html_list(self, python_list): + """Convert a python nested list like the one representing the toc to an html equivalent.""" + if python_list: + if isinstance(python_list, str): + return '
  • %s
  • \n' % (python_list, python_list) + elif isinstance(python_list, list): + html_list = '
      ' + for elt in python_list: + html_list += self.python_list2html_list(elt) + html_list += '
    \n' + return html_list + else: + return '' + + def toc_emit(self, document): + """Emit the toc where the <> macro was.""" + html_toc = self.python_list2html_list(self.toc[-1]) + return document.replace('

    <>

    ', html_toc) + def emit(self): """Emit the document represented by self.root DOM tree.""" - return self.emit_node(self.root).strip() + document = self.emit_node(self.root).strip() + if self.has_toc: + return self.toc_emit(document) + else: + return document def error(self, text, exc_info=None): """ diff --git a/creole/tests/test_creole2html.py b/creole/tests/test_creole2html.py index 9a88e44..9915dc1 100644 --- a/creole/tests/test_creole2html.py +++ b/creole/tests/test_creole2html.py @@ -4,9 +4,9 @@ """ creole2html unittest ~~~~~~~~~~~~~~~~~~~~ - + Here are only some tests witch doesn't work in the cross compare tests. - + Info: There exist some situations with different whitespace handling between creol2html and html2creole. @@ -59,9 +59,9 @@ def test_stderr(self): # Check if we get a traceback information into our stderr handler must_have = ( "Traceback", - "AttributeError:", - "has no attribute 'notexist1'", - "has no attribute 'notexist2'", + "KeyError:", + "KeyError: 'notexist1'", + "KeyError: 'notexist2'", ) for part in must_have: @@ -194,7 +194,6 @@ def test(text, foo): - class TestCreole2htmlMarkup(BaseCreoleTest): def test_creole_basic(self): @@ -226,16 +225,16 @@ def test_html_lines(self): self.assert_creole2html(r""" This is a normal Text block witch would escape html chars like < and > ;) - + So you can't insert directly. - +

    This escaped, too.

    """, """

    This is a normal Text block witch would
    escape html chars like < and > ;)

    - +

    So you can't insert <html> directly.

    - +

    <p>This escaped, too.</p>

    """) @@ -258,20 +257,20 @@ def test_cross_paragraphs(self): self.assert_creole2html(r""" Bold and italics should //not be... - ...able// to **cross - + ...able// to **cross + paragraphs.** """, """

    Bold and italics should //not be...

    - +

    ...able// to **cross

    - +

    paragraphs.**

    """) def test_list_special(self): """ - optional whitespace before the list + optional whitespace before the list """ self.assert_creole2html(r""" * Item 1 @@ -279,7 +278,7 @@ def test_list_special(self): ** Item 1.2 ** Item 1.3 * Item2 - + # one ## two """, """ @@ -309,7 +308,7 @@ def test_macro_basic(self): A <>bar1<> in a line... ...a single <> tag, or: <> closed... - + a macro block: <> the @@ -321,7 +320,7 @@ def test_macro_basic(self): A [test macro1 - kwargs: args='foo1',text='bar1'] in a line...
    ...a single [test macro1 - kwargs: foo='bar',text=None] tag,
    or: [test macro1 - kwargs: a=1,b=2,text=None] closed...

    - +

    a macro block:

    the|text

    the end

    @@ -335,12 +334,12 @@ def test_macro_html1(self): <>

    <>

    <> - + inline: <>{...}<> code """, r"""

    html macro:

    <>

    - +

    inline: {...} code

    """, macros=example_macros, @@ -350,7 +349,7 @@ def test_macro_not_exist1(self): """ not existing macro with creole2html.HtmlEmitter(verbose=1): A error message should be insertet into the generated code - + Two tests: with verbose=1 and verbose=2, witch write a Traceback information to a given "stderr" """ @@ -359,14 +358,14 @@ def test_macro_not_exist1(self): <> foo bar <> - + inline macro: <> """ should_string = r"""

    macro block:

    [Error: Macro 'notexists' doesn't exist] - +

    inline macro:
    [Error: Macro 'notexisttoo' doesn't exist]

    @@ -391,7 +390,7 @@ def test_wrong_macro_syntax(self): def test_macro_not_exist2(self): """ not existing macro with creole2html.HtmlEmitter(verbose=0): - + No error messages should be inserted. """ self.assert_creole2html(r""" @@ -399,17 +398,27 @@ def test_macro_not_exist2(self): <> foo bar <> - + inline macro: <> """, r"""

    macro block:

    - +

    inline macro:

    """, verbose=False ) + + def test_toc(self): + """ + Simple test to check the table of content is correctly generated. + """ + html = creole2html("""<>\n= Creole""") + self.assertEqual(html, + """\n\n

    Creole

    \n
    """) + + def test_image(self): """ test image tag with different picture text """ self.assert_creole2html(r""" @@ -446,7 +455,7 @@ def test_links(self): def test_standalone_hyperlink(self): self.assert_creole2html(r""" - a link to the http://www.pylucid.org page. + a link to the http://www.pylucid.org page. """, """

    a link to the http://www.pylucid.org page.

    """ @@ -457,14 +466,14 @@ def test_wiki_style_line_breaks1(self): markup_string=self._prepare_text(""" wiki style linebreaks - + ...and not blog styled. """), parser_kwargs={"blog_line_breaks":False}, ) self.assertEqual(html, self._prepare_text("""

    wiki style linebreaks

    - +

    ...and not blog styled.

    """)) @@ -473,7 +482,7 @@ def test_wiki_style_line_breaks2(self): markup_string=self._prepare_text(""" **one** //two// - + * one * two """), @@ -481,7 +490,7 @@ def test_wiki_style_line_breaks2(self): ) self.assertEqual(html, self._prepare_text("""

    one two

    - +
      \t
    • one
    • \t
    • two
    • @@ -493,29 +502,29 @@ def test_wiki_style_line_breaks3(self): markup_string=self._prepare_text(""" with blog line breaks, every line break would be convertet into
      with wiki style not. - + This is the first line,\\\\and this is the second. - + new line block 1 - + new line block 2 - + end """), parser_kwargs={"blog_line_breaks":False}, ) self.assertEqual(html, self._prepare_text("""

      with blog line breaks, every line break would be convertet into<br /> with wiki style not.

      - +

      This is the first line,
      and this is the second.

      - +

      new line block 1

      - +

      new line block 2

      - +

      end

      """)) From bcdd5e91684cb4d084101ca7861259599792cb53 Mon Sep 17 00:00:00 2001 From: Julien Enselme Date: Mon, 12 May 2014 21:11:00 +0200 Subject: [PATCH 2/4] Various code improvment: - Avoid use of float('inf') - Rename python_list2html_list to toc_list2html (more coherent) - Rename variables in toc_list2html to be clearer - Use document.replace('

      <>

      ', html_toc, 1) in toc emit to limit replacement Unit tests still fail in python2 (but pass with python3). --- creole/creole2html/emitter.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/creole/creole2html/emitter.py b/creole/creole2html/emitter.py index bbc3f38..ab92c91 100644 --- a/creole/creole2html/emitter.py +++ b/creole/creole2html/emitter.py @@ -29,7 +29,7 @@ def __init__(self, root, macros=None, verbose=None, stderr=None): self.root = root self.macros = macros self.has_toc = False - self.toc_max_depth = float('inf') + self.toc_max_depth = None if self.macros is None: self.macros = {'toc': self.create_toc} @@ -59,7 +59,7 @@ def html_escape(self, text): def attr_escape(self, text): return self.html_escape(text).replace('"', '"') - def create_toc(self, depth=float('inf'), **kwargs): + def create_toc(self, depth=None, **kwargs): """Called when if the macro <> is defined when it is emitted.""" self.has_toc = True self.toc_max_depth = depth @@ -69,7 +69,7 @@ def create_toc(self, depth=float('inf'), **kwargs): def update_toc(self, level, content): """Add the current header to the toc.""" - if level <= self.toc_max_depth: + if self.toc_max_depth is None or level < self.toc_max_depth: current_level = 0 toc_node = self.toc while current_level != level: @@ -307,24 +307,25 @@ def emit_node(self, node): emit = getattr(self, '%s_emit' % node.kind, self.default_emit) return emit(node) - def python_list2html_list(self, python_list): + def toc_list2html(self, toc_list): """Convert a python nested list like the one representing the toc to an html equivalent.""" - if python_list: - if isinstance(python_list, str): - return '
    • %s
    • \n' % (python_list, python_list) - elif isinstance(python_list, list): - html_list = '
        ' - for elt in python_list: - html_list += self.python_list2html_list(elt) - html_list += '
      \n' - return html_list + print(toc_list) + if toc_list: + if isinstance(toc_list, str): + return '
    • %s
    • \n' % (toc_list, toc_list) + elif isinstance(toc_list, list): + html = '
        ' + for elt in toc_list: + html += self.toc_list2html(elt) + html += '
      \n' + return html else: return '' def toc_emit(self, document): """Emit the toc where the <> macro was.""" - html_toc = self.python_list2html_list(self.toc[-1]) - return document.replace('

      <>

      ', html_toc) + html_toc = self.toc_list2html(self.toc[-1]) + return document.replace('

      <>

      ', html_toc, 1) def emit(self): """Emit the document represented by self.root DOM tree.""" From 904b24d08745bfcdc45925d9b74f678866dac9ac Mon Sep 17 00:00:00 2001 From: Julien Enselme Date: Mon, 12 May 2014 21:34:50 +0200 Subject: [PATCH 3/4] Fix the unitary tests. --- creole/creole2html/emitter.py | 3 +-- creole/tests/test_creole2html.py | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/creole/creole2html/emitter.py b/creole/creole2html/emitter.py index ab92c91..9fc67d4 100644 --- a/creole/creole2html/emitter.py +++ b/creole/creole2html/emitter.py @@ -309,9 +309,8 @@ def emit_node(self, node): def toc_list2html(self, toc_list): """Convert a python nested list like the one representing the toc to an html equivalent.""" - print(toc_list) if toc_list: - if isinstance(toc_list, str): + if isinstance(toc_list, TEXT_TYPE): return '
    • %s
    • \n' % (toc_list, toc_list) elif isinstance(toc_list, list): html = '
        ' diff --git a/creole/tests/test_creole2html.py b/creole/tests/test_creole2html.py index 9915dc1..45e73dd 100644 --- a/creole/tests/test_creole2html.py +++ b/creole/tests/test_creole2html.py @@ -28,6 +28,7 @@ from creole.tests.utils.base_unittest import BaseCreoleTest from creole.tests import test_macros +from creole.py3compat import PY3 from creole import creole2html from creole.shared import example_macros @@ -57,12 +58,20 @@ def test_stderr(self): error_msg = my_stderr.getvalue() # Check if we get a traceback information into our stderr handler - must_have = ( - "Traceback", - "KeyError:", - "KeyError: 'notexist1'", - "KeyError: 'notexist2'", - ) + if PY3: + must_have = ( + "Traceback", + "KeyError:", + "KeyError: 'notexist1'", + "KeyError: 'notexist2'", + ) + else: + must_have = ( + "Traceback", + "KeyError:", + "KeyError: u'notexist1'", + "KeyError: u'notexist2'", + ) for part in must_have: self.assertIn(part, error_msg) From b01ca17b71ce26dacce79eb9b07fedf5e890b5cc Mon Sep 17 00:00:00 2001 From: Julien Enselme Date: Mon, 12 May 2014 21:56:29 +0200 Subject: [PATCH 4/4] Should fix travis error. --- creole/creole2html/emitter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/creole/creole2html/emitter.py b/creole/creole2html/emitter.py index 9fc67d4..44ca6f4 100644 --- a/creole/creole2html/emitter.py +++ b/creole/creole2html/emitter.py @@ -65,7 +65,7 @@ def create_toc(self, depth=None, **kwargs): self.toc_max_depth = depth self.toc = ['root', []] - return u'<>' + return '<>' def update_toc(self, level, content): """Add the current header to the toc."""