FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Rewritten using the tokenize module, which gives us a real tokenizer · python/cpython@040d7ca · GitHub

Repository navigation

Commit 040d7ca

Browse files
committed
Rewritten using the tokenize module, which gives us a real tokenizer
rather than a number of approximating regular expressions. Alas, it is 3-4 times slower. Let that be a challenge for the tokenize module.
1 parent fd372aa commit 040d7ca

1 file changed

Lines changed: 144 additions & 188 deletions

File tree

‎Lib/pyclbr.py‎

Lines changed: 144 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
definitions and to find out the superclasses of a class.
55
66
The interface consists of a single function:
7-
readmodule(module, path)
7+
readmodule_ex(module [, path[, inpackage]])
88
module is the name of a Python module, path is an optional list of
99
directories where the module is to be searched. If present, path is
10-
prepended to the system search path sys.path.
10+
prepended to the system search path sys.path. (inpackage is used
11+
internally to search for a submodule of a package.)
1112
The return value is a dictionary. The keys of the dictionary are
1213
the names of the classes defined in the module (including classes
1314
that are defined via the from XXX import YYY construct). The values
@@ -28,12 +29,10 @@
2829
are recognized and imported modules are scanned as well, this
2930
shouldn't happen often.
3031
32+
XXX describe the Function class.
33+
3134
BUGS
32-
- Continuation lines are not dealt with at all, except inside strings.
3335
- Nested classes and functions can confuse it.
34-
- Code that doesn't pass tabnanny or python -t will confuse it, unless
35-
you set the module TABWIDTH vrbl (default 8) to the correct tab width
36-
for the file.
3736
3837
PACKAGE RELATED BUGS
3938
- If you have a package and a module inside that or another package
@@ -52,69 +51,11 @@ class MyClass(subpackage.SuperClass):
5251

5352
import sys
5453
import imp
55-
import re
56-
import string
54+
import tokenize # Python tokenizer
55+
from token import NAME
5756

5857
__all__ = ["readmodule"]
5958

60-
TABWIDTH = 8
61-
62-
_getnext = re.compile(r"""
63-
(?P<String>
64-
\""" [^"\\]* (?:
65-
(?: \\. | "(?!"") )
66-
[^"\\]*
67-
)*
68-
\"""
69-
70-
| ''' [^'\\]* (?:
71-
(?: \\. | '(?!'') )
72-
[^'\\]*
73-
)*
74-
'''
75-
76-
| " [^"\\\n]* (?: \\. [^"\\\n]*)* "
77-
78-
| ' [^'\\\n]* (?: \\. [^'\\\n]*)* '
79-
)
80-
81-
| (?P<Method>
82-
^
83-
(?P<MethodIndent> [ \t]* )
84-
def [ \t]+
85-
(?P<MethodName> [a-zA-Z_] \w* )
86-
[ \t]* \(
87-
)
88-
89-
| (?P<Class>
90-
^
91-
(?P<ClassIndent> [ \t]* )
92-
class [ \t]+
93-
(?P<ClassName> [a-zA-Z_] \w* )
94-
[ \t]*
95-
(?P<ClassSupers> \( [^)\n]* \) )?
96-
[ \t]* :
97-
)
98-
99-
| (?P<Import>
100-
^ import [ \t]+
101-
(?P<ImportList> [^#;\n]+ )
102-
)
103-
104-
| (?P<ImportFrom>
105-
^ from [ \t]+
106-
(?P<ImportFromPath>
107-
[a-zA-Z_] \w*
108-
(?:
109-
[ \t]* \. [ \t]* [a-zA-Z_] \w*
110-
)*
111-
)
112-
[ \t]+
113-
import [ \t]+
114-
(?P<ImportFromList> [^#;\n]+ )
115-
)
116-
""", re.VERBOSE | re.DOTALL | re.MULTILINE).search
117-
11859
_modules = {} # cache of modules we've seen
11960

12061
# each Python class is represented by an instance of this class
@@ -140,7 +81,7 @@ def __init__(self, module, name, file, lineno):
14081
def _addmethod(self, name, lineno):
14182
assert 0, "Function._addmethod() shouldn't be called"
14283

143-
def readmodule(module, path=[], inpackage=0):
84+
def readmodule(module, path=[], inpackage=False):
14485
'''Backwards compatible interface.
14586
14687
Like readmodule_ex() but strips Function objects from the
@@ -153,7 +94,7 @@ def readmodule(module, path=[], inpackage=0):
15394
res[key] = value
15495
return res
15596

156-
def readmodule_ex(module, path=[], inpackage=0):
97+
def readmodule_ex(module, path=[], inpackage=False):
15798
'''Read a module file and return a dictionary of classes.
15899
159100
Search for MODULE in PATH and sys.path, read and parse the
@@ -168,7 +109,7 @@ def readmodule_ex(module, path=[], inpackage=0):
168109
package = module[:i].strip()
169110
submodule = module[i+1:].strip()
170111
parent = readmodule_ex(package, path, inpackage)
171-
child = readmodule_ex(submodule, parent['__path__'], 1)
112+
child = readmodule_ex(submodule, parent['__path__'], True)
172113
return child
173114

174115
if module in _modules:
@@ -204,129 +145,144 @@ def readmodule_ex(module, path=[], inpackage=0):
204145

205146
_modules[module] = dict
206147
classstack = [] # stack of (class, indent) pairs
207-
src = f.read()
208-
f.close()
209-
210-
# To avoid having to stop the regexp at each newline, instead
211-
# when we need a line number we simply count the number of
212-
# newlines in the string since the last time we did this; i.e.,
213-
# lineno += src.count('\n', last_lineno_pos, here)
214-
# last_lineno_pos = here
215-
lineno, last_lineno_pos = 1, 0
216-
i = 0
217-
while 1:
218-
m = _getnext(src, i)
219-
if not m:
220-
break
221-
start, i = m.span()
222-
223-
if m.start("Method") >= 0:
224-
# found a method definition or function
225-
thisindent = _indent(m.group("MethodIndent"))
226-
meth_name = m.group("MethodName")
227-
lineno += src.count('\n', last_lineno_pos, start)
228-
last_lineno_pos = start
229-
# close all classes indented at least as much
230-
while classstack and \
231-
classstack[-1][1] >= thisindent:
232-
del classstack[-1]
233-
if classstack:
234-
# it's a class method
235-
cur_class = classstack[-1][0]
236-
cur_class._addmethod(meth_name, lineno)
237-
else:
238-
# it's a function
239-
f = Function(module, meth_name,
240-
file, lineno)
241-
dict[meth_name] = f
242-
243-
elif m.start("String") >= 0:
244-
pass
245148

246-
elif m.start("Class") >= 0:
247-
# we found a class definition
248-
thisindent = _indent(m.group("ClassIndent"))
249-
# close all classes indented at least as much
250-
while classstack and \
251-
classstack[-1][1] >= thisindent:
252-
del classstack[-1]
253-
lineno += src.count('\n', last_lineno_pos, start)
254-
last_lineno_pos = start
255-
class_name = m.group("ClassName")
256-
inherit = m.group("ClassSupers")
257-
if inherit:
258-
# the class inherits from other classes
259-
inherit = inherit[1:-1].strip()
260-
names = []
261-
for n in inherit.split(','):
262-
n = n.strip()
263-
if n in dict:
264-
# we know this super class
265-
n = dict[n]
266-
else:
267-
c = n.split('.')
268-
if len(c) > 1:
269-
# super class
270-
# is of the
271-
# form module.class:
272-
# look in
273-
# module for class
274-
m = c[-2]
275-
c = c[-1]
276-
if m in _modules:
277-
d = _modules[m]
278-
if c in d:
279-
n = d[c]
280-
names.append(n)
281-
inherit = names
282-
# remember this class
283-
cur_class = Class(module, class_name, inherit,
284-
file, lineno)
285-
dict[class_name] = cur_class
286-
classstack.append((cur_class, thisindent))
287-
288-
elif m.start("Import") >= 0:
289-
# import module
290-
for n in m.group("ImportList").split(','):
291-
n = n.strip()
149+
g = tokenize.generate_tokens(f.readline)
150+
try:
151+
for tokentype, token, start, end, line in g:
152+
if token == 'def':
153+
lineno, thisindent = start
154+
tokentype, meth_name, start, end, line = g.next()
155+
if tokentype != NAME:
156+
continue # Syntax error
157+
# close all classes indented at least as much
158+
while classstack and \
159+
classstack[-1][1] >= thisindent:
160+
del classstack[-1]
161+
if classstack:
162+
# it's a class method
163+
cur_class = classstack[-1][0]
164+
cur_class._addmethod(meth_name, lineno)
165+
else:
166+
# it's a function
167+
dict[meth_name] = Function(module, meth_name, file, lineno)
168+
elif token == 'class':
169+
lineno, thisindent = start
170+
tokentype, class_name, start, end, line = g.next()
171+
if tokentype != NAME:
172+
continue # Syntax error
173+
# close all classes indented at least as much
174+
while classstack and \
175+
classstack[-1][1] >= thisindent:
176+
del classstack[-1]
177+
# parse what follows the class name
178+
tokentype, token, start, end, line = g.next()
179+
inherit = None
180+
if token == '(':
181+
names = [] # List of superclasses
182+
# there's a list of superclasses
183+
level = 1
184+
super = [] # Tokens making up current superclass
185+
while True:
186+
tokentype, token, start, end, line = g.next()
187+
if token in (')', ',') and level == 1:
188+
n = "".join(super)
189+
if n in dict:
190+
# we know this super class
191+
n = dict[n]
192+
else:
193+
c = n.split('.')
194+
if len(c) > 1:
195+
# super class is of the form
196+
# module.class: look in module for
197+
# class
198+
m = c[-2]
199+
c = c[-1]
200+
if m in _modules:
201+
d = _modules[m]
202+
if c in d:
203+
n = d[c]
204+
names.append(n)
205+
if token == '(':
206+
level += 1
207+
elif token == ')':
208+
level -= 1
209+
if level == 0:
210+
break
211+
elif token == ',' and level == 1:
212+
pass
213+
else:
214+
super.append(token)
215+
inherit = names
216+
cur_class = Class(module, class_name, inherit, file, lineno)
217+
dict[class_name] = cur_class
218+
classstack.append((cur_class, thisindent))
219+
elif token == 'import' and start[1] == 0:
220+
modules = _getnamelist(g)
221+
for mod, mod2 in modules:
222+
readmodule_ex(mod, path, inpackage)
223+
elif token == 'from' and start[1] == 0:
224+
mod, token = _getname(g)
225+
if not mod or token != "import":
226+
continue
227+
names = _getnamelist(g)
292228
try:
293229
# recursively read the imported module
294-
d = readmodule_ex(n, path, inpackage)
230+
d = readmodule_ex(mod, path, inpackage)
295231
except:
296-
##print 'module', n, 'not found'
297-
pass
298-
299-
elif m.start("ImportFrom") >= 0:
300-
# from module import stuff
301-
mod = m.group("ImportFromPath")
302-
names = m.group("ImportFromList").split(',')
303-
try:
304-
# recursively read the imported module
305-
d = readmodule_ex(mod, path, inpackage)
306-
except:
307-
##print 'module', mod, 'not found'
308-
continue
309-
# add any classes that were defined in the
310-
# imported module to our name space if they
311-
# were mentioned in the list
312-
for n in names:
313-
n = n.strip()
314-
if n in d:
315-
dict[n] = d[n]
316-
elif n == '*':
317-
# only add a name if not
318-
# already there (to mimic what
319-
# Python does internally)
320-
# also don't add names that
321-
# start with _
322-
for n in d:
323-
if n[0] != '_' and \
324-
not n in dict:
325-
dict[n] = d[n]
326-
else:
327-
assert 0, "regexp _getnext found something unexpected"
232+
continue
233+
# add any classes that were defined in the imported module
234+
# to our name space if they were mentioned in the list
235+
for n, n2 in names:
236+
if n in d:
237+
dict[n2 or n] = d[n]
238+
elif n == '*':
239+
# only add a name if not already there (to mimic
240+
# what Python does internally) also don't add
241+
# names that start with _
242+
for n in d:
243+
if n[0] != '_' and not n in dict:
244+
dict[n] = d[n]
245+
except StopIteration:
246+
pass
328247

248+
f.close()
329249
return dict
330250

331-
def _indent(ws, _expandtabs=string.expandtabs):
332-
return len(_expandtabs(ws, TABWIDTH))
251+
def _getnamelist(g):
252+
# Helper to get a comma-separated list of dotted names plus 'as'
253+
# clauses. Return a list of pairs (name, name2) where name2 is
254+
# the 'as' name, or None if there is no 'as' clause.
255+
names = []
256+
while True:
257+
name, token = _getname(g)
258+
if not name:
259+
break
260+
if token == 'as':
261+
name2, token = _getname(g)
262+
else:
263+
name2 = None
264+
names.append((name, name2))
265+
while token != "," and "\n" not in token:
266+
tokentype, token, start, end, line = g.next()
267+
if token != ",":
268+
break
269+
return names
270+
271+
def _getname(g):
272+
# Helper to get a dotted name, return a pair (name, token) where
273+
# name is the dotted name, or None if there was no dotted name,
274+
# and token is the next input token.
275+
parts = []
276+
tokentype, token, start, end, line = g.next()
277+
if tokentype != NAME and token != '*':
278+
return (None, token)
279+
parts.append(token)
280+
while True:
281+
tokentype, token, start, end, line = g.next()
282+
if token != '.':
283+
break
284+
tokentype, token, start, end, line = g.next()
285+
if tokentype != NAME:
286+
break
287+
parts.append(token)
288+
return (".".join(parts), token)

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL