Welcome, guest | Sign In | My Account | Store | Cart

markdowndoc is a pydoc extension that allows you to autogenerate API docs for websites that use Markdown. We specifically developed it with Gitorious's wikis in mind, but it should work for other wikispaces and, of course, ActiveState. You can see a few examples of it here.

Python, 188 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#! /usr/bin/env python3

"""
markdowndoc.py

Written by Patrick Laban and Geremy Condra

Licensed under GPLv3

Released 28 April 2009

This module contains a simple class to output Markdown-style
pydocs.
"""

import pydoc, inspect, re, builtins

class MarkdownDoc(pydoc.TextDoc):

	underline = "*" * 40

	def process_docstring(self, obj):
		"""Get the docstring and turn it into a list."""
		docstring = pydoc.getdoc(obj)
		if docstring:
			return docstring + "\n\n"
		return ""

	def process_class_name(self, name, bases, module):
		"""Format the class's name and bases."""
		title = "## class " + self.bold(name)
		if bases:
			# get the names of each of the bases
			base_titles = [pydoc.classname(base, module) for base in bases]
			# if its not just object
			if len(base_titles) > 1:
				# append the list to the title
				title += "(%s)" % ", ".join(base_titles)
		return title

	def process_subsection(self, name):
		"""format the subsection as a header"""
		return "### " + name

	def docclass(self, cls, name=None, mod=None):
		"""Produce text documentation for the class object cls."""

		# the overall document, as a line-delimited list
		document = []

		# get the object's actual name, defaulting to the passed in name
		name = name or cls.__name__

		# get the object's bases
		bases = cls.__bases__

		# get the object's module
		mod = cls.__module__

		# get the object's MRO
		mro = [pydoc.classname(base, mod) for base in inspect.getmro(cls)]

		# get the object's classname, which should be printed
		classtitle = self.process_class_name(name, bases, mod)
		document.append(classtitle)
		document.append(self.underline)

		# get the object's docstring, which should be printed
		docstring = self.process_docstring(cls)
		document.append(docstring)

		# get all the attributes of the class
		attrs = []
		for name, kind, classname, value in pydoc.classify_class_attrs(cls):
			if pydoc.visiblename(name):
				attrs.append((name, kind, classname, value))

		# sort them into categories
		data, descriptors, methods = [], [], []
		for attr in attrs:
			if attr[1] == "data" and not attr[0].startswith("_"):
				data.append(attr)
			elif attr[1] == "data descriptor" and not attr[0].startswith("_"):
				descriptors.append(attr)
			elif "method" in attr[1] and not attr[2] is builtins.object:
				methods.append(attr)

		if data:
			# start the data section
			document.append(self.process_subsection(self.bold("data")))
			document.append(self.underline)

			# process your attributes
			for name, kind, classname, value in data:
				if hasattr(value, '__call__') or inspect.isdatadescriptor(value):
					doc = getdoc(value)
				else: 
					doc = None
				document.append(self.docother(getattr(cls, name), name, mod, maxlen=70, doc=doc) + '\n')

		if descriptors:
			# start the descriptors section
			document.append(self.process_subsection(self.bold("descriptors")))
			document.append(self.underline)

			# process your descriptors
			for name, kind, classname, value in descriptors:
				document.append(self._docdescriptor(name, value, mod))

		if methods:
			# start the methods section
			document.append(self.process_subsection(self.bold("methods")))
			document.append(self.underline)

			# process your methods
			for name, kind, classname, value in methods:
				document.append(self.document(getattr(cls, name), name, mod, cls))

		return "\n".join(document)		

	def bold(self, text):
		""" Formats text as bold in markdown. """
		if text.startswith('_') and text.endswith('_'):
			return "__\%s\__" %text
		elif text.startswith('_'):
			return "__\%s__" %text
		elif text.endswith('_'):
			return "__%s\__" %text
		else:
			return "__%s__" %text

	def indent(self, text, prefix=''):
		"""Indent text by prepending a given prefix to each line."""
		return text
    
	def section(self, title, contents):
		"""Format a section with a given heading."""
		clean_contents = self.indent(contents).rstrip()
		return "# " + self.bold(title) + '\n\n' + clean_contents + '\n\n'

	def docroutine(self, object, name=None, mod=None, cl=None):
		"""Produce text documentation for a function or method object."""
		realname = object.__name__
		name = name or realname
		note = ''
		skipdocs = 0
		if inspect.ismethod(object):
			object = object.__func__
		if name == realname:
			title = self.bold(realname)
		else:
			if (cl and realname in cl.__dict__ and cl.__dict__[realname] is object):
				skipdocs = 1
			title = self.bold(name) + ' = ' + realname
		if inspect.isfunction(object):
			args, varargs, varkw, defaults, kwonlyargs, kwdefaults, ann = inspect.getfullargspec(object)
			argspec = inspect.formatargspec(
				args, varargs, varkw, defaults, kwonlyargs, kwdefaults, ann,
				formatvalue=self.formatvalue,
				formatannotation=inspect.formatannotationrelativeto(object))
			if realname == '<lambda>':
				title = self.bold(name) + ' lambda '
				# XXX lambda's won't usually have func_annotations['return']
				# since the syntax doesn't support but it is possible.
				# So removing parentheses isn't truly safe.
				argspec = argspec[1:-1] # remove parentheses
		else:
			argspec = '(...)'
		decl = "#### " + "def " + title + argspec + ':' + '\n' + note

		if skipdocs:
			return decl + '\n'
		else:
			doc = pydoc.getdoc(object) or ''
			return decl + '\n' + (doc and self.indent(doc).rstrip() + '\n')

	def docother(self, object, name=None, mod=None, parent=None, maxlen=None, doc=None):
		"""Produce text documentation for a data object."""
		line = "#### " + object.__name__ + "\n"
		line += super().docother(object, name, mod, parent, maxlen, doc)
		return line + "\n"

	def _docdescriptor(self, name, value, mod):
		results = ""
		if name: results += "#### " + self.bold(name) + "\n"
		doc = pydoc.getdoc(value) or ""
		if doc: results += doc + "\n"
		return results

A good deal of the actual code in this- including almost all of docroutine- are basically a copy/paste job from pydoc itself. We intend to clean it up somewhat over the next few days, so, if you have any suggestions on how to improve it for other markdown-aware sites, let us know.