diff --git a/zippydoc/__init__.py b/zippydoc/__init__.py
index e2e7e54..d328b10 100644
--- a/zippydoc/__init__.py
+++ b/zippydoc/__init__.py
@@ -1,2 +1,4 @@
from block_markup import *
-from parser import *
+from document import *
+from transformation_ruleset import *
+from value import *
diff --git a/zippydoc/block_markup.py b/zippydoc/block_markup.py
index b7f07c2..3d1187d 100644
--- a/zippydoc/block_markup.py
+++ b/zippydoc/block_markup.py
@@ -1,4 +1,5 @@
import re
+from value import Value
class TreeLevel:
def __init__(self, indentation, data):
@@ -8,49 +9,17 @@ class TreeLevel:
def add(self, element):
self.elements.append(element)
-
- def output(self):
- return self.render()
-
- def render_children(self):
+
+ def transform(self, ruleset):
+ return self.transform_children(ruleset)
+
+ def transform_children(self, ruleset):
child_output = ""
for child in self.elements:
- child_output += child.output()
+ child_output += child.transform(ruleset)
- return '
%s
' % child_output
-
- def process_inline_markup(self, text):
- text = re.sub("`([^`]+)`", '\\1', text) # Fixed-width
- text = re.sub("\*\*([^*]+)\*\*", "\\1", text) # Emphasized
- text = re.sub("__([^_]+)__", "\\1", text) # Strong
- text = re.sub("{>([^}]+)}\(([^)]+)\)", '\\2', text) # Hyperlink with text
- text = re.sub("{>([^}]+)}", '\\1', text) # Hyperlink
- text = re.sub("{([^}]+:[^}]+)}\(([^)]+)\)", '\\2', text) # External hyperlink with text
- text = re.sub("{([^}]+:[^}]+)}", '\\1', text) # External hyperlink
- text = re.sub("{<([^}]+)}\(([^)]+)\)", '\\2', text) # Forced external hyperlink with text
- text = re.sub("{<([^}]+)}", '\\1', text) # Forced external hyperlink
-
- return text
-
- def clear_markup(self, text):
- text = re.sub("`([^`]+)`", '\\1', text) # Fixed-width
- text = re.sub("\*\*([^*]+)\*\*", "\\1", text) # Emphasized
- text = re.sub("__([^_]+)__", "\\1", text) # Strong
- text = re.sub("{>([^}]+)}\(([^)]+)\)", '\\2', text) # Hyperlink with text
- text = re.sub("{>([^}]+)}", '\\1', text) # Hyperlink
- text = re.sub("{([^}]+:[^}]+)}\(([^)]+)\)", '\\2', text) # External hyperlink with text
- text = re.sub("{([^}]+:[^}]+)}", '\\1', text) # External hyperlink
- text = re.sub("{<([^}]+)}\(([^)]+)\)", '\\2', text) # Forced external hyperlink with text
- text = re.sub("{<([^}]+)}", '\\1', text) # Forced external hyperlink
-
- return text
-
- def fix_preformatted(self, text):
- return text.replace("<", "<").replace(">", ">")
-
- def render(self):
- return self.render_children()
+ return ruleset.transform_children(child_output)
class Header(TreeLevel):
def __init__(self, indentation, data, depth):
@@ -59,83 +28,58 @@ class Header(TreeLevel):
self.data = data
self.depth = depth
- def render(self):
- if self.depth <= 7:
- title_type = "h%d" % self.depth
- else:
- title_type = "h7"
-
- return "<%s>%s%s>" % (title_type, self.data, title_type)
+ def transform(self, ruleset):
+ return ruleset.transform_header(self.depth, Value(self.data))
class Text(TreeLevel):
- def render(self):
- return '%s
' % self.process_inline_markup(self.data)
+ def transform(self, ruleset):
+ return ruleset.transform_text(Value(self.data))
class Exclamation(TreeLevel):
- def render(self):
- return 'Important: %s
' % self.process_inline_markup(self.data)
+ def transform(self, ruleset):
+ return ruleset.transform_exclamation(Value(self.data), self.transform_children(ruleset))
class Definition(TreeLevel):
- def get_anchor(self):
- first = self.clear_markup(self.data.splitlines()[0])
- anchor = first.replace("...", "")
- anchor = anchor.replace(".", "_")
- anchor = re.sub("[^a-zA-Z0-9_]", "", anchor)
- return anchor
+ def __init__(self, indentation, forms):
+ self.elements = []
+ self.indentation = indentation
+ self.forms = [form.lstrip() for form in forms]
+
+ def transform(self, ruleset):
+ return ruleset.transform_definition([Value(form) for form in self.forms], self.transform_children(ruleset))
+
+ def get_forms(self):
+ return [Value(form) for form in self.forms]
def get_description(self):
for element in self.elements:
if element.__class__.__name__ == "Text":
- data = self.process_inline_markup(element.data)
-
- if len(data) > 80:
- matches = re.match("^(.{0,80})\W", data)
- return matches.group(1) + "..."
- else:
- return data
+ return element.data
return ""
- def render(self):
- return '' % (self.get_anchor(), self.process_inline_markup(self.data.replace("\n", "
")), self.render_children())
-
class Argument(TreeLevel):
def __init__(self, indentation, data, argname):
self.elements = []
self.indentation = indentation
self.data = data
self.argname = argname
-
- def render(self):
- return '- %s
- %s%s
' % (self.argname, self.process_inline_markup(self.data), self.render_children())
+
+ def transform(self, ruleset):
+ return ruleset.transform_argument(Value(self.argname), Value(self.data), self.transform_children(ruleset))
class Example(TreeLevel):
- def render(self):
- return 'Example: %s %s
' % (self.data, self.render_children())
+ def transform(self, ruleset):
+ return ruleset.transform_example(Value(self.data), self.transform_children(ruleset))
class Code(TreeLevel):
- def render(self):
- return 'Code:%s
' % self.fix_preformatted(self.data)
+ def transform(self, ruleset):
+ return ruleset.transform_code(self.data)
class Output(TreeLevel):
- def render(self):
- return 'Output:%s
' % self.fix_preformatted(self.data)
+ def transform(self, ruleset):
+ return ruleset.transform_output(Value(self.data))
class Index(TreeLevel):
- def render(self):
- rendered = ""
-
- for item in self.data.toc_items:
- forms = item.data.splitlines()
- first = self.clear_markup(forms[0])
-
- if len(forms) > 1:
- rest = '(also: ' + ', '.join(self.clear_markup(form) for form in forms[1:]) + ")"
- else:
- rest = ""
-
- anchor = item.get_anchor()
- description = item.get_description()
- rendered += '%s %s %s' % (anchor, first, description, rest)
-
- return '' % rendered
+ def transform(self, ruleset):
+ return ruleset.transform_toc([(definition, Value(definition.get_description())) for definition in self.data.get_definitions()])
diff --git a/zippydoc/document.py b/zippydoc/document.py
new file mode 100644
index 0000000..bbfd741
--- /dev/null
+++ b/zippydoc/document.py
@@ -0,0 +1,85 @@
+import re
+import block_markup
+
+class Document():
+ def __init__(self, data):
+ self.data = data
+ self._parse()
+
+ def _parse(self):
+ paragraphs = re.split("\s*\n\s*\n", self.data)
+
+ self.paragraphs = paragraphs
+ self.definitions = []
+
+ current_level = 0
+ current_paragraph = 0
+ self.current_elements = {0: block_markup.TreeLevel(0, "root")}
+
+ for paragraph in paragraphs:
+ if paragraph.strip() == "":
+ continue
+
+ current_paragraph += 1
+ indentation = len(paragraph) - len(paragraph.lstrip("\t")) + 1
+
+ if indentation > current_level + 1:
+ raise Exception("Invalid indentation found in paragraph %d" % current_paragraph)
+
+ start = indentation - 1
+ lines = [line[start:] for line in paragraph.splitlines()]
+
+ if lines[0].startswith("#"):
+ # Header
+ depth = len(lines[0]) - len(lines[0].lstrip("#"))
+ lines[0] = lines[0].lstrip("# ")
+ element = block_markup.Header(indentation, " ".join(lines), depth)
+ elif lines[0].startswith("^"):
+ # Definition
+ lines[0] = lines[0].lstrip("^ ")
+ element = block_markup.Definition(indentation, lines)
+ self.definitions.append(element)
+ elif lines[0].startswith("@"):
+ # Example
+ lines[0] = lines[0].lstrip("@ ")
+ element = block_markup.Example(indentation, " ".join(lines))
+ elif lines[0].startswith("$$") and self.current_elements[current_level].__class__.__name__ == "Code":
+ # Code continuation
+ self.current_elements[current_level].data += "\n\n" + "\n".join(lines).lstrip("$ ")
+ continue
+ elif lines[0].startswith("$"):
+ # Code block start
+ lines[0] = lines[0].lstrip("$ ")
+ element = block_markup.Code(indentation, "\n".join(lines))
+ elif lines[0].startswith(">>") and self.current_elements[current_level].__class__.__name__ == "Output":
+ # Output continuation
+ self.current_elements[current_level].data += "\n\n" + "\n".join(lines).lstrip("> ")
+ continue
+ elif lines[0].startswith(">"):
+ # Output block start
+ lines[0] = lines[0].lstrip("> ")
+ element = block_markup.Output(indentation, "\n".join(lines))
+ elif lines[0].startswith("!"):
+ # Exclamation
+ lines[0] = lines[0].lstrip("! ")
+ element = block_markup.Exclamation(indentation, " ".join(lines))
+ elif re.match(".*::\s*$", lines[0]):
+ # Argument definition
+ argname = re.match("(.*)::\s*$", lines[0]).group(1)
+ element = block_markup.Argument(indentation, " ".join(line.lstrip() for line in lines[1:]), argname)
+ elif lines[0].strip() == "{TOC}":
+ # Table of contents
+ element = block_markup.Index(indentation, self)
+ else:
+ # Text
+ element = block_markup.Text(indentation, " ".join(lines))
+
+ self.current_elements[indentation - 1].add(element)
+ current_level = indentation
+ self.current_elements[current_level] = element
+
+ def transform(self, ruleset):
+ return self.current_elements[0].transform(ruleset)
+
+ def get_definitions(self):
+ return self.definitions
diff --git a/zippydoc/parser.py b/zippydoc/parser.py
deleted file mode 100644
index 57cdbc0..0000000
--- a/zippydoc/parser.py
+++ /dev/null
@@ -1,86 +0,0 @@
-from block_markup import *
-
-class Parser():
- def __init__(self, template):
- self.template = template
-
- def render(self, text):
- paragraphs = re.split("\s*\n\s*\n", text)
- self.toc_items = []
- current_level = 0
- current_paragraph = 0
- current_elements = {0: TreeLevel(0, "root")}
-
- for paragraph in paragraphs:
- if paragraph.strip() == "":
- continue
-
- current_paragraph += 1
- indentation = len(paragraph) - len(paragraph.lstrip("\t")) + 1
-
- if indentation > current_level + 1:
- raise Exception("Invalid indentation found in paragraph %d" % current_paragraph)
-
- element_type = TreeLevel
- start = indentation - 1
-
- lines = [line[start:] for line in paragraph.splitlines()]
-
- if lines[0].startswith("#"):
- element_type = Header
- depth = len(lines[0]) - len(lines[0].lstrip("#"))
- lines[0] = lines[0].lstrip("# ")
- data = " ".join(lines)
- elif lines[0].startswith("^"):
- element_type = Definition
- lines[0] = lines[0].lstrip("^ ")
- data = "\n".join(lines)
- elif lines[0].startswith("@"):
- element_type = Example
- lines[0] = lines[0].lstrip("@ ")
- data = " ".join(lines)
- elif lines[0].startswith("$$") and current_elements[current_level].__class__.__name__ == "Code":
- current_elements[current_level].data += "\n\n" + "\n".join(lines).lstrip("$ ")
- continue
- elif lines[0].startswith("$"):
- element_type = Code
- lines[0] = lines[0].lstrip("$ ")
- data = "\n".join(lines)
- elif lines[0].startswith(">>") and current_elements[current_level].__class__.__name__ == "Output":
- current_elements[current_level].data += "\n\n" + "\n".join(lines).lstrip("> ")
- continue
- elif lines[0].startswith(">"):
- element_type = Output
- lines[0] = lines[0].lstrip("> ")
- data = "\n".join(lines)
- elif lines[0].startswith("!"):
- element_type = Exclamation
- lines[0] = lines[0].lstrip("! ")
- data = " ".join(lines)
- elif re.match(".*::\s*$", lines[0]):
- element_type = Argument
- argname = lines[0][:-2]
- data = " ".join(line.lstrip() for line in lines[1:])
- elif lines[0].strip() == "{TOC}":
- element_type = Index
- data = self
- else:
- element_type = Text
- data = " ".join(lines)
-
- if element_type.__name__ == "Header":
- element = Header(indentation, data, depth)
- elif element_type.__name__ == "Argument":
- element = Argument(indentation, data, argname)
- else:
- element = element_type(indentation, data)
-
- if element_type.__name__ == "Definition":
- self.toc_items.append(element)
-
- current_elements[indentation - 1].add(element)
-
- current_level = indentation
- current_elements[current_level] = element
-
- return self.template.replace("{CONTENT}", current_elements[0].output())
diff --git a/zippydoc/transformation_ruleset.py b/zippydoc/transformation_ruleset.py
new file mode 100644
index 0000000..d1b1c37
--- /dev/null
+++ b/zippydoc/transformation_ruleset.py
@@ -0,0 +1,45 @@
+class TransformationRuleset():
+ def transform_children(self, text):
+ pass
+
+ def transform_header(self, depth, text):
+ pass
+
+ def transform_definition(self, forms, children):
+ pass
+
+ def transform_argument(self, name, description, children):
+ pass
+
+ def transform_example(self, title, children):
+ pass
+
+ def transform_code(self, text):
+ pass
+
+ def transform_output(self, text):
+ pass
+
+ def transform_exclamation(self, text, children):
+ pass
+
+ def transform_text(self, text):
+ pass
+
+ def transform_reference(self, target, description):
+ pass
+
+ def transform_external_reference(self, target, description):
+ pass
+
+ def transform_fixed_width(self, text):
+ pass
+
+ def transform_emphasis(self, text):
+ pass
+
+ def transform_strong(self, text):
+ pass
+
+ def transform_toc(self, items):
+ pass
diff --git a/zippydoc/value.py b/zippydoc/value.py
new file mode 100644
index 0000000..f63cd27
--- /dev/null
+++ b/zippydoc/value.py
@@ -0,0 +1,29 @@
+import re
+
+class Value(str):
+ def transform(self, ruleset):
+ text = self
+ text = re.sub("`([^`]+)`", lambda x: ruleset.transform_fixed_width(Value(x.group(1))), text) # Fixed-width
+ text = re.sub("\*\*([^*]+)\*\*", lambda x: ruleset.transform_emphasis(Value(x.group(1))), text) # Emphasized
+ text = re.sub("__([^_]+)__", lambda x: ruleset.transform_strong(Value(x.group(1))), text) # Strong
+ text = re.sub("{>([^}]+)}\(([^)]+)\)", lambda x: ruleset.transform_reference(Value(x.group(1)), Value(x.group(2))), text) # Hyperlink with text
+ text = re.sub("{>([^}]+)}", lambda x: ruleset.transform_reference(Value(x.group(1)), Value(x.group(1))), text) # Hyperlink
+ text = re.sub("{([^}]+:[^}]+)}\(([^)]+)\)", lambda x: ruleset.transform_external_reference(Value(x.group(1)), Value(x.group(2))), text) # External hyperlink with text
+ text = re.sub("{([^}]+:[^}]+)}", lambda x: ruleset.transform_external_reference(Value(x.group(1)), Value(x.group(1))), text) # External hyperlink
+ text = re.sub("{<([^}]+)}\(([^)]+)\)", lambda x: ruleset.transform_external_reference(Value(x.group(1)), Value(x.group(2))), text) # Forced external hyperlink with text
+ text = re.sub("{<([^}]+)}", lambda x: ruleset.transform_external_reference(Value(x.group(1)), Value(x.group(1))), text) # Forced external hyperlink
+ return text
+
+ def clean(self):
+ text = self
+ text = re.sub("`([^`]+)`", '\\1', text) # Fixed-width
+ text = re.sub("\*\*([^*]+)\*\*", "\\1", text) # Emphasized
+ text = re.sub("__([^_]+)__", "\\1", text) # Strong
+ text = re.sub("{>([^}]+)}\(([^)]+)\)", '\\2', text) # Hyperlink with text
+ text = re.sub("{>([^}]+)}", '\\1', text) # Hyperlink
+ text = re.sub("{([^}]+:[^}]+)}\(([^)]+)\)", '\\2', text) # External hyperlink with text
+ text = re.sub("{([^}]+:[^}]+)}", '\\1', text) # External hyperlink
+ text = re.sub("{<([^}]+)}\(([^)]+)\)", '\\2', text) # Forced external hyperlink with text
+ text = re.sub("{<([^}]+)}", '\\1', text) # Forced external hyperlink
+ return text
+
diff --git a/zpy2html.py b/zpy2html.py
index f391d23..7330883 100644
--- a/zpy2html.py
+++ b/zpy2html.py
@@ -9,9 +9,91 @@ parser.add_argument('files', metavar='FILE', type=str, nargs='+',
args = parser.parse_args()
options = vars(args)
+class HtmlRuleset(zippydoc.TransformationRuleset):
+ def create_anchor(self, title):
+ anchor = title.clean().replace("...", "").replace(".", "_")
+ anchor = re.sub("[^a-zA-Z0-9_]", "", anchor)
+ return anchor
+
+ def escape_html(self, text):
+ return text.replace("<", "<").replace(">", ">")
+
+ def transform_children(self, text):
+ return '%s
' % text
+
+ def transform_header(self, depth, text):
+ if depth <= 7:
+ title_type = "h%d" % depth
+ else:
+ title_type = "h7"
+
+ return "<%s>%s%s>" % (title_type, text.transform(self), title_type)
+
+ def transform_definition(self, forms, children):
+ anchor = self.create_anchor(forms[0])
+ formlist = "
".join([form.transform(self) for form in forms])
+ return '' % (anchor, formlist, children)
+
+ def transform_argument(self, name, description, children):
+ return "- %s
- %s%s
" % (name, description.transform(self), children)
+
+ def transform_example(self, title, children):
+ return 'Example: %s %s
' % (title.transform(self), children)
+
+ def transform_code(self, text):
+ return 'Code:%s
' % self.escape_html(text)
+
+ def transform_output(self, text):
+ return 'Output:%s
' % self.escape_html(text)
+
+ def transform_exclamation(self, text, children):
+ return 'Important: %s %s
' % (text.transform(self), children)
+
+ def transform_text(self, text):
+ return '%s
' % text.transform(self)
+
+ def transform_reference(self, target, description):
+ return '%s' % (target, description.transform(self))
+
+ def transform_external_reference(self, target, description):
+ return '%s' % (target, description.transform(self))
+
+ def transform_fixed_width(self, text):
+ return '%s' % text
+
+ def transform_emphasis(self, text):
+ return "%s" % text.transform(self)
+
+ def transform_strong(self, text):
+ return "%s" % text.transform(self)
+
+ def transform_toc(self, items):
+ rendered = ""
+
+ for item in items:
+ forms = item[0].get_forms()
+ anchor = self.create_anchor(forms[0])
+
+ if len(forms) > 1:
+ alternatives = '(also: %s)' % ", ".join(form.clean() for form in forms[1:])
+ else:
+ alternatives = ""
+
+ description = item[1]
+
+ if len(description) > 80:
+ matches = re.match("^(.{0,80})\W", data)
+ description = matches.group(1) + "..."
+
+ description = zippydoc.Value(description).transform(self)
+
+ rendered += '%s %s %s' % (anchor, forms[0].clean(), description, alternatives)
+
+ return '' % rendered
+
files = options["files"]
-docparser = zippydoc.Parser(open("template.html").read())
+template = open("template.html").read()
for zpy in files:
destination = os.path.splitext(zpy)[0] + ".html"
@@ -20,10 +102,12 @@ for zpy in files:
data = f.read()
f.close()
- rendered = docparser.render(data)
+ doc = zippydoc.Document(open(zpy, "r").read())
+
+ rendered = doc.transform(HtmlRuleset())
f = open(destination, "w")
- f.write(rendered)
+ f.write(template.replace("{CONTENT}", rendered))
f.close()
print "Rendered %s" % destination