Skip to content

Commit 008ece5

Browse files
author
y-p
committed
ENH: add script for locating undocumented arguments in docstrings
1 parent 6dec888 commit 008ece5

File tree

1 file changed

+83
-0
lines changed

1 file changed

+83
-0
lines changed

scripts/find_undoc_args.py

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
from __future__ import print_function
5+
6+
from collections import namedtuple
7+
from itertools import islice
8+
import types
9+
import os
10+
import re
11+
import argparse
12+
#http://docs.python.org/2/library/argparse.html
13+
# arg name is positional is not prefixed with - or --
14+
15+
parser = argparse.ArgumentParser(description='Program description.')
16+
parser.add_argument('-p', '--path', metavar='PATH', type=str,required=False,
17+
default=None,
18+
help='full path relative to which paths wills be reported',action='store')
19+
parser.add_argument('-m', '--module', metavar='MODULE', type=str,required=True,
20+
help='name of package to import and examine',action='store')
21+
22+
args = parser.parse_args()
23+
24+
Entry=namedtuple("Entry","func loc undoc_names missing_args nsig_names ndoc_names")
25+
26+
def entry_gen(root_ns,module_name):
27+
28+
q=[root_ns]
29+
seen=set()
30+
while q:
31+
ns = q.pop()
32+
for x in dir(ns):
33+
cand = getattr(ns,x)
34+
if (isinstance(cand,types.ModuleType)
35+
and cand.__name__ not in seen
36+
and cand.__name__.startswith(module_name)):
37+
# print(cand.__name__)
38+
seen.add(cand.__name__)
39+
q.insert(0,cand)
40+
elif (isinstance(cand,(types.MethodType,types.FunctionType)) and
41+
cand not in seen and cand.func_doc):
42+
seen.add(cand)
43+
yield cand
44+
45+
def cmp_docstring_sig(f):
46+
def build_loc(f):
47+
path=f.func_code.co_filename.split(args.path,1)[-1][1:]
48+
return "+{} {}".format(f.func_code.co_firstlineno,path)
49+
import inspect
50+
sig_names=set(inspect.getargspec(f).args)
51+
doc = f.func_doc.lower()
52+
doc = re.split("^\s*parameters\s*",doc,1,re.M)[-1]
53+
doc = re.split("^\s*returns*",doc,1,re.M)[0]
54+
doc_names={x.split(":")[0].strip() for x in doc.split("\n")
55+
if re.match("\s+[\w_]+\s*:",x)}
56+
sig_names.discard("self")
57+
doc_names.discard("kwds")
58+
doc_names.discard("kwargs")
59+
doc_names.discard("args")
60+
return Entry(func=f,loc=build_loc(f),undoc_names=sig_names.difference(doc_names),
61+
missing_args=doc_names.difference(sig_names),nsig_names=len(sig_names),
62+
ndoc_names=len(doc_names))
63+
64+
def main():
65+
module = __import__(args.module)
66+
if not args.path:
67+
args.path=os.path.dirname(module.__file__)
68+
collect=[cmp_docstring_sig(e) for e in entry_gen(module,module.__name__)]
69+
# only include if there are missing arguments in the docstring (less false positives)
70+
# and there are at least some documented arguments
71+
collect = [e for e in collect if e.undoc_names and len(e.undoc_names) != e.nsig_names]
72+
73+
tmpl = "{}:[{}]\t missing[{}/{}]={}"
74+
for x in collect:
75+
s= tmpl.format(x.loc,x.func.__name__,len(x.undoc_names),
76+
x.nsig_names,list(x.undoc_names))
77+
if x.missing_args:
78+
s+= " extra(?)={}".format(list(x.missing_args))
79+
print(s)
80+
81+
if __name__ == "__main__":
82+
import sys
83+
sys.exit(main())

0 commit comments

Comments
 (0)