/scripts/mkwiki.py
Python | 379 lines | 374 code | 3 blank | 2 comment | 1 complexity | 137ef7316edc817324055f74175b76dc MD5 | raw file
1#!/usr/bin/env python 2 3from commands import getstatusoutput 4from docutils.core import publish_string, publish_parts 5from docutils.nodes import SparseNodeVisitor 6from docutils.readers.standalone import Reader 7from docutils.writers import Writer 8from nose.config import Config 9import nose.plugins 10from nose.plugins.manager import BuiltinPluginManager 11from nose.plugins import errorclass 12import nose 13import os 14import pudge.browser 15import re 16import sys 17import textwrap 18import time 19 20sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) 21from mkdocs import formatargspec 22 23# constants 24success = 0 25div = '\n----\n' 26warning = """ 27*Do not edit above this line. Content above this line is automatically 28generated and edits above this line will be discarded.* 29 30= Comments = 31""" 32wiki_word_re = re.compile(r'^[A-Z][a-z]+(?:[A-Z][a-z]+)+') 33 34def ucfirst(s): 35 return s[0].upper() + s[1:].lower() 36 37def words(s): 38 return s.split(' ') 39 40 41def is_wiki_word(text): 42 return wiki_word_re.match(text) 43 44 45def wiki_word(node): 46 orig = text = node.astext() 47 # handle module/plugin links -- link to code 48 if is_wiki_word(text): 49 node['refuri'] = text 50 else: 51 if '.' in text: 52 parts = text.split('.') 53 link = 'http://python-nose.googlecode.com/svn/trunk' 54 for p in parts: 55 # stop at class names 56 if p[0].upper() == p[0]: 57 break 58 link += '/' + p 59 node['refuri'] = link 60 return True 61 node['refuri'] = ''.join(map(ucfirst, words(text))) 62 print "Unknown ref %s -> %s" % (orig, node['refuri']) 63 del node['refname'] 64 node.resolved = True 65 return True 66wiki_word.priority = 100 67 68class WWReader(Reader): 69 unknown_reference_resolvers = (wiki_word,) 70 71 72class WikiWriter(Writer): 73 def translate(self): 74 visitor = WikiVisitor(self.document) 75 self.document.walkabout(visitor) 76 self.output = visitor.astext() 77 78 79class WikiVisitor(SparseNodeVisitor): 80 def __init__(self, document): 81 SparseNodeVisitor.__init__(self, document) 82 self.list_depth = 0 83 self.list_item_prefix = None 84 self.indent = self.old_indent = '' 85 self.output = [] 86 self.preformat = False 87 88 def astext(self): 89 return ''.join(self.output) 90 91 def visit_Text(self, node): 92 #print "Text", node 93 data = node.astext() 94 if not self.preformat: 95 data = data.lstrip('\n\r') 96 data = data.replace('\r', '') 97 data = data.replace('\n', ' ') 98 self.output.append(data) 99 100 def visit_bullet_list(self, node): 101 self.list_depth += 1 102 self.list_item_prefix = (' ' * self.list_depth) + '* ' 103 104 def depart_bullet_list(self, node): 105 self.list_depth -= 1 106 if self.list_depth == 0: 107 self.list_item_prefix = None 108 else: 109 (' ' * self.list_depth) + '* ' 110 self.output.append('\n\n') 111 112 def visit_list_item(self, node): 113 self.old_indent = self.indent 114 self.indent = self.list_item_prefix 115 116 def depart_list_item(self, node): 117 self.indent = self.old_indent 118 119 def visit_literal_block(self, node): 120 self.output.extend(['{{{', '\n']) 121 self.preformat = True 122 123 def depart_literal_block(self, node): 124 self.output.extend(['\n', '}}}', '\n\n']) 125 self.preformat = False 126 127 def visit_doctest_block(self, node): 128 self.output.extend(['{{{', '\n']) 129 self.preformat = True 130 131 def depart_doctest_block(self, node): 132 self.output.extend(['\n', '}}}', '\n\n']) 133 self.preformat = False 134 135 def visit_paragraph(self, node): 136 self.output.append(self.indent) 137 138 def depart_paragraph(self, node): 139 self.output.append('\n\n') 140 if self.indent == self.list_item_prefix: 141 # we're in a sub paragraph of a list item 142 self.indent = ' ' * self.list_depth 143 144 def visit_reference(self, node): 145 if node.has_key('refuri'): 146 href = node['refuri'] 147 elif node.has_key('refid'): 148 href = '#' + node['refid'] 149 else: 150 href = None 151 self.output.append('[' + href + ' ') 152 153 def depart_reference(self, node): 154 self.output.append(']') 155 156 def visit_subtitle(self, node): 157 self.output.append('=== ') 158 159 def depart_subtitle(self, node): 160 self.output.append(' ===\n\n') 161 self.list_depth = 0 162 self.indent = '' 163 164 def visit_title(self, node): 165 self.output.append('== ') 166 167 def depart_title(self, node): 168 self.output.append(' ==\n\n') 169 self.list_depth = 0 170 self.indent = '' 171 172 def visit_title_reference(self, node): 173 self.output.append("`") 174 175 def depart_title_reference(self, node): 176 self.output.append("`") 177 178 def visit_emphasis(self, node): 179 self.output.append('*') 180 181 def depart_emphasis(self, node): 182 self.output.append('*') 183 184 def visit_literal(self, node): 185 self.output.append('`') 186 187 def depart_literal(self, node): 188 self.output.append('`') 189 190 191def runcmd(cmd): 192 print cmd 193 (status,output) = getstatusoutput(cmd) 194 if status != success: 195 raise Exception(output) 196 197 198def section(doc, name): 199 m = re.search(r'(%s\n%s.*?)\n[^\n-]{3,}\n-{3,}\n' % 200 (name, '-' * len(name)), doc, re.DOTALL) 201 if m: 202 return m.groups()[0] 203 raise Exception('Section %s not found' % name) 204 205 206def wikirst(doc): 207 return publish_string(doc, reader=WWReader(), writer=WikiWriter()) 208 209 210def plugin_interface(): 211 """use pudge browser to generate interface docs 212 from nose.plugins.base.PluginInterface 213 """ 214 b = pudge.browser.Browser(['nose.plugins.base'], None) 215 m = b.modules()[0] 216 intf = list([ c for c in m.classes() if c.name == 217 'IPluginInterface'])[0] 218 doc = wikirst(intf.doc()) 219 methods = [ m for m in intf.routines() if not m.name.startswith('_') ] 220 methods.sort(lambda a, b: cmp(a.name, b.name)) 221 mdoc = [] 222 for m in methods: 223 # FIXME fix the arg list so literal os.environ is not in there 224 mdoc.append('*%s%s*\n\n' % (m.name, formatargspec(m.obj))) 225 # FIXME this is resulting in poorly formatted doc sections 226 mdoc.append(' ' + m.doc().replace('\n', '\n ')) 227 mdoc.append('\n\n') 228 doc = doc + ''.join(mdoc) 229 return doc 230 231 232def example_plugin(): 233 # FIXME dump whole example plugin code from setup.py and plug.py 234 # into python source sections 235 root = os.path.abspath(os.path.join(os.path.dirname(__file__), 236 '..')) 237 exp = os.path.join(root, 'examples', 'plugin') 238 setup = file(os.path.join(exp, 'setup.py'), 'r').read() 239 plug = file(os.path.join(exp, 'plug.py'), 'r').read() 240 241 wik = "*%s:*\n{{{\n%s\n}}}\n" 242 return wik % ('setup.py', setup) + wik % ('plug.py', plug) 243 244 245def tools(): 246 top = wikirst(nose.tools.__doc__) 247 b = pudge.browser.Browser(['nose.tools'], None) 248 m = b.modules()[0] 249 funcs = [ (f.name, f.formatargs().replace('(self, ', '('), f.doc()) 250 for f in m.routines() ] 251 funcs.sort() 252 mdoc = [top, '\n\n'] 253 for name, args, doc in funcs: 254 mdoc.append("*%s%s*\n\n" % (name, args)) 255 mdoc.append(' ' + doc.replace('\n', '\n ')) 256 mdoc.append('\n\n') 257 return ''.join(mdoc) 258 259 260def usage(): 261 conf = Config(plugins=BuiltinPluginManager()) 262 usage_text = conf.help(nose.main.__doc__).replace('mkwiki.py', 'nosetests') 263 out = '{{{\n%s\n}}}\n' % usage_text 264 return out 265 266 267def mkwiki(path): 268 # 269 # Pages to publish and the docstring(s) to load for that page 270 # 271 272 pages = { #'SandBox': wikirst(section(nose.__doc__, 'Writing tests')) 273 'WritingTests': wikirst(section(nose.__doc__, 'Writing tests')), 274 'NoseFeatures': wikirst(section(nose.__doc__, 'Features')), 275 'WritingPlugins': wikirst(nose.plugins.__doc__), 276 'PluginInterface': plugin_interface(), 277 'ErrorClassPlugin': wikirst(errorclass.__doc__), 278 'TestingTools': tools(), 279 'FindingAndRunningTests': wikirst( 280 section(nose.__doc__, 'Finding and running tests')), 281 # FIXME finish example plugin doc... add some explanation 282 'ExamplePlugin': example_plugin(), 283 284 'NosetestsUsage': usage(), 285 } 286 287 current = os.getcwd() 288 w = Wiki(path) 289 for page, doc in pages.items(): 290 print "====== %s ======" % page 291 w.update_docs(page, doc) 292 print "====== %s ======" % page 293 os.chdir(current) 294 295 296class Wiki(object): 297 doc_re = re.compile(r'(.*?)' + div, re.DOTALL) 298 299 def __init__(self, path): 300 self.path = path 301 self.newpages = [] 302 os.chdir(path) 303 runcmd('svn up') 304 305 def filename(self, page): 306 if not page.endswith('.wiki'): 307 page = page + '.wiki' 308 return page 309 310 def get_page(self, page): 311 headers = [] 312 content = [] 313 314 try: 315 fh = file(self.filename(page), 'r') 316 in_header = True 317 for line in fh: 318 if in_header: 319 if line.startswith('#'): 320 headers.append(line) 321 else: 322 in_header = False 323 content.append(line) 324 else: 325 content.append(line) 326 fh.close() 327 return (headers, ''.join(content)) 328 except IOError: 329 self.newpages.append(page) 330 return ('', '') 331 332 def set_docs(self, page, headers, page_src, docs): 333 wikified = docs + div 334 if not page_src: 335 new_src = wikified + warning 336 print "! Adding new page" 337 else: 338 m = self.doc_re.search(page_src) 339 if m: 340 print "! Updating doc section" 341 new_src = self.doc_re.sub(wikified, page_src, 1) 342 else: 343 print "! Adding new doc section" 344 new_src = wikified + page_src 345 if new_src == page_src: 346 print "! No changes" 347 return 348 # Restore any headers (lines marked by # at start of file) 349 if headers: 350 new_src = ''.join(headers) + '\n' + new_src 351 fh = file(self.filename(page), 'w') 352 fh.write(new_src) 353 fh.close() 354 355 def update_docs(self, page, doc): 356 headers, current = self.get_page(page) 357 self.set_docs(page, headers, current, doc) 358 if page in self.newpages: 359 runcmd('svn add %s' % self.filename(page)) 360 361 362def findwiki(root): 363 if not root or root is '/': # not likely to work on windows 364 raise ValueError("wiki path not found") 365 if not os.path.isdir(root): 366 return findwiki(os.path.dirname(root)) 367 entries = os.listdir(root) 368 if 'wiki' in entries: 369 return os.path.join(root, 'wiki') 370 return findwiki(os.path.dirname(root)) 371 372 373def main(): 374 path = findwiki(os.path.abspath(__file__)) 375 mkwiki(path) 376 377 378if __name__ == '__main__': 379 main()