Tuesday, July 20, 2010

DocPy - Python Source Doc Gen




# docpy.py - david.barkhuizn@gmail.com

# 1. module methods - signature, followed by docstring
# 2. module fields ?
# 3. module classes
# class name, parent class, __init__ method parameters & docstring
# instance fields (get from self.? refs in __init__ method)

# ---------------------------------------------------------------

# ISSUES
# need to strip out classes before extracting method info
# can't handle multi-line method signatures

# ---------------------------------------------------------------

import sys

# ---------------------------------------------------------------

single_marker = '\'\'\''
double_marker = '\"\"\"'

# ---------------------------------------------------------------

class MethodInfo:

def __init__(self, method_name, params, docstrings):
'''
method_name - string
params - list of param name strings
'''
self.method_name = method_name
self.params = params
self.docstrings = docstrings

# ---------------------------------------------------------------

def load_text_file(file_path):
'''
return list of lines in text file @ 'file_path'
'''

try:
text_file = open(file_path, 'r')
text_block = text_file.read()
lines = text_block.split('\n')
text_file.close()
return lines

except Exception, err:
print('error during reading of text file %s' % file_path)
print(err)
return []

def src_file_path_from_cmdargs():

valid_args = []

for arg in sys.argv:
if arg.lower() not in ['docpy.py', 'python']:
valid_args.append(arg.lower())

return 'docpy.py'

if len(valid_args) != 1:
return ''
else:
return valid_args[0]

def get_sig_line_idxs(lines):
'''
lines = list of lines in python source file (in sequence)
'''
token = 'def '

indices = []

for i in range(len(lines)):
if lines[i].strip()[:4] == token:
indices.append(i)

return indices

def parse_method_sig(sig):

# e.g.' def parse_methodsig(sig):#comment '

sig = sig.strip()
# e.g.'def parse_methodsig(sig):#comment'

sig = sig[4:len(sig)]
# e.g.'parse_methodsig(sig):#comment'

i = sig.find('#')
if i != -1:
sig = sig[:i-1]
# e.g.'parse_methodsig(sig):

left = sig.find('(')
right = sig.rfind(')')

meth_name = sig[:left]

params = []

if right - left != 1:

params = sig[left+1:right]

params = params.replace(',', ' ')

got_double = True
while got_double == True:
got_double = (params.find(' ') != -1)
if got_double:
params = params.replace(' ', ' ')

params = params.split(' ')

return MethodInfo(meth_name, params, [])

def extract_single_line_docstring(docstring):
'''example of single line method docstring'''

lsingle = docstring.find(single_marker)
ldouble = docstring.find(double_marker)

marker = ''
left = -1
if lsingle != -1:
marker = single_marker
left = lsingle
elif ldouble != -1:
marker = double_marker
left = ldouble
else:
return ''

right = docstring.rfind(marker)

if (right != -1) and (right != left):
return docstring[left+3:right]
else:
return ''

def strip_leading_triplequotes(line):

left = line.find(single_marker)
if left == -1:
left = line.find(double_marker)
if left == -1:
return ''

return line[left + 3:]

def strip_trailing_triplequotes(line):

right = line.rfind(double_marker)
if right == -1:
right = line.rfind(double_marker)
if right == -1:
return ''

return line[:right]

def extract_docstrings(lines, meth_sig_idx, next_meth_sig_idx):

# determine line indices of starting and ending triple quotes

start = -1
end = -1

for i in range(meth_sig_idx + 1, next_meth_sig_idx):

line = lines[i]

sidx = line.find(single_marker)

if sidx == -1:
sidx = line.find(double_marker)

if sidx != -1:
if start == -1:
start = i
else:
end = i
break

if start == -1:
return []
elif end == -1:
single_line = extract_single_line_docstring(lines[start])
if single_line != '':
return [ single_line ]
else:
return []
else:
docstrings = []

for i in range(start, end + 1):
no_whitespace = lines[i].strip()
if i == start:
stripped = strip_leading_triplequotes(no_whitespace)
if stripped != '':
docstrings.append(stripped)
elif i == end:
stripped = strip_trailing_triplequotes(no_whitespace)
if stripped != '':
docstrings.append(stripped)
else:
docstrings.append(no_whitespace)

return docstrings

def get_method_info(lines, meth_sig_idx, next_meth_sig_idx):

method_info = parse_method_sig(lines[meth_sig_idx])
method_info.docstrings = extract_docstrings(lines, meth_sig_idx, next_meth_sig_idx)

return method_info

def mock_method(one, two, three, four):
print('vokol')

def display_module_method_info(meth_info):

print(meth_info.method_name)
for p in meth_info.params:
print(' ' + p)
for docstring in meth_info.docstrings:
print(docstring)

def main():

text_file_path = src_file_path_from_cmdargs()
lines = load_text_file(text_file_path)
sig_line_idxs = get_sig_line_idxs(lines)

module_methods_info = []

for i in range(len(sig_line_idxs)):

idx = sig_line_idxs[i]

if i < len(sig_line_idxs) - 1:
next_idx = sig_line_idxs[i + 1]
else:
next_idx = len(sig_line_idxs)

module_methods_info.append( get_method_info(lines, idx, next_idx) )

for meth_info in module_methods_info:
display_module_method_info(meth_info)


if __name__ == '__main__':
main()

No comments:

Post a Comment

comment: