1 #!/usr/bin/env python 2 3 """ 4 Table of contents macro. 5 6 Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 from moinformat.macros.common import Macro 23 from moinformat.tree.moin import Container, Heading, List, ListItem, Text 24 25 class TableOfContents(Macro): 26 27 "A table of contents macro." 28 29 name = "TableOfContents" 30 31 def evaluate(self): 32 33 "Evaluate the macro, producing a table of contents." 34 35 arglist = [] 36 _defaults = [None] * 2 37 38 for arg, default in map(None, self.node.args, _defaults): 39 if arg is not None: 40 try: 41 arg = max(1, int(arg.strip())) 42 except ValueError: 43 arg = None 44 arglist.append(arg) 45 46 self.make_table(arglist[0], arglist[1]) 47 48 def make_table(self, min_level=None, max_level=None): 49 50 """ 51 Make a table of contents with the given 'min_level' and 'max_level' of 52 headings. 53 """ 54 55 headings = [] 56 self.find_headings(self.doc, headings) 57 58 if not headings: 59 return 60 61 # Common list features. 62 63 marker = "1." 64 space = " " 65 num = "1" 66 nl = [Text("\n")] 67 68 # Start with no lists, no current item. 69 70 lists = [] 71 item = None 72 level = 0 73 74 for heading in headings: 75 new_level = heading.level 76 77 # Create new lists if the level increases. 78 79 if new_level > level: 80 while level < new_level: 81 level += 1 82 83 if not (min_level <= level <= max_level): 84 continue 85 86 # Determine whether the heading should be generated at this 87 # level. 88 89 nodes = level == new_level and heading.nodes[:] + nl or [] 90 indent = level - 1 91 92 # Make a list and add an item to it. 93 94 new_items = [] 95 new_list = List(new_items, indent, marker, num) 96 new_item = ListItem(nodes, indent, marker, space, None) 97 new_items.append(new_item) 98 99 # Add the list to the current item, if any. 100 101 if item: 102 item.nodes.append(new_list) 103 104 # Record the new list. 105 106 lists.append(new_list) 107 108 # Reference the new list's items and current item. 109 110 items = new_items 111 item = new_item 112 113 else: 114 # Retrieve an existing list if the level decreases. 115 116 if new_level < level: 117 while level > new_level: 118 if min_level <= level <= max_level: 119 lists.pop() 120 level -= 1 121 122 # Obtain the existing list and the current item. 123 124 items = lists[-1].nodes 125 item = items[-1] 126 127 # Add the heading as an item. 128 129 if min_level <= level <= max_level: 130 indent = level - 1 131 nodes = heading.nodes[:] + nl 132 133 item = ListItem(nodes, indent, marker, space, None) 134 items.append(item) 135 136 # Replace the macro node's children with the top-level list. 137 # The macro cannot be replaced because it will be appearing inline. 138 139 self.node.nodes = [lists[0]] 140 141 def find_headings(self, node, headings): 142 143 "Find headings under 'node', adding them to the 'headings' list." 144 145 if node.nodes: 146 for n in node.nodes: 147 if isinstance(n, Heading): 148 headings.append(n) 149 elif isinstance(n, Container): 150 self.find_headings(n, headings) 151 152 macro = TableOfContents 153 154 # vim: tabstop=4 expandtab shiftwidth=4