/scripts/mkwiki.py

https://bitbucket.org/jpellerin/nose/ · Python · 379 lines · 289 code · 72 blank · 18 comment · 42 complexity · 137ef7316edc817324055f74175b76dc MD5 · raw file

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