##// END OF EJS Templates
add ipy_lookfor. closes #245
Ville M. Vainio -
Show More
@@ -0,0 +1,234 b''
1 """
2 IPython extension: %lookfor command for searching docstrings
3
4 """
5 # Pauli Virtanen <pav@iki.fi>, 2008.
6
7 import re, inspect, pkgutil, pydoc
8
9 #------------------------------------------------------------------------------
10 # Lookfor functionality
11 #------------------------------------------------------------------------------
12
13 # Cache for lookfor: {id(module): {name: (docstring, kind, index), ...}...}
14 # where kind: "func", "class", "module", "object"
15 # and index: index in breadth-first namespace traversal
16 _lookfor_caches = {}
17
18 # regexp whose match indicates that the string may contain a function signature
19 _function_signature_re = re.compile(r"[a-z_]+\(.*[,=].*\)", re.I)
20
21 def lookfor(what, modules=None, import_modules=True, regenerate=False):
22 """
23 Search for objects whose documentation contains all given words.
24 Shows a summary of matching objects, sorted roughly by relevance.
25
26 Parameters
27 ----------
28 what : str
29 String containing words to look for.
30
31 module : str, module
32 Module whose docstrings to go through.
33 import_modules : bool
34 Whether to import sub-modules in packages.
35 Will import only modules in __all__
36 regenerate: bool
37 Re-generate the docstring cache
38
39 """
40 # Cache
41 cache = {}
42 for module in modules:
43 try:
44 c = _lookfor_generate_cache(module, import_modules, regenerate)
45 cache.update(c)
46 except ImportError:
47 pass
48
49 # Search
50 # XXX: maybe using a real stemming search engine would be better?
51 found = []
52 whats = str(what).lower().split()
53 if not whats: return
54
55 for name, (docstring, kind, index) in cache.iteritems():
56 if kind in ('module', 'object'):
57 # don't show modules or objects
58 continue
59 ok = True
60 doc = docstring.lower()
61 for w in whats:
62 if w not in doc:
63 ok = False
64 break
65 if ok:
66 found.append(name)
67
68 # Relevance sort
69 # XXX: this is full Harrison-Stetson heuristics now,
70 # XXX: it probably could be improved
71
72 kind_relevance = {'func': 1000, 'class': 1000,
73 'module': -1000, 'object': -1000}
74
75 def relevance(name, docstr, kind, index):
76 r = 0
77 # do the keywords occur within the start of the docstring?
78 first_doc = "\n".join(docstr.lower().strip().split("\n")[:3])
79 r += sum([200 for w in whats if w in first_doc])
80 # do the keywords occur in the function name?
81 r += sum([30 for w in whats if w in name])
82 # is the full name long?
83 r += -len(name) * 5
84 # is the object of bad type?
85 r += kind_relevance.get(kind, -1000)
86 # is the object deep in namespace hierarchy?
87 r += -name.count('.') * 10
88 r += max(-index / 100, -100)
89 return r
90
91 def relevance_sort(a, b):
92 dr = relevance(b, *cache[b]) - relevance(a, *cache[a])
93 if dr != 0: return dr
94 else: return cmp(a, b)
95 found.sort(relevance_sort)
96
97 # Pretty-print
98 s = "Search results for '%s'" % (' '.join(whats))
99 help_text = [s, "-"*len(s)]
100 for name in found:
101 doc, kind, ix = cache[name]
102
103 doclines = [line.strip() for line in doc.strip().split("\n")
104 if line.strip()]
105
106 # find a suitable short description
107 try:
108 first_doc = doclines[0].strip()
109 if _function_signature_re.search(first_doc):
110 first_doc = doclines[1].strip()
111 except IndexError:
112 first_doc = ""
113 help_text.append("%s\n %s" % (name, first_doc))
114
115 # Output
116 if len(help_text) > 10:
117 pager = pydoc.getpager()
118 pager("\n".join(help_text))
119 else:
120 print "\n".join(help_text)
121
122 def _lookfor_generate_cache(module, import_modules, regenerate):
123 """
124 Generate docstring cache for given module.
125
126 Parameters
127 ----------
128 module : str, None, module
129 Module for which to generate docstring cache
130 import_modules : bool
131 Whether to import sub-modules in packages.
132 Will import only modules in __all__
133 regenerate: bool
134 Re-generate the docstring cache
135
136 Returns
137 -------
138 cache : dict {obj_full_name: (docstring, kind, index), ...}
139 Docstring cache for the module, either cached one (regenerate=False)
140 or newly generated.
141
142 """
143 global _lookfor_caches
144
145 if module is None:
146 module = "numpy"
147
148 if isinstance(module, str):
149 module = __import__(module)
150
151 if id(module) in _lookfor_caches and not regenerate:
152 return _lookfor_caches[id(module)]
153
154 # walk items and collect docstrings
155 cache = {}
156 _lookfor_caches[id(module)] = cache
157 seen = {}
158 index = 0
159 stack = [(module.__name__, module)]
160 while stack:
161 name, item = stack.pop(0)
162 if id(item) in seen: continue
163 seen[id(item)] = True
164
165 index += 1
166 kind = "object"
167
168 if inspect.ismodule(item):
169 kind = "module"
170 try:
171 _all = item.__all__
172 except AttributeError:
173 _all = None
174 # import sub-packages
175 if import_modules and hasattr(item, '__path__'):
176 for m in pkgutil.iter_modules(item.__path__):
177 if _all is not None and m[1] not in _all:
178 continue
179 try:
180 __import__("%s.%s" % (name, m[1]))
181 except ImportError:
182 continue
183 for n, v in inspect.getmembers(item):
184 if _all is not None and n not in _all:
185 continue
186 stack.append(("%s.%s" % (name, n), v))
187 elif inspect.isclass(item):
188 kind = "class"
189 for n, v in inspect.getmembers(item):
190 stack.append(("%s.%s" % (name, n), v))
191 elif callable(item):
192 kind = "func"
193
194 doc = inspect.getdoc(item)
195 if doc is not None:
196 cache[name] = (doc, kind, index)
197
198 return cache
199
200 #------------------------------------------------------------------------------
201 # IPython connectivity
202 #------------------------------------------------------------------------------
203
204 import IPython.ipapi
205 ip = IPython.ipapi.get()
206
207 _lookfor_modules = ['numpy', 'scipy']
208
209 def lookfor_f(self, arg=''):
210 r"""
211 Search for objects whose documentation contains all given words.
212 Shows a summary of matching objects, sorted roughly by relevance.
213
214 Usage
215 -----
216 %lookfor +numpy some words
217 Search module 'numpy'
218
219 %lookfor_modules numpy scipy
220 Set default modules whose docstrings to search
221
222 """
223 lookfor(arg, modules=_lookfor_modules)
224
225 def lookfor_modules_f(self, arg=''):
226 global _lookfor_modules
227 if not arg:
228 print "Modules included in %lookfor search:", _lookfor_modules
229 else:
230 _lookfor_modules = arg.split()
231
232 ip.expose_magic('lookfor', lookfor_f)
233 ip.expose_magic('lookfor_modules', lookfor_modules_f)
234
@@ -1,3 +1,8 b''
1 2008-04-20 Ville Vainio <vivainio@gmail.com>
2
3 * Extensions/ipy_lookfor.py: add %lookfor magic command
4 (search docstrings for words) by Pauli Virtanen. Close #245.
5
1 2008-04-18 Fernando Perez <Fernando.Perez@berkeley.edu>
6 2008-04-18 Fernando Perez <Fernando.Perez@berkeley.edu>
2
7
3 * IPython/genutils.py (page): apply workaround to curses bug that
8 * IPython/genutils.py (page): apply workaround to curses bug that
General Comments 0
You need to be logged in to leave comments. Login now