2 Add Sphinx index entries to RST source.
4 TODO: scan directory tree, look for *.rst
5 TODO: add option to remove entries
13 from ConfigParser import ConfigParser
15 from configparser import ConfigParser
18 # Configuration defaults.
19 defaults = {'comment': ".. Generated by autoindex",
23 # Read config settings.
24 config = ConfigParser(defaults, allow_no_value=True)
25 config.optionxform = str
27 thisdir = os.path.dirname(__file__)
28 conffile = os.path.join(thisdir, "autoindex.cfg")
31 # Extract keywords and role mappings.
35 if config.has_section(section):
36 for name in config.options(section):
37 if name not in defaults:
38 mapping[name] = config.get(section, name)
42 keywords = getmap('keywords')
43 rolemap = getmap('rolemap')
46 comment = config.get('DEFAULT', 'comment')
48 # Minimum amount of text twixt identical entries.
49 mintext = config.getint('DEFAULT', 'mintext')
51 # Don't add index entries after paragraphs matching this.
52 noindex = config.get('DEFAULT', 'noindex').strip().split("\n")
55 noindex_patterns = "(%s)" % "|".join(noindex)
57 noindex_patterns = None
59 # Paragraph separator.
69 infile, outfile = args[1:]
71 sys.exit("Usage: %s INFILE [OUTFILE]" % args[0])
73 ##dump_paragraphs(infile)
76 autoindex_file(infile, outfile)
79 def autoindex_file(infile, outfile=None):
80 "Add index entries to a file."
83 with open(infile) as fp:
87 itext = autoindex_text(text)
89 # Write output (but don't modify original if nothing changed).
90 if outfile or itext != text:
92 sys.stdout.write(itext)
94 with open(outfile or infile, "wb") as fp:
98 def autoindex_text(text):
99 "Add index entries to the given text."
100 return separator.join(indexed_paragraphs(text))
103 def indexed_paragraphs(text):
104 "Yield indexed paragraphs from the specified text."
106 # Current text position.
109 # Text position of last entries for each index word (to avoid too many
110 # close together for the same entry).
113 def addindex(index, name, desc=None):
114 if name not in lastpos or lastpos[name] + mintext < textpos:
115 index.append((name, desc, textpos))
116 lastpos[name] = textpos
118 # Whether to add index entries.
121 for info in paragraph_info(text):
126 # Initialise index (list of [name, desc, textpos]).
129 # Find index entries for roles.
130 for match in re.finditer(r':(.+?):`(.+?)`', para):
131 role, name = match.groups()
133 addindex(index, name, rolemap[role])
135 # Find index entries for keywords.
136 paraline = para.replace("\n", " ")
137 for word, desc in keywords.items():
138 if re.search(r'\b' + word + r'\b', paraline):
139 addindex(index, word, desc)
141 # Yield index paragraph if required.
142 if index and not noindex:
143 indent = info['indent']
144 lines = [indent + comment]
145 lines.append(indent + ".. index::")
147 for name, desc, pos in sorted(index):
148 msg = "autoindex: " + name
151 text = " pair: %s; %s" % (name, desc)
152 msg += " (" + desc + ")"
154 text = " single: %s" % name
156 lines.append(indent + text)
157 sys.stderr.write("%s [%s]\n" % (msg, pos))
159 yield "\n".join(lines)
161 noindex = info['noindex']
167 def unindexed_paragraphs(text):
168 "Yield paragraphs stripped of autoindex comments."
170 for para in text.split(separator):
171 if comment not in para:
175 def paragraph_info(text):
176 "Yield paragraph information from text."
181 for para in unindexed_paragraphs(text):
182 indent = re.match(r' *', para).group()
185 # Detect first entry in a list. Should be at same indent level as
187 match = re.match(r'\* ', para.lstrip())
189 level += len(match.group())
192 if not noindex and re.search(noindex_patterns, para, re.M):
193 noindex_level = level
195 elif noindex_level is not None and level <= noindex_level:
205 def dump_paragraphs(infile):
206 print noindex_patterns
208 with open(infile) as fp:
211 for info in paragraph_info(text):
212 print info['level'], info['noindex'], info['text'].replace("\n", " ")
215 if __name__ == "__main__":