Skip to content

Commit 3052ab7

Browse files
committed
Version 0.1b
1 parent b6e4587 commit 3052ab7

File tree

1 file changed

+242
-0
lines changed

1 file changed

+242
-0
lines changed

doc2md.py

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
#!/usr/bin/python3
2+
# -*- coding: utf-8 -*-
3+
4+
"""
5+
doc2md.py generates Python documentation in the Markdown (md) format. It was
6+
written to automatically generate documentation that can be put on Github
7+
or Bitbucket wiki pages. It is initially based on Ferry Boender's pydocmd.
8+
9+
It is as of yet not very complete and is more of a Proof-of-concept than a
10+
fully-fledged tool. Markdown is also a very restricted format and every
11+
implementation works subtly, or completely, different. This means output
12+
may be different on different converters.
13+
14+
## Usage
15+
16+
$ python doc2md.py module [...]
17+
18+
19+
## Example output
20+
21+
- http://github.com/blasterbug/SmileANN/wiki/neuron
22+
- http://github.com/blasterbug/SmileANN/wiki/faces
23+
- http://github.com/blasterbug/pydocmd/wiki/pydocmd
24+
25+
"""
26+
27+
28+
29+
import sys
30+
import os
31+
import imp
32+
import inspect
33+
34+
35+
__author__ = "Benjamin Sientzoff"
36+
__version__ = "0.1b"
37+
__maintainer__ = "Benjamin Sientzoff (blasterbug)"
38+
__license__ = "GNU GPL V2"
39+
40+
def remove_extension( fl ):
41+
return str(fl).split('.')[0]
42+
43+
def fmt_doc(doc, indent=''):
44+
"""
45+
Format a doc-string.
46+
"""
47+
s = ''
48+
for line in doc.lstrip().splitlines():
49+
s += '%s%s \n' % (indent, line.strip())
50+
return s.rstrip()
51+
52+
def insp_file(file_name):
53+
"""
54+
Inspect a file and return module information
55+
"""
56+
mod_inst = imp.load_source(remove_extension( file_name ), file_name)
57+
58+
if not mod_inst:
59+
sys.stderr.write("Failed to import '%s'\n" % (file_name))
60+
sys.exit(2)
61+
62+
mod_name = inspect.getmodulename(file_name)
63+
if not mod_name:
64+
mod_name = os.path.splitext(os.path.basename(file_name))[0]
65+
return insp_mod(mod_name, mod_inst)
66+
67+
def insp_mod(mod_name, mod_inst):
68+
"""
69+
Inspect a module return doc, vars, functions and classes.
70+
"""
71+
info = {
72+
'name': mod_name,
73+
'inst': mod_inst,
74+
'author': {},
75+
'doc': '',
76+
'vars': [],
77+
'functions': [],
78+
'classes': [],
79+
}
80+
81+
# Get module documentation
82+
mod_doc = inspect.getdoc(mod_inst)
83+
if mod_doc:
84+
info['doc'] = mod_doc
85+
86+
for attr_name in ['author', 'copyright', 'license', 'version', 'maintainer', 'email']:
87+
if hasattr(mod_inst, '__%s__' % (attr_name)):
88+
info['author'][attr_name] = getattr(mod_inst, '__%s__' % (attr_name))
89+
90+
# Get module global vars
91+
for member_name, member_inst in inspect.getmembers(mod_inst):
92+
if not member_name.startswith('_') and \
93+
not inspect.isfunction(member_inst) and \
94+
not inspect.isclass(member_inst) and \
95+
not inspect.ismodule(member_inst) and \
96+
member_inst.__module__ == mod_name and \
97+
member_name not in mod_inst.__builtins__:
98+
info['vars'].append( (member_name, member_inst) )
99+
100+
# Get module functions
101+
functions = inspect.getmembers(mod_inst, inspect.isfunction)
102+
if functions:
103+
for func_name, func_inst in functions:
104+
if func_inst.__module__ == mod_name :
105+
info['functions'].append(insp_method(func_name, func_inst))
106+
107+
# Get module classes
108+
classes = inspect.getmembers(mod_inst, inspect.isclass)
109+
if classes:
110+
for class_name, class_inst in classes:
111+
if class_inst.__module__ == mod_name :
112+
info['classes'].append(insp_class(class_name, class_inst))
113+
114+
return info
115+
116+
def insp_class(class_name, class_inst):
117+
"""
118+
Inspect class and return doc, methods.
119+
"""
120+
info = {
121+
'name': class_name,
122+
'inst': class_inst,
123+
'doc': '',
124+
'methods': [],
125+
}
126+
127+
# Get class documentation
128+
class_doc = inspect.getdoc(class_inst)
129+
# if class_doc:
130+
#info['doc'] = fmt_doc(class_doc)
131+
132+
# Get class methods
133+
methods = inspect.getmembers(class_inst, inspect.ismethod)
134+
for method_name, method_inst in methods:
135+
info['methods'].append(insp_method(method_name, method_inst))
136+
137+
return info
138+
139+
def insp_method(method_name, method_inst):
140+
"""
141+
Inspect a method and return arguments, doc.
142+
"""
143+
info = {
144+
'name': method_name,
145+
'inst': method_inst,
146+
'args': [],
147+
'doc': ''
148+
}
149+
150+
# Get method arguments
151+
method_args = inspect.getargspec(method_inst)
152+
for arg in method_args.args:
153+
if arg != 'self':
154+
info['args'].append(arg)
155+
156+
# Apply default argumument values to arguments
157+
if method_args.defaults:
158+
a_pos = len(info['args']) - len(method_args.defaults)
159+
for pos, default in enumerate(method_args.defaults):
160+
info['args'][a_pos + pos] = '%s=%s' % (info['args'][a_pos + pos], default)
161+
162+
# Print method documentation
163+
method_doc = inspect.getdoc(method_inst)
164+
if method_doc:
165+
info['doc'] = fmt_doc(method_doc)
166+
return info
167+
168+
169+
def to_markdown( text_block ) :
170+
"""
171+
Markdownify an inspect file
172+
"""
173+
doc_output = ("# %s \n" % file_i['name'] )
174+
doc_output += file_i['doc'] + ' \n'
175+
author = ''
176+
if 'author' in file_i['author']:
177+
author += file_i['author']['author'] + ' '
178+
if 'email' in file_i['author']:
179+
author += '<%s>' % (file_i['author']['email'])
180+
if author:
181+
doc_output += str(" - __Author__: %s\n" % author )
182+
183+
author_attrs = [
184+
('Version', 'version'),
185+
('Copyright', 'copyright'),
186+
('License', 'license'),
187+
]
188+
for attr_friendly, attr_name in author_attrs:
189+
if attr_name in file_i['author']:
190+
doc_output += " - __%s__: %s \n" % (attr_friendly, file_i['author'][attr_name])
191+
192+
if file_i['vars']:
193+
doc_output += "\n## Variables\n"
194+
for var_name, var_inst in file_i['vars']:
195+
doc_output += " - `%s`: %s\n" % (var_name, var_inst)
196+
197+
if file_i['functions']:
198+
doc_output += "\n\n## Functions\n"
199+
for function_i in file_i['functions']:
200+
if function_i['name'].startswith('_'):
201+
continue
202+
doc_output += "\n\n### def `%s(%s)`\n" % (function_i['name'], ', '.join(function_i['args']))
203+
if function_i['doc']:
204+
doc_output += "%s" % (function_i['doc'])
205+
else:
206+
doc_output += "No documentation for this function "
207+
208+
if file_i['classes']:
209+
doc_output += "\n\n## Classes\n"
210+
for class_i in file_i['classes']:
211+
doc_output += "\n\n### class `%s()`\n" % (class_i['name'])
212+
if class_i['doc']:
213+
doc_output += "%s " % (class_i['doc'])
214+
else:
215+
doc_output += "No documentation for this class "
216+
217+
doc_output += "\n\n### Methods:\n"
218+
for method_i in class_i['methods']:
219+
if method_i['name'] != '__init__' and method_i['name'].startswith('_'):
220+
continue
221+
doc_output += "\n\n#### def `%s(%s)`\n" % (method_i['name'], ', '.join(method_i['args']))
222+
doc_output += "%s " % (method_i['doc'])
223+
return doc_output
224+
225+
226+
if __name__ == '__main__':
227+
if 1 < len(sys.argv) :
228+
doc_dir = "doc"
229+
for arg in sys.argv[1:] :
230+
file_i = insp_file(arg)
231+
doc_content = to_markdown(file_i)
232+
if not os.path.exists( doc_dir ) :
233+
os.makedirs( doc_dir )
234+
doc_file = open( doc_dir + "/" + remove_extension(arg) + ".md", 'w')
235+
sys.stdout.write( "Writing documentation for %s in doc/\n" % arg )
236+
doc_file.write( doc_content )
237+
doc_file.close()
238+
else:
239+
sys.stderr.write('Usage: %s <file.py>\n' % (sys.argv[0]))
240+
sys.exit(1)
241+
242+

0 commit comments

Comments
 (0)