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\nCreole
\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."""