diff --git a/template.html b/template.html new file mode 100644 index 0000000..d8ea410 --- /dev/null +++ b/template.html @@ -0,0 +1,161 @@ + + + + + + + {CONTENT} + + diff --git a/zippydoc/__init__.py b/zippydoc/__init__.py new file mode 100644 index 0000000..e2e7e54 --- /dev/null +++ b/zippydoc/__init__.py @@ -0,0 +1,2 @@ +from block_markup import * +from parser import * diff --git a/zippydoc/block_markup.py b/zippydoc/block_markup.py new file mode 100644 index 0000000..b7f07c2 --- /dev/null +++ b/zippydoc/block_markup.py @@ -0,0 +1,141 @@ +import re + +class TreeLevel: + def __init__(self, indentation, data): + self.elements = [] + self.indentation = indentation + self.data = data + + def add(self, element): + self.elements.append(element) + + def output(self): + return self.render() + + def render_children(self): + child_output = "" + + for child in self.elements: + child_output += child.output() + + 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() + +class Header(TreeLevel): + def __init__(self, indentation, data, depth): + self.elements = [] + self.indentation = indentation + 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" % (title_type, self.data, title_type) + +class Text(TreeLevel): + def render(self): + return '
%s
' % self.process_inline_markup(self.data) + +class Exclamation(TreeLevel): + def render(self): + return '
Important: %s
' % self.process_inline_markup(self.data) + +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 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 "" + + def render(self): + return '
%s %s
' % (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()) + +class Example(TreeLevel): + def render(self): + return '
Example: %s %s
' % (self.data, self.render_children()) + +class Code(TreeLevel): + def render(self): + return 'Code:
%s
' % self.fix_preformatted(self.data) + +class Output(TreeLevel): + def render(self): + return 'Output:
%s
' % self.fix_preformatted(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 '

    Table of contents

    ' % rendered diff --git a/zippydoc/parser.py b/zippydoc/parser.py new file mode 100644 index 0000000..57cdbc0 --- /dev/null +++ b/zippydoc/parser.py @@ -0,0 +1,86 @@ +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/zpy2html.py b/zpy2html.py index 7968f34..f391d23 100644 --- a/zpy2html.py +++ b/zpy2html.py @@ -1,4 +1,5 @@ import os, argparse, sys, re +import zippydoc parser = argparse.ArgumentParser(description='Converts ZippyDoc source files to HTML.') @@ -10,301 +11,7 @@ options = vars(args) files = options["files"] -template = """ - - - - - - - %s - - -""" - -class TreeLevel: - def __init__(self, indentation, data): - self.elements = [] - self.indentation = indentation - self.data = data - - def add(self, element): - self.elements.append(element) - - def output(self): - return self.render() - - def render_children(self): - child_output = "" - - for child in self.elements: - child_output += child.output() - - return '
    %s
    ' % child_output - - def process_inline_markup(self, text): - text = re.sub("`([^`]+)`", '\\1', text) # Emphasized - 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 clear_markup(self, text): - return re.sub("\*\*([^*]+)\*\*", "\\1", text) - - def render(self): - return self.render_children() - -class Example(TreeLevel): - def render(self): - return '
    Example: %s %s
    ' % (self.data, self.render_children()) - -class Code(TreeLevel): - def render(self): - return 'Code:
    %s
    ' % self.fix_preformatted(self.data) - -class Output(TreeLevel): - def render(self): - return 'Output:
    %s
    ' % self.fix_preformatted(self.data) - -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 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 "" - - def render(self): - return '
    %s %s
    ' % (self.get_anchor(), self.process_inline_markup(self.data.replace("\n", "
    ")), self.render_children()) - -class Exclamation(TreeLevel): - def render(self): - return '
    Important: %s
    ' % self.process_inline_markup(self.data) - -class Argument(TreeLevel): - def __init__(self, indentation, data, argname): - self.elements = [] - self.indentation = indentation - self.argname = argname - self.data = data - - def render(self): - return '
    %s
    %s%s
    ' % (self.argname, self.process_inline_markup(self.data), self.render_children()) - -class Header(TreeLevel): - def __init__(self, indentation, data, depth): - self.elements = [] - self.indentation = indentation - self.depth = depth - self.data = data - - def render(self): - if self.depth <= 7: - title_type = "h%d" % self.depth - else: - title_type = "h7" - - return "<%s>%s" % (title_type, self.data, title_type) - -class Text(TreeLevel): - def render(self): - return '
    %s
    ' % self.process_inline_markup(self.data) - -class Index(TreeLevel): - def render(self): - global toc_items - - rendered = "" - - for item in 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 '

    Table of contents

    ' % rendered +docparser = zippydoc.Parser(open("template.html").read()) for zpy in files: destination = os.path.splitext(zpy)[0] + ".html" @@ -313,87 +20,7 @@ for zpy in files: data = f.read() f.close() - paragraphs = re.split("\s*\n\s*\n", data) - 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 = "" - else: - element_type = Text - data = " ".join(lines) - - #print "Found element of type %s at indentation %d with data %s" % (element_type.__name__, indentation, data[:80]) - - 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": - toc_items.append(element) - - current_elements[indentation - 1].add(element) - - current_level = indentation - current_elements[current_level] = element - - rendered = template % (current_elements[0].output()) + rendered = docparser.render(data) f = open(destination, "w") f.write(rendered)