Skip to content

Commit

Permalink
support for chaotic <<toc>>
Browse files Browse the repository at this point in the history
  • Loading branch information
jedie committed May 15, 2014
1 parent 0b1a196 commit 3f55b32
Showing 4 changed files with 271 additions and 83 deletions.
3 changes: 2 additions & 1 deletion AUTHORS
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ CONTRIBUTORS are and/or have been:
* Eric O'Connell <[email protected]>


Special thanks to the python-forum.de guys, particularly (in alphabetic order):
Special thanks to the python-forum.de guys, particularly:
* EyDu
* sma

1 change: 1 addition & 0 deletions README.creole
Original file line number Diff line number Diff line change
@@ -210,6 +210,7 @@ Note: In this case you must install **docutils**! See above.
** NEW: Add {{{<<toc>>}}} 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
210 changes: 129 additions & 81 deletions creole/creole2html/emitter.py
Original file line number Diff line number Diff line change
@@ -20,25 +20,118 @@
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 <<toc>> is defined when it is emitted."""
if self._created:
return "&lt;&lt;toc&gt;&gt;"

self._created = True
if depth is not None:
self.max_depth = depth

return '<<toc>>'

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<li><a href="#%s">%s</a></li>\n' % (indent, nested_headlines, nested_headlines)
elif isinstance(nested_headlines, list):
html = '%s<ul>\n' % indent
for elt in nested_headlines:
html += self.nested_headlines2html(elt, level + 1)
html += '%s</ul>' % indent
if level > 0:
html += "\n"
return html

def emit(self, document):
"""Emit the toc where the <<toc>> macro was."""
nested_headlines = self.flat_list2nest_list(self.headlines)
html = self.nested_headlines2html(nested_headlines)

This comment has been minimized.

Copy link
@jedie

jedie May 15, 2014

Author Owner

Simpler solution here: #22

# FIXME: We should not use <p> here, because it doesn't match
# if no newline was made before <<toc>>
if "<p><<toc>></p>" in document:
document = document.replace("<p><<toc>></p>", html, 1)
else:
document = document.replace("<<toc>>", html, 1)

return document



class HtmlEmitter(object):
"""
Generate HTML output for the document
tree consisting of DocNodes.
"""
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 <<toc>>
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('"', '&quot')

_toc_created = False
def create_toc(self, depth=None, **kwargs):
"""Called when if the macro <<toc>> is defined when it is emitted."""
if self._toc_created:
return "&lt;&lt;toc&gt;&gt;"

self._toc_created = True
self.toc_max_depth = depth

return '<<toc>>'

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 = '<h%d>%s</h%d>' % (
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 = '<a name="%s">%s</a>' % (
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<li><a href="#%s">%s</a></li>\n' % (indent, toc_list, toc_list)
elif isinstance(toc_list, list):
html = '%s<ul>\n' % indent
for elt in toc_list:
html += self.toc_list2html(elt, level + 1)
html += '%s</ul>' % indent
if level > 0:
html += "\n"
return html
else:
return ''

def toc_emit(self, document):
"""Emit the toc where the <<toc>> macro was."""
html_toc = self.toc_list2html(self.toc[-1])

# FIXME: We should not use <p> here, because it doesn't match
# if no newline was made before <<toc>>
document = document.replace('<p><<toc>></p>', 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,18 +416,23 @@ def error(self, text, exc_info=None):


if __name__ == "__main__":
txt = """A <<test_macro1 args="foo1">>bar1<</test_macro1>> in a line..."""
txt = """Local test
<<toc>>
= 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
p = CreoleParser(txt)
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"))
Loading

0 comments on commit 3f55b32

Please sign in to comment.