-
Notifications
You must be signed in to change notification settings - Fork 1
/
pydoc2md.py
executable file
·259 lines (213 loc) · 8.86 KB
/
pydoc2md.py
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
pydoc2md.py
Simple script for creation python code documentation in Markdown format
### how to use
```shell
$ ./pydoc2md pydoc2md > README.md
```
### links
- [Markdown syntax](http://sourceforge.net/p/pydoc/wiki/markdown_syntax/)
'''
__author__ = 'Andrey Usov <https://github.com/ownport/pydoc2md>'
__version__ = '0.1'
import os
import sys
import pydoc
import inspect
import __builtin__
__all__ = ['render_doc', 'TextDoc2Markdown',]
class TextDoc2Markdown(pydoc.TextDoc, object):
''' PyDoc in Markdown format
'''
# ------------------------------------------- text formatting utilities
def indent(self, text, prefix=''):
'''Indent text by prepending a given prefix to each line.
'''
return super(TextDoc2Markdown, self).indent(text, prefix=prefix)
def bold(self, text):
''' Format a string in bold
'''
return "**%s**" % text
def section(self, title, contents):
''' Format a section with a given heading.
'''
return '## %s' % title + '\n\n' + self.indent(contents).rstrip() + '\n\n'
# ---------------------------------------------- type-specific routines
def formattree(self, tree, modname, parent=None, prefix='+-- '):
''' Render in text a class tree as returned by inspect.getclasstree().
'''
result = []
for entry in tree:
if type(entry) is type(()):
c, bases = entry
entry_details = '%s%s' % (prefix, pydoc.classname(c, modname))
if bases and bases != (parent,):
parents = map(lambda c, m=modname: pydoc.classname(c, m), bases)
entry_details += '(%s)' % ', '.join(parents)
result.append(entry_details)
elif type(entry) is type([]):
result.append(self.formattree(entry, modname, c, '| +-- '))
return '\n'.join(result)
def docmodule(self, object, name=None, mod=None):
''' Produce text documentation for a given module object.
'''
name = object.__name__ # ignore the passed-in name
synop, desc = pydoc.splitdoc(pydoc.getdoc(object))
result = self.section('Name', name + (synop and ' - ' + synop))
if desc:
result = result + self.section('Description', desc)
classes = []
for key, value in inspect.getmembers(object, inspect.isclass):
if (inspect.getmodule(value) or object) is object:
classes.append((key, value))
funcs = []
for key, value in inspect.getmembers(object, inspect.isroutine):
if inspect.isbuiltin(value) or inspect.getmodule(value) is object:
funcs.append((key, value))
if hasattr(object, '__path__'):
modpkgs = []
for file in os.listdir(object.__path__[0]):
path = os.path.join(object.__path__[0], file)
modname = inspect.getmodulename(file)
if modname and modname not in modpkgs:
modpkgs.append(modname)
elif pydoc.ispackage(path):
modpkgs.append(file + ' (package)')
modpkgs.sort()
result = result + self.section(
'Package contents', '\n'.join(modpkgs))
if classes:
classlist = map(lambda (key, value): value, classes)
classes_tree = self.formattree(inspect.getclasstree(classlist, 1), name)
if classes_tree:
classes_tree = '```text\n%s\n```\n' % classes_tree
result = result + self.section('Classes Tree', classes_tree)
contents = []
for key, value in classes:
contents.append(self.document(value, key, name))
result = result + self.section('Classes', '\n'.join(contents))
if funcs:
contents = []
for key, value in funcs:
contents.append(self.document(value, key, name))
result = result + self.section('Functions', '\n'.join(contents))
if hasattr(object, '__version__'):
version = str(object.__version__)
if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':
version = strip(version[11:-1])
result = result + self.section('Version', version)
if hasattr(object, '__date__'):
result = result + self.section('Date', str(object.__date__))
if hasattr(object, '__author__'):
result = result + self.section('Author', str(object.__author__))
if hasattr(object, '__credits__'):
result = result + self.section('Credits', str(object.__credits__))
return result
def docclass(self, object, name=None, mod=None):
''' Produce text documentation for a given class object.
'''
realname = object.__name__
name = name or realname
bases = object.__bases__
if name == realname:
title = '### class ' + self.bold(realname)
else:
title = '### ' + self.bold(name) + ' = class ' + realname
if bases:
def makename(c, m=object.__module__): return pydoc.classname(c, m)
parents = map(makename, bases)
title = title + '(%s)' % ', '.join(parents)
doc = pydoc.getdoc(object)
contents = doc and doc + '\n'
methods = pydoc.allmethods(object).items()
methods.sort()
for key, value in methods:
if key.startswith('_'):
continue
contents = contents + '\n' + self.document(value, key, mod, object)
if not contents: return title + '\n'
return title + '\n' + self.indent(contents.rstrip()) + '\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):
imclass = object.im_class
if cl:
if imclass is not cl:
note = ' from ' + pydoc.classname(imclass, mod)
skipdocs = 1
else:
if object.im_self:
note = ' method of %s instance' % classname(
object.im_self.__class__, mod)
else:
note = ' unbound %s method' % classname(imclass,mod)
object = object.im_func
if name == realname:
# title = self.bold(realname)
title = '#### ' + realname
else:
if (cl and cl.__dict__.has_key(realname) and
cl.__dict__[realname] is object):
skipdocs = 1
# title = self.bold(name) + ' = ' + realname
title = '####' + name + ' = ' + realname
if inspect.isbuiltin(object):
argspec = '(...)'
else:
args, varargs, varkw, defaults = inspect.getargspec(object)
argspec = inspect.formatargspec(
args, varargs, varkw, defaults, formatvalue=self.formatvalue)
if realname == '<lambda>':
title = 'lambda'
argspec = argspec[1:-1] # remove parentheses
decl = title + argspec + note
if skipdocs:
return decl + '\n'
else:
doc = pydoc.getdoc(object) or ''
return decl + '\n' + '*%s*\n\n' % (doc and self.indent(doc).rstrip())
pydoc.text = TextDoc2Markdown()
def cli():
''' Command-line interface (looks at sys.argv to decide what to do).
'''
import os
import sys
import getopt
class BadUsage: pass
# Scripts don't get the current directory in their path by default
# unless they are run with the '-m' switch
if '' not in sys.path:
scriptdir = os.path.dirname(sys.argv[0])
if scriptdir in sys.path:
sys.path.remove(scriptdir)
sys.path.insert(0, '.')
try:
if len(sys.argv) < 2:
raise BadUsage
for arg in sys.argv[1:]:
if pydoc.ispath(arg) and not os.path.exists(arg):
print 'file %r does not exist' % arg
break
try:
print pydoc.render_doc(arg, title='# %s')
except (ImportError, pydoc.ErrorDuringImport), err:
print err
except BadUsage:
cmd = os.path.basename(sys.argv[0])
print '''pydoc2md - the Python documentation tool in Markdown format
%s <name> ...
Show documentation in Markdown formam. <name> may be the name
of a Python function, module, or package, or a dotted reference
to a class or function within a module or module in a package.
If <name> contains a '%s', it is used as the path to a Python
source file to document.
''' % (cmd, os.sep)
if __name__ == '__main__':
cli()