| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent fd372aa commit 040d7ca
1 file changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,10 +4,11 @@ | |||
| 4 | 4 | definitions and to find out the superclasses of a class. | |
| 5 | 5 | ||
| 6 | 6 | The interface consists of a single function: | |
| 7 | - readmodule(module, path) | ||
| 7 | + readmodule_ex(module [, path[, inpackage]]) | ||
| 8 | 8 | module is the name of a Python module, path is an optional list of | |
| 9 | 9 | 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.) | ||
| 11 | 12 | The return value is a dictionary. The keys of the dictionary are | |
| 12 | 13 | the names of the classes defined in the module (including classes | |
| 13 | 14 | that are defined via the from XXX import YYY construct). The values | |
@@ -28,12 +29,10 @@ | |||
| 28 | 29 | are recognized and imported modules are scanned as well, this | |
| 29 | 30 | shouldn't happen often. | |
| 30 | 31 | ||
| 32 | + XXX describe the Function class. | ||
| 33 | + | ||
| 31 | 34 | BUGS | |
| 32 | - - Continuation lines are not dealt with at all, except inside strings. | ||
| 33 | 35 | - 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. | ||
| 37 | 36 | ||
| 38 | 37 | PACKAGE RELATED BUGS | |
| 39 | 38 | - If you have a package and a module inside that or another package | |
@@ -52,69 +51,11 @@ class MyClass(subpackage.SuperClass): | |||
| 52 | 51 | ||
| 53 | 52 | import sys | |
| 54 | 53 | import imp | |
| 55 | - import re | ||
| 56 | - import string | ||
| 54 | + import tokenize # Python tokenizer | ||
| 55 | + from token import NAME | ||
| 57 | 56 | ||
| 58 | 57 | __all__ = ["readmodule"] | |
| 59 | 58 | ||
| 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 | - | ||
| 118 | 59 | _modules = {} # cache of modules we've seen | |
| 119 | 60 | ||
| 120 | 61 | # each Python class is represented by an instance of this class | |
@@ -140,7 +81,7 @@ def __init__(self, module, name, file, lineno): | |||
| 140 | 81 | def _addmethod(self, name, lineno): | |
| 141 | 82 | assert 0, "Function._addmethod() shouldn't be called" | |
| 142 | 83 | ||
| 143 | - def readmodule(module, path=[], inpackage=0): | ||
| 84 | + def readmodule(module, path=[], inpackage=False): | ||
| 144 | 85 | '''Backwards compatible interface. | |
| 145 | 86 | ||
| 146 | 87 | Like readmodule_ex() but strips Function objects from the | |
@@ -153,7 +94,7 @@ def readmodule(module, path=[], inpackage=0): | |||
| 153 | 94 | res[key] = value | |
| 154 | 95 | return res | |
| 155 | 96 | ||
| 156 | - def readmodule_ex(module, path=[], inpackage=0): | ||
| 97 | + def readmodule_ex(module, path=[], inpackage=False): | ||
| 157 | 98 | '''Read a module file and return a dictionary of classes. | |
| 158 | 99 | ||
| 159 | 100 | Search for MODULE in PATH and sys.path, read and parse the | |
@@ -168,7 +109,7 @@ def readmodule_ex(module, path=[], inpackage=0): | |||
| 168 | 109 | package = module[:i].strip() | |
| 169 | 110 | submodule = module[i+1:].strip() | |
| 170 | 111 | parent = readmodule_ex(package, path, inpackage) | |
| 171 | - child = readmodule_ex(submodule, parent['__path__'], 1) | ||
| 112 | + child = readmodule_ex(submodule, parent['__path__'], True) | ||
| 172 | 113 | return child | |
| 173 | 114 | ||
| 174 | 115 | if module in _modules: | |
@@ -204,129 +145,144 @@ def readmodule_ex(module, path=[], inpackage=0): | |||
| 204 | 145 | ||
| 205 | 146 | _modules[module] = dict | |
| 206 | 147 | 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 | ||
| 245 | 148 | ||
| 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) | ||
| 292 | 228 | try: | |
| 293 | 229 | # recursively read the imported module | |
| 294 | - d = readmodule_ex(n, path, inpackage) | ||
| 230 | + d = readmodule_ex(mod, path, inpackage) | ||
| 295 | 231 | 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 | ||
| 328 | 247 | ||
| 248 | + f.close() | ||
| 329 | 249 | return dict | |
| 330 | 250 | ||
| 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) | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments