csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 1 | """Parse a Python module and describe its classes and functions. |
Sjoerd Mullender | 8cb4b1f | 1995-07-28 09:30:01 +0000 | [diff] [blame] | 2 | |
Guido van Rossum | 0a6f954 | 2002-12-03 08:14:35 +0000 | [diff] [blame] | 3 | Parse enough of a Python file to recognize imports and class and |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 4 | function definitions, and to find out the superclasses of a class. |
Sjoerd Mullender | 8cb4b1f | 1995-07-28 09:30:01 +0000 | [diff] [blame] | 5 | |
| 6 | The interface consists of a single function: |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 7 | readmodule_ex(module, path=None) |
Guido van Rossum | 0a6f954 | 2002-12-03 08:14:35 +0000 | [diff] [blame] | 8 | where module is the name of a Python module, and path is an optional |
| 9 | list of directories where the module is to be searched. If present, |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 10 | path is prepended to the system search path sys.path. The return value |
| 11 | is a dictionary. The keys of the dictionary are the names of the |
| 12 | classes and functions defined in the module (including classes that are |
| 13 | defined via the from XXX import YYY construct). The values are |
| 14 | instances of classes Class and Function. One special key/value pair is |
| 15 | present for packages: the key '__path__' has a list as its value which |
| 16 | contains the package search path. |
Sjoerd Mullender | 8cb4b1f | 1995-07-28 09:30:01 +0000 | [diff] [blame] | 17 | |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 18 | Classes and Functions have a common superclass: _Object. Every instance |
| 19 | has the following attributes: |
| 20 | module -- name of the module; |
| 21 | name -- name of the object; |
| 22 | file -- file in which the object is defined; |
| 23 | lineno -- line in the file where the object's definition starts; |
| 24 | parent -- parent of this object, if any; |
| 25 | children -- nested objects contained in this object. |
| 26 | The 'children' attribute is a dictionary mapping names to objects. |
| 27 | |
Batuhan Taskaya | fa476fe | 2020-11-11 10:14:12 +0300 | [diff] [blame] | 28 | Instances of Function describe functions with the attributes from _Object, |
| 29 | plus the following: |
| 30 | is_async -- if a function is defined with an 'async' prefix |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 31 | |
| 32 | Instances of Class describe classes with the attributes from _Object, |
| 33 | plus the following: |
| 34 | super -- list of super classes (Class instances if possible); |
| 35 | methods -- mapping of method names to beginning line numbers. |
Sjoerd Mullender | 8cb4b1f | 1995-07-28 09:30:01 +0000 | [diff] [blame] | 36 | If the name of a super class is not recognized, the corresponding |
| 37 | entry in the list of super classes is not a class instance but a |
| 38 | string giving the name of the super class. Since import statements |
| 39 | are recognized and imported modules are scanned as well, this |
| 40 | shouldn't happen often. |
Guido van Rossum | 4b8c6ea | 2000-02-04 15:39:30 +0000 | [diff] [blame] | 41 | """ |
Sjoerd Mullender | 8cb4b1f | 1995-07-28 09:30:01 +0000 | [diff] [blame] | 42 | |
Batuhan Taskaya | fa476fe | 2020-11-11 10:14:12 +0300 | [diff] [blame] | 43 | import ast |
| 44 | import copy |
Sjoerd Mullender | 8cb4b1f | 1995-07-28 09:30:01 +0000 | [diff] [blame] | 45 | import sys |
Eric Snow | 6029e08 | 2014-01-25 15:32:46 -0700 | [diff] [blame] | 46 | import importlib.util |
Sjoerd Mullender | 8cb4b1f | 1995-07-28 09:30:01 +0000 | [diff] [blame] | 47 | |
Guido van Rossum | 0ed7aa1 | 2002-12-02 14:54:20 +0000 | [diff] [blame] | 48 | __all__ = ["readmodule", "readmodule_ex", "Class", "Function"] |
Skip Montanaro | c62c81e | 2001-02-12 02:00:42 +0000 | [diff] [blame] | 49 | |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 50 | _modules = {} # Initialize cache of modules we've seen. |
Sjoerd Mullender | 8cb4b1f | 1995-07-28 09:30:01 +0000 | [diff] [blame] | 51 | |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 52 | |
| 53 | class _Object: |
Xtreak | 0d70227 | 2019-06-03 04:42:33 +0530 | [diff] [blame] | 54 | "Information about Python class or function." |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 55 | def __init__(self, module, name, file, lineno, parent): |
Tim Peters | 2344fae | 2001-01-15 00:50:52 +0000 | [diff] [blame] | 56 | self.module = module |
| 57 | self.name = name |
Tim Peters | 2344fae | 2001-01-15 00:50:52 +0000 | [diff] [blame] | 58 | self.file = file |
| 59 | self.lineno = lineno |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 60 | self.parent = parent |
| 61 | self.children = {} |
Batuhan Taskaya | fa476fe | 2020-11-11 10:14:12 +0300 | [diff] [blame] | 62 | if parent is not None: |
| 63 | parent.children[name] = self |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 64 | |
| 65 | class Function(_Object): |
| 66 | "Information about a Python function, including methods." |
Batuhan Taskaya | fa476fe | 2020-11-11 10:14:12 +0300 | [diff] [blame] | 67 | def __init__(self, module, name, file, lineno, parent=None, is_async=False): |
| 68 | super().__init__(module, name, file, lineno, parent) |
| 69 | self.is_async = is_async |
| 70 | if isinstance(parent, Class): |
| 71 | parent.methods[name] = lineno |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 72 | |
| 73 | class Class(_Object): |
| 74 | "Information about a Python class." |
Batuhan Taskaya | fa476fe | 2020-11-11 10:14:12 +0300 | [diff] [blame] | 75 | def __init__(self, module, name, super_, file, lineno, parent=None): |
| 76 | super().__init__(module, name, file, lineno, parent) |
| 77 | self.super = super_ or [] |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 78 | self.methods = {} |
Sjoerd Mullender | 8cb4b1f | 1995-07-28 09:30:01 +0000 | [diff] [blame] | 79 | |
Batuhan Taskaya | fa476fe | 2020-11-11 10:14:12 +0300 | [diff] [blame] | 80 | # These 2 functions are used in these tests |
| 81 | # Lib/test/test_pyclbr, Lib/idlelib/idle_test/test_browser.py |
| 82 | def _nest_function(ob, func_name, lineno, is_async=False): |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 83 | "Return a Function after nesting within ob." |
Batuhan Taskaya | fa476fe | 2020-11-11 10:14:12 +0300 | [diff] [blame] | 84 | return Function(ob.module, func_name, ob.file, lineno, ob, is_async) |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 85 | |
| 86 | def _nest_class(ob, class_name, lineno, super=None): |
| 87 | "Return a Class after nesting within ob." |
Batuhan Taskaya | fa476fe | 2020-11-11 10:14:12 +0300 | [diff] [blame] | 88 | return Class(ob.module, class_name, super, ob.file, lineno, ob) |
Guido van Rossum | a3b4a33 | 1999-06-10 14:39:39 +0000 | [diff] [blame] | 89 | |
Christian Heimes | 81ee3ef | 2008-05-04 22:42:01 +0000 | [diff] [blame] | 90 | def readmodule(module, path=None): |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 91 | """Return Class objects for the top-level classes in module. |
Guido van Rossum | a3b4a33 | 1999-06-10 14:39:39 +0000 | [diff] [blame] | 92 | |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 93 | This is the original interface, before Functions were added. |
| 94 | """ |
Guido van Rossum | a3b4a33 | 1999-06-10 14:39:39 +0000 | [diff] [blame] | 95 | |
Tim Peters | 2344fae | 2001-01-15 00:50:52 +0000 | [diff] [blame] | 96 | res = {} |
Christian Heimes | 81ee3ef | 2008-05-04 22:42:01 +0000 | [diff] [blame] | 97 | for key, value in _readmodule(module, path or []).items(): |
Guido van Rossum | 0ed7aa1 | 2002-12-02 14:54:20 +0000 | [diff] [blame] | 98 | if isinstance(value, Class): |
Tim Peters | 2344fae | 2001-01-15 00:50:52 +0000 | [diff] [blame] | 99 | res[key] = value |
| 100 | return res |
Guido van Rossum | a3b4a33 | 1999-06-10 14:39:39 +0000 | [diff] [blame] | 101 | |
Christian Heimes | 81ee3ef | 2008-05-04 22:42:01 +0000 | [diff] [blame] | 102 | def readmodule_ex(module, path=None): |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 103 | """Return a dictionary with all functions and classes in module. |
Sjoerd Mullender | 8cb4b1f | 1995-07-28 09:30:01 +0000 | [diff] [blame] | 104 | |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 105 | Search for module in PATH + sys.path. |
| 106 | If possible, include imported superclasses. |
| 107 | Do this by reading source, without importing (and executing) it. |
| 108 | """ |
Christian Heimes | 81ee3ef | 2008-05-04 22:42:01 +0000 | [diff] [blame] | 109 | return _readmodule(module, path or []) |
Sjoerd Mullender | 8cb4b1f | 1995-07-28 09:30:01 +0000 | [diff] [blame] | 110 | |
Christian Heimes | 81ee3ef | 2008-05-04 22:42:01 +0000 | [diff] [blame] | 111 | def _readmodule(module, path, inpackage=None): |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 112 | """Do the hard work for readmodule[_ex]. |
Christian Heimes | 81ee3ef | 2008-05-04 22:42:01 +0000 | [diff] [blame] | 113 | |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 114 | If inpackage is given, it must be the dotted name of the package in |
Guido van Rossum | 0ed7aa1 | 2002-12-02 14:54:20 +0000 | [diff] [blame] | 115 | which we are searching for a submodule, and then PATH must be the |
| 116 | package search path; otherwise, we are searching for a top-level |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 117 | module, and path is combined with sys.path. |
| 118 | """ |
| 119 | # Compute the full module name (prepending inpackage if set). |
Christian Heimes | 81ee3ef | 2008-05-04 22:42:01 +0000 | [diff] [blame] | 120 | if inpackage is not None: |
Guido van Rossum | 0ed7aa1 | 2002-12-02 14:54:20 +0000 | [diff] [blame] | 121 | fullmodule = "%s.%s" % (inpackage, module) |
| 122 | else: |
| 123 | fullmodule = module |
| 124 | |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 125 | # Check in the cache. |
Guido van Rossum | 0ed7aa1 | 2002-12-02 14:54:20 +0000 | [diff] [blame] | 126 | if fullmodule in _modules: |
| 127 | return _modules[fullmodule] |
| 128 | |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 129 | # Initialize the dict for this module's contents. |
| 130 | tree = {} |
Guido van Rossum | 3d54871 | 1999-06-09 15:49:09 +0000 | [diff] [blame] | 131 | |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 132 | # Check if it is a built-in module; we don't do much for these. |
Christian Heimes | 81ee3ef | 2008-05-04 22:42:01 +0000 | [diff] [blame] | 133 | if module in sys.builtin_module_names and inpackage is None: |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 134 | _modules[module] = tree |
| 135 | return tree |
Sjoerd Mullender | 8cb4b1f | 1995-07-28 09:30:01 +0000 | [diff] [blame] | 136 | |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 137 | # Check for a dotted module name. |
Guido van Rossum | 0ed7aa1 | 2002-12-02 14:54:20 +0000 | [diff] [blame] | 138 | i = module.rfind('.') |
| 139 | if i >= 0: |
| 140 | package = module[:i] |
| 141 | submodule = module[i+1:] |
Guido van Rossum | 0a6f954 | 2002-12-03 08:14:35 +0000 | [diff] [blame] | 142 | parent = _readmodule(package, path, inpackage) |
Christian Heimes | 81ee3ef | 2008-05-04 22:42:01 +0000 | [diff] [blame] | 143 | if inpackage is not None: |
Guido van Rossum | 0ed7aa1 | 2002-12-02 14:54:20 +0000 | [diff] [blame] | 144 | package = "%s.%s" % (inpackage, package) |
Petri Lehtinen | 8d88604 | 2012-05-18 21:51:11 +0300 | [diff] [blame] | 145 | if not '__path__' in parent: |
| 146 | raise ImportError('No package named {}'.format(package)) |
Guido van Rossum | 0a6f954 | 2002-12-03 08:14:35 +0000 | [diff] [blame] | 147 | return _readmodule(submodule, parent['__path__'], package) |
Guido van Rossum | 0ed7aa1 | 2002-12-02 14:54:20 +0000 | [diff] [blame] | 148 | |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 149 | # Search the path for the module. |
Tim Peters | 2344fae | 2001-01-15 00:50:52 +0000 | [diff] [blame] | 150 | f = None |
Christian Heimes | 81ee3ef | 2008-05-04 22:42:01 +0000 | [diff] [blame] | 151 | if inpackage is not None: |
Brett Cannon | ee78a2b | 2012-05-12 17:43:17 -0400 | [diff] [blame] | 152 | search_path = path |
Guido van Rossum | 0ed7aa1 | 2002-12-02 14:54:20 +0000 | [diff] [blame] | 153 | else: |
Brett Cannon | ee78a2b | 2012-05-12 17:43:17 -0400 | [diff] [blame] | 154 | search_path = path + sys.path |
Eric Snow | 6029e08 | 2014-01-25 15:32:46 -0700 | [diff] [blame] | 155 | spec = importlib.util._find_spec_from_path(fullmodule, search_path) |
Brett Cannon | 5086589 | 2019-03-22 15:16:50 -0700 | [diff] [blame] | 156 | if spec is None: |
| 157 | raise ModuleNotFoundError(f"no module named {fullmodule!r}", name=fullmodule) |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 158 | _modules[fullmodule] = tree |
| 159 | # Is module a package? |
Victor Stinner | 5c13aa1 | 2016-03-17 09:06:41 +0100 | [diff] [blame] | 160 | if spec.submodule_search_locations is not None: |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 161 | tree['__path__'] = spec.submodule_search_locations |
Brett Cannon | ee78a2b | 2012-05-12 17:43:17 -0400 | [diff] [blame] | 162 | try: |
Eric Snow | 02b9f9d | 2014-01-06 20:42:59 -0700 | [diff] [blame] | 163 | source = spec.loader.get_source(fullmodule) |
Brett Cannon | ee78a2b | 2012-05-12 17:43:17 -0400 | [diff] [blame] | 164 | except (AttributeError, ImportError): |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 165 | # If module is not Python source, we cannot do anything. |
| 166 | return tree |
Brett Cannon | 5086589 | 2019-03-22 15:16:50 -0700 | [diff] [blame] | 167 | else: |
| 168 | if source is None: |
| 169 | return tree |
Sjoerd Mullender | 8cb4b1f | 1995-07-28 09:30:01 +0000 | [diff] [blame] | 170 | |
Victor Stinner | 5c13aa1 | 2016-03-17 09:06:41 +0100 | [diff] [blame] | 171 | fname = spec.loader.get_filename(fullmodule) |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 172 | return _create_tree(fullmodule, path, fname, source, tree, inpackage) |
Victor Stinner | 5c13aa1 | 2016-03-17 09:06:41 +0100 | [diff] [blame] | 173 | |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 174 | |
Batuhan Taskaya | fa476fe | 2020-11-11 10:14:12 +0300 | [diff] [blame] | 175 | class _ModuleBrowser(ast.NodeVisitor): |
| 176 | def __init__(self, module, path, file, tree, inpackage): |
| 177 | self.path = path |
| 178 | self.tree = tree |
| 179 | self.file = file |
| 180 | self.module = module |
| 181 | self.inpackage = inpackage |
| 182 | self.stack = [] |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 183 | |
Batuhan Taskaya | fa476fe | 2020-11-11 10:14:12 +0300 | [diff] [blame] | 184 | def visit_ClassDef(self, node): |
| 185 | bases = [] |
| 186 | for base in node.bases: |
| 187 | name = ast.unparse(base) |
| 188 | if name in self.tree: |
| 189 | # We know this super class. |
| 190 | bases.append(self.tree[name]) |
| 191 | elif len(names := name.split(".")) > 1: |
| 192 | # Super class form is module.class: |
| 193 | # look in module for class. |
| 194 | *_, module, class_ = names |
| 195 | if module in _modules: |
| 196 | bases.append(_modules[module].get(class_, name)) |
| 197 | else: |
| 198 | bases.append(name) |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 199 | |
Batuhan Taskaya | fa476fe | 2020-11-11 10:14:12 +0300 | [diff] [blame] | 200 | parent = self.stack[-1] if self.stack else None |
| 201 | class_ = Class( |
| 202 | self.module, node.name, bases, self.file, node.lineno, parent |
| 203 | ) |
| 204 | if parent is None: |
| 205 | self.tree[node.name] = class_ |
| 206 | self.stack.append(class_) |
| 207 | self.generic_visit(node) |
| 208 | self.stack.pop() |
Brett Cannon | ee78a2b | 2012-05-12 17:43:17 -0400 | [diff] [blame] | 209 | |
Batuhan Taskaya | fa476fe | 2020-11-11 10:14:12 +0300 | [diff] [blame] | 210 | def visit_FunctionDef(self, node, *, is_async=False): |
| 211 | parent = self.stack[-1] if self.stack else None |
| 212 | function = Function( |
| 213 | self.module, node.name, self.file, node.lineno, parent, is_async |
| 214 | ) |
| 215 | if parent is None: |
| 216 | self.tree[node.name] = function |
| 217 | self.stack.append(function) |
| 218 | self.generic_visit(node) |
| 219 | self.stack.pop() |
Guido van Rossum | ad38055 | 1999-06-07 15:25:18 +0000 | [diff] [blame] | 220 | |
Batuhan Taskaya | fa476fe | 2020-11-11 10:14:12 +0300 | [diff] [blame] | 221 | def visit_AsyncFunctionDef(self, node): |
| 222 | self.visit_FunctionDef(node, is_async=True) |
| 223 | |
| 224 | def visit_Import(self, node): |
| 225 | if node.col_offset != 0: |
| 226 | return |
| 227 | |
| 228 | for module in node.names: |
| 229 | try: |
Tim Peters | 2344fae | 2001-01-15 00:50:52 +0000 | [diff] [blame] | 230 | try: |
Batuhan Taskaya | fa476fe | 2020-11-11 10:14:12 +0300 | [diff] [blame] | 231 | _readmodule(module.name, self.path, self.inpackage) |
| 232 | except ImportError: |
| 233 | _readmodule(module.name, []) |
| 234 | except (ImportError, SyntaxError): |
| 235 | # If we can't find or parse the imported module, |
| 236 | # too bad -- don't die here. |
| 237 | continue |
Guido van Rossum | ad38055 | 1999-06-07 15:25:18 +0000 | [diff] [blame] | 238 | |
Batuhan Taskaya | fa476fe | 2020-11-11 10:14:12 +0300 | [diff] [blame] | 239 | def visit_ImportFrom(self, node): |
| 240 | if node.col_offset != 0: |
| 241 | return |
| 242 | try: |
| 243 | module = "." * node.level |
| 244 | if node.module: |
| 245 | module += node.module |
| 246 | module = _readmodule(module, self.path, self.inpackage) |
| 247 | except (ImportError, SyntaxError): |
| 248 | return |
| 249 | |
| 250 | for name in node.names: |
| 251 | if name.name in module: |
| 252 | self.tree[name.asname or name.name] = module[name.name] |
| 253 | elif name.name == "*": |
| 254 | for import_name, import_value in module.items(): |
| 255 | if import_name.startswith("_"): |
| 256 | continue |
| 257 | self.tree[import_name] = import_value |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 258 | |
Guido van Rossum | df9f7a3 | 1999-06-08 12:53:21 +0000 | [diff] [blame] | 259 | |
Batuhan Taskaya | fa476fe | 2020-11-11 10:14:12 +0300 | [diff] [blame] | 260 | def _create_tree(fullmodule, path, fname, source, tree, inpackage): |
| 261 | mbrowser = _ModuleBrowser(fullmodule, path, fname, tree, inpackage) |
| 262 | mbrowser.visit(ast.parse(source)) |
| 263 | return mbrowser.tree |
Guido van Rossum | 0a6f954 | 2002-12-03 08:14:35 +0000 | [diff] [blame] | 264 | |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 265 | |
Guido van Rossum | 0a6f954 | 2002-12-03 08:14:35 +0000 | [diff] [blame] | 266 | def _main(): |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 267 | "Print module output (default this file) for quick visual check." |
Guido van Rossum | 0a6f954 | 2002-12-03 08:14:35 +0000 | [diff] [blame] | 268 | import os |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 269 | try: |
| 270 | mod = sys.argv[1] |
| 271 | except: |
| 272 | mod = __file__ |
Guido van Rossum | 0a6f954 | 2002-12-03 08:14:35 +0000 | [diff] [blame] | 273 | if os.path.exists(mod): |
| 274 | path = [os.path.dirname(mod)] |
| 275 | mod = os.path.basename(mod) |
| 276 | if mod.lower().endswith(".py"): |
| 277 | mod = mod[:-3] |
| 278 | else: |
| 279 | path = [] |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 280 | tree = readmodule_ex(mod, path) |
| 281 | lineno_key = lambda a: getattr(a, 'lineno', 0) |
| 282 | objs = sorted(tree.values(), key=lineno_key, reverse=True) |
| 283 | indent_level = 2 |
| 284 | while objs: |
| 285 | obj = objs.pop() |
| 286 | if isinstance(obj, list): |
| 287 | # Value is a __path__ key. |
| 288 | continue |
| 289 | if not hasattr(obj, 'indent'): |
| 290 | obj.indent = 0 |
| 291 | |
| 292 | if isinstance(obj, _Object): |
| 293 | new_objs = sorted(obj.children.values(), |
| 294 | key=lineno_key, reverse=True) |
| 295 | for ob in new_objs: |
| 296 | ob.indent = obj.indent + indent_level |
| 297 | objs.extend(new_objs) |
Guido van Rossum | 0a6f954 | 2002-12-03 08:14:35 +0000 | [diff] [blame] | 298 | if isinstance(obj, Class): |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 299 | print("{}class {} {} {}" |
| 300 | .format(' ' * obj.indent, obj.name, obj.super, obj.lineno)) |
Guido van Rossum | 0a6f954 | 2002-12-03 08:14:35 +0000 | [diff] [blame] | 301 | elif isinstance(obj, Function): |
csabella | 246ff3b | 2017-07-03 21:31:25 -0400 | [diff] [blame] | 302 | print("{}def {} {}".format(' ' * obj.indent, obj.name, obj.lineno)) |
Guido van Rossum | 0a6f954 | 2002-12-03 08:14:35 +0000 | [diff] [blame] | 303 | |
| 304 | if __name__ == "__main__": |
| 305 | _main() |