# HG changeset patch # User Paul Boddie # Date 1493499870 -7200 # Node ID 7825ca4d035797bc70a829e16112667858ef6085 # Parent 575c7b5d97ac7e0c6f249e7fd1c92eda6661be21 Added support for headings. diff -r 575c7b5d97ac -r 7825ca4d0357 moinformat/__init__.py --- a/moinformat/__init__.py Sat Apr 29 18:20:55 2017 +0200 +++ b/moinformat/__init__.py Sat Apr 29 23:04:30 2017 +0200 @@ -19,31 +19,39 @@ this program. If not, see . """ -from moinformat.tree import Block, ListItem, Region, Rule, Text +from moinformat.tree import Block, Heading, ListItem, Region, Rule, Text import re # Regular expressions. syntax = { # Page regions: - "regionstart" : (r"((^\s*)([{]{3,}))", re.MULTILINE | re.DOTALL), # {{{... - "regionend" : (r"^\s*([}]{3,})", re.MULTILINE | re.DOTALL), # }}}... - "header" : (r"#!(.*?)\n", 0), # #! char-excl-nl + "regionstart" : r"((^\s*)([{]{3,}))", # {{{... + "regionend" : r"^\s*([}]{3,})", # }}}... + "header" : r"#!(.*?)\n", # #! char-excl-nl # Region contents: - "break" : (r"^(\s*?)\n", re.MULTILINE), # blank line - "listitem" : (r"^((\s+)([*]|\d+[.]))", re.MULTILINE), # indent (list-item or number-item) - "rule" : (r"(-----*)", 0), # ----... + # Line-oriented patterns: + "break" : r"^(\s*?)\n", # blank line + "heading" : r"^(\s*)(?P=+)(\s+)(?=.*?\s+(?P=x)\s*\n)", # [ws...] =... ws... expecting headingend + "listitem" : r"^((\s+)([*]|\d+[.]))", # indent (list-item or number-item) + + # Region contents: + # Inline patterns: + "rule" : r"(-----*)", # ----... + + # Heading contents: + "headingend" : r"(\s+)(=+)(\s*\n)", # ws... =... [ws...] nl # List contents: - "listitemend" : (r"^", re.MULTILINE), # next line + "listitemend" : r"^", # next line } # Define patterns for the regular expressions. patterns = {} -for name, (value, flags) in syntax.items(): - patterns[name] = re.compile(value, re.UNICODE | flags) +for name, value in syntax.items(): + patterns[name] = re.compile(value, re.UNICODE | re.MULTILINE) @@ -156,7 +164,7 @@ "Parse the data provided by 'items' to populate a wiki 'region'." new_block(region) - parse_region_details(items, region, ["break", "listitem", "regionstart", "regionend", "rule"]) + parse_region_details(items, region, ["break", "heading", "listitem", "regionstart", "regionend", "rule"]) def parse_region_opaque(items, region): @@ -215,11 +223,27 @@ block.final = False new_block(region) -def parse_listitem_end(items, region): +def parse_heading(items, region): + + "Handle a heading." - "Handle the end of a list." + start_extra = items.read_match(1) + level = len(items.read_match(2)) + start_pad = items.read_match(3) + heading = Heading([], level, start_extra, start_pad) + parse_region_details(items, heading, ["headingend"]) + region.append(heading) + new_block(region) - raise StopIteration +def parse_heading_end(items, heading): + + "Handle the end of a heading." + + level = len(items.read_match(2)) + if heading.level == level: + heading.end_pad = items.read_match(1) + heading.end_extra = items.read_match(3) + raise StopIteration def parse_listitem(items, region): @@ -230,6 +254,12 @@ region.append(item) new_block(region) +def parse_listitem_end(items, item): + + "Handle the end of a list." + + raise StopIteration + def parse_rule(items, region): "Handle a horizontal rule within 'region'." @@ -265,6 +295,8 @@ handlers = { None : end_region, "break" : parse_break, + "heading" : parse_heading, + "headingend" : parse_heading_end, "listitemend" : parse_listitem_end, "listitem" : parse_listitem, "regionstart" : parse_section, diff -r 575c7b5d97ac -r 7825ca4d0357 moinformat/serialisers.py --- a/moinformat/serialisers.py Sat Apr 29 18:20:55 2017 +0200 +++ b/moinformat/serialisers.py Sat Apr 29 23:04:30 2017 +0200 @@ -51,6 +51,12 @@ if not final: self.out("\n") + def start_heading(self, level, extra, pad): + self.out(extra + "=" * level + pad) + + def end_heading(self, level, pad, extra): + self.out(pad + "=" * level + extra) + def start_listitem(self): self.out(" *") @@ -92,6 +98,12 @@ def end_block(self, final): self.out("

") + def start_heading(self, level, extra, pad): + self.out("" % level) + + def end_heading(self, level, pad, extra): + self.out("" % level) + def start_listitem(self): self.out("
  • ") diff -r 575c7b5d97ac -r 7825ca4d0357 moinformat/tree.py --- a/moinformat/tree.py Sat Apr 29 18:20:55 2017 +0200 +++ b/moinformat/tree.py Sat Apr 29 23:04:30 2017 +0200 @@ -140,6 +140,35 @@ node.to_string(out) out.end_block(self.final) +class Heading(Container): + + "A heading." + + def __init__(self, nodes, level, start_extra="", start_pad="", end_pad="", end_extra=""): + Container.__init__(self, nodes) + self.level = level + self.start_extra = start_extra + self.start_pad = start_pad + self.end_pad = end_pad + self.end_extra = end_extra + + def __repr__(self): + return "Heading(%r, %d, %r, %r, %r, %r)" % ( + self.nodes, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra) + + def prettyprint(self, indent=""): + l = ["%sHeading: level=%d start_extra=%r start_pad=%r end_pad=%r end_extra=%r" % ( + indent, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra)] + for node in self.nodes: + l.append(node.prettyprint(indent + " ")) + return "\n".join(l) + + def to_string(self, out): + out.start_heading(self.level, self.start_extra, self.start_pad) + for node in self.nodes: + node.to_string(out) + out.end_heading(self.level, self.end_pad, self.end_extra) + class ListItem(Container): "A list item." diff -r 575c7b5d97ac -r 7825ca4d0357 tests/test_parser.py --- a/tests/test_parser.py Sat Apr 29 18:20:55 2017 +0200 +++ b/tests/test_parser.py Sat Apr 29 23:04:30 2017 +0200 @@ -59,6 +59,17 @@ also still a rule---- EOF""") +sl.append("""\ += Level 1 = +Text + == Level 2 Heading == +Text +Not == a heading == +== Not a heading == either += Mismatched heading == +== Another mismatched heading = +""") + dl = map(parse, sl) nl = map(serialise, dl)