1.1 --- a/moinformat/macros/toc.py Mon Aug 13 17:55:16 2018 +0200
1.2 +++ b/moinformat/macros/toc.py Mon Aug 13 22:52:54 2018 +0200
1.3 @@ -20,7 +20,28 @@
1.4 """
1.5
1.6 from moinformat.macros.common import Macro
1.7 -from moinformat.tree.moin import Container, Heading, Link, List, ListItem, Text
1.8 +from moinformat.tree.moin import Block, Container, Heading, Link, List, \
1.9 + ListItem, Text
1.10 +
1.11 +def in_range(min_level, level, max_level):
1.12 +
1.13 + """
1.14 + Test that 'min_level' <= 'level' <= 'max_level', only imposing tests
1.15 + involving limits not set to None.
1.16 + """
1.17 +
1.18 + return (min_level is None or min_level <= level) and \
1.19 + (max_level is None or level <= max_level)
1.20 +
1.21 +def above_minimum(min_level, level, max_level):
1.22 +
1.23 + """
1.24 + Test that 'min_level' < 'level' <= 'max_level', only imposing tests
1.25 + involving limits not set to None.
1.26 + """
1.27 +
1.28 + return (min_level is None or min_level < level) and \
1.29 + (max_level is None or level <= max_level)
1.30
1.31 class TableOfContents(Macro):
1.32
1.33 @@ -81,7 +102,7 @@
1.34
1.35 # Ignore levels outside the range of interest.
1.36
1.37 - if not (min_level <= level <= max_level):
1.38 + if not in_range(min_level, level, max_level):
1.39 continue
1.40
1.41 # Determine whether the heading should be generated at this
1.42 @@ -133,7 +154,7 @@
1.43
1.44 # Retain a list at the minimum level.
1.45
1.46 - if min_level < level <= max_level:
1.47 + if above_minimum(min_level, level, max_level):
1.48 lists.pop()
1.49
1.50 level -= 1
1.51 @@ -145,17 +166,52 @@
1.52
1.53 # Add the heading as an item.
1.54
1.55 - if min_level <= level <= max_level:
1.56 + if in_range(min_level, level, max_level):
1.57 +
1.58 indent = level - 1
1.59 nodes = self.get_entry(heading)
1.60
1.61 item = ListItem(nodes, indent, marker, space, None)
1.62 items.append(item)
1.63
1.64 - # Replace the macro node's children with the top-level list.
1.65 - # The macro cannot be replaced because it will be appearing inline.
1.66 + # Replace the macro node with the top-level list.
1.67 +
1.68 + self.insert_table(lists[0])
1.69 +
1.70 + def insert_table(self, content):
1.71 +
1.72 + "Insert the given 'content' into the document."
1.73 +
1.74 + macro = self.node
1.75 + parent = macro.parent
1.76 + region = macro.region
1.77 +
1.78 + # Replace the macro if it is not inside a block.
1.79 + # NOTE: This attempts to avoid blocks being used in inline-only contexts
1.80 + # NOTE: but may not be successful in every case.
1.81 +
1.82 + if not isinstance(parent, Block) or parent is region:
1.83 + parent.replace(macro, content)
1.84
1.85 - self.node.nodes = lists and [lists[0]] or []
1.86 + # Split any block containing the macro into preceding and following
1.87 + # parts.
1.88 +
1.89 + else:
1.90 + following = parent.split_at(macro)
1.91 +
1.92 + # Insert any non-empty following block.
1.93 +
1.94 + if not following.whitespace_only():
1.95 + region.insert_after(parent, following)
1.96 +
1.97 + # Insert the new content.
1.98 +
1.99 + region.insert_after(parent, content)
1.100 +
1.101 + # Remove any empty preceding block.
1.102 +
1.103 + if parent.whitespace_only():
1.104 + region.remove(parent)
1.105
1.106 def find_headings(self, node, headings):
1.107
2.1 --- a/moinformat/parsers/moin.py Mon Aug 13 17:55:16 2018 +0200
2.2 +++ b/moinformat/parsers/moin.py Mon Aug 13 22:52:54 2018 +0200
2.3 @@ -559,7 +559,7 @@
2.4 # interpret the individual arguments.
2.5
2.6 arglist = args and args.split(",") or []
2.7 - macro = Macro(name, arglist, region.append_point())
2.8 + macro = Macro(name, arglist, region.append_point(), region)
2.9 region.append_inline(macro)
2.10
2.11 # Record the macro for later processing.
3.1 --- a/moinformat/tree/moin.py Mon Aug 13 17:55:16 2018 +0200
3.2 +++ b/moinformat/tree/moin.py Mon Aug 13 22:52:54 2018 +0200
3.3 @@ -69,6 +69,13 @@
3.4 def empty(self):
3.5 return not self.nodes
3.6
3.7 + def insert_after(self, old, new):
3.8 +
3.9 + "Insert after 'old' in the children the 'new' node."
3.10 +
3.11 + index = self.nodes.index(old)
3.12 + self.nodes.insert(index + 1, new)
3.13 +
3.14 def node(self, index):
3.15 try:
3.16 return self.nodes[index]
3.17 @@ -106,6 +113,12 @@
3.18 if text:
3.19 self.append(text)
3.20
3.21 + def remove(self, node):
3.22 +
3.23 + "Remove 'node' from the children."
3.24 +
3.25 + self.nodes.remove(node)
3.26 +
3.27 def replace(self, old, new):
3.28
3.29 "Replace 'old' with 'new' in the children."
3.30 @@ -113,6 +126,21 @@
3.31 i = self.nodes.index(old)
3.32 self.nodes[i] = new
3.33
3.34 + def split_at(self, node):
3.35 +
3.36 + """
3.37 + Split the container at 'node', returning a new container holding the
3.38 + nodes following 'node' that are moved from this container.
3.39 + """
3.40 +
3.41 + i = self.nodes.index(node)
3.42 + following = self.__class__(self.nodes[i+1:])
3.43 +
3.44 + # Remove the node and the following parts from this container.
3.45 +
3.46 + del self.nodes[i:]
3.47 + return following
3.48 +
3.49 def text_content(self):
3.50
3.51 """
3.52 @@ -130,6 +158,12 @@
3.53
3.54 return "".join(l)
3.55
3.56 + def whitespace_only(self):
3.57 +
3.58 + "Return whether the container provides only whitespace text."
3.59 +
3.60 + return not self.text_content().strip()
3.61 +
3.62 def __str__(self):
3.63 return self.prettyprint()
3.64
3.65 @@ -508,14 +542,15 @@
3.66
3.67 "Macro details."
3.68
3.69 - def __init__(self, name, args, parent, nodes=None):
3.70 + def __init__(self, name, args, parent, region, nodes=None):
3.71 Container.__init__(self, nodes or [])
3.72 self.name = name
3.73 + self.args = args
3.74 self.parent = parent
3.75 - self.args = args
3.76 + self.region = region
3.77
3.78 def __repr__(self):
3.79 - return "Macro(%r, %r, %r, %r)" % (self.name, self.args, self.parent, self.nodes)
3.80 + return "Macro(%r, %r, %r, %r, %r)" % (self.name, self.args, self.parent, self.region, self.nodes)
3.81
3.82 def prettyprint(self, indent=""):
3.83 l = ["%sMacro: name=%r args=%r" % (indent, self.name, self.args)]