1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/moinformat/macros/toc.py Tue Jul 24 23:36:13 2018 +0200
1.3 @@ -0,0 +1,153 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +"""
1.7 +Table of contents macro.
1.8 +
1.9 +Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk>
1.10 +
1.11 +This program is free software; you can redistribute it and/or modify it under
1.12 +the terms of the GNU General Public License as published by the Free Software
1.13 +Foundation; either version 3 of the License, or (at your option) any later
1.14 +version.
1.15 +
1.16 +This program is distributed in the hope that it will be useful, but WITHOUT
1.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
1.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1.19 +details.
1.20 +
1.21 +You should have received a copy of the GNU General Public License along with
1.22 +this program. If not, see <http://www.gnu.org/licenses/>.
1.23 +"""
1.24 +
1.25 +from moinformat.macros.common import Macro
1.26 +from moinformat.tree.moin import Container, Heading, List, ListItem, Text
1.27 +
1.28 +class TableOfContents(Macro):
1.29 +
1.30 + "A table of contents macro."
1.31 +
1.32 + name = "TableOfContents"
1.33 +
1.34 + def evaluate(self):
1.35 +
1.36 + "Evaluate the macro, producing a table of contents."
1.37 +
1.38 + arglist = []
1.39 + _defaults = [None] * 2
1.40 +
1.41 + for arg, default in map(None, self.node.args, _defaults):
1.42 + if arg is not None:
1.43 + try:
1.44 + arg = max(1, int(arg.strip()))
1.45 + except ValueError:
1.46 + arg = None
1.47 + arglist.append(arg)
1.48 +
1.49 + self.make_table(arglist[0], arglist[1])
1.50 +
1.51 + def make_table(self, min_level=None, max_level=None):
1.52 +
1.53 + """
1.54 + Make a table of contents with the given 'min_level' and 'max_level' of
1.55 + headings.
1.56 + """
1.57 +
1.58 + headings = []
1.59 + self.find_headings(self.doc, headings)
1.60 +
1.61 + if not headings:
1.62 + return
1.63 +
1.64 + # Common list features.
1.65 +
1.66 + marker = "1."
1.67 + space = " "
1.68 + num = "1"
1.69 + nl = [Text("\n")]
1.70 +
1.71 + # Start with no lists, no current item.
1.72 +
1.73 + lists = []
1.74 + item = None
1.75 + level = 0
1.76 +
1.77 + for heading in headings:
1.78 + new_level = heading.level
1.79 +
1.80 + # Create new lists if the level increases.
1.81 +
1.82 + if new_level > level:
1.83 + while level < new_level:
1.84 + level += 1
1.85 +
1.86 + if not (min_level <= level <= max_level):
1.87 + continue
1.88 +
1.89 + # Determine whether the heading should be generated at this
1.90 + # level.
1.91 +
1.92 + nodes = level == new_level and heading.nodes[:] + nl or []
1.93 + indent = level - 1
1.94 +
1.95 + # Make a list and add an item to it.
1.96 +
1.97 + new_items = []
1.98 + new_list = List(new_items, indent, marker, num)
1.99 + new_item = ListItem(nodes, indent, marker, space, None)
1.100 + new_items.append(new_item)
1.101 +
1.102 + # Add the list to the current item, if any.
1.103 +
1.104 + if item:
1.105 + item.nodes.append(new_list)
1.106 +
1.107 + # Record the new list.
1.108 +
1.109 + lists.append(new_list)
1.110 +
1.111 + # Reference the new list's items and current item.
1.112 +
1.113 + items = new_items
1.114 + item = new_item
1.115 +
1.116 + else:
1.117 + # Retrieve an existing list if the level decreases.
1.118 +
1.119 + if new_level < level:
1.120 + while level > new_level:
1.121 + if min_level <= level <= max_level:
1.122 + lists.pop()
1.123 + level -= 1
1.124 +
1.125 + # Obtain the existing list and the current item.
1.126 +
1.127 + items = lists[-1].nodes
1.128 + item = items[-1]
1.129 +
1.130 + # Add the heading as an item.
1.131 +
1.132 + if min_level <= level <= max_level:
1.133 + indent = level - 1
1.134 + nodes = heading.nodes[:] + nl
1.135 +
1.136 + item = ListItem(nodes, indent, marker, space, None)
1.137 + items.append(item)
1.138 +
1.139 + # Replace the macro node's children with the top-level list.
1.140 +
1.141 + self.node.nodes = [lists[0]]
1.142 +
1.143 + def find_headings(self, node, headings):
1.144 +
1.145 + "Find headings under 'node', adding them to the 'headings' list."
1.146 +
1.147 + if node.nodes:
1.148 + for n in node.nodes:
1.149 + if isinstance(n, Heading):
1.150 + headings.append(n)
1.151 + elif isinstance(n, Container):
1.152 + self.find_headings(n, headings)
1.153 +
1.154 +macro = TableOfContents
1.155 +
1.156 +# vim: tabstop=4 expandtab shiftwidth=4