| 1 | # This program is free software; you can redistribute it and/or modify it under |
|---|
| 2 | # the terms of the GNU General Public License as published by the Free Software |
|---|
| 3 | # Foundation; either version 2 of the License, or (at your option) any later |
|---|
| 4 | # version. |
|---|
| 5 | # |
|---|
| 6 | # This program is distributed in the hope that it will be useful, but WITHOUT |
|---|
| 7 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|---|
| 8 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
|---|
| 9 | # |
|---|
| 10 | # You should have received a copy of the GNU General Public License along with |
|---|
| 11 | # this program; if not, write to the Free Software Foundation, Inc., |
|---|
| 12 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|---|
| 13 | """visitor doing some postprocessing on the astng tree. |
|---|
| 14 | Try to resolve definitions (namespace) dictionnary, relationship... |
|---|
| 15 | |
|---|
| 16 | This module has been imported from pyreverse |
|---|
| 17 | |
|---|
| 18 | |
|---|
| 19 | :version: $Revision: 1.6 $ |
|---|
| 20 | :author: Sylvain Thenault |
|---|
| 21 | :copyright: 2003-2005 LOGILAB S.A. (Paris, FRANCE) |
|---|
| 22 | :contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org |
|---|
| 23 | :copyright: 2003-2005 Sylvain Thenault |
|---|
| 24 | :contact: mailto:thenault@gmail.com |
|---|
| 25 | """ |
|---|
| 26 | |
|---|
| 27 | __docformat__ = "restructuredtext en" |
|---|
| 28 | |
|---|
| 29 | from os.path import dirname |
|---|
| 30 | |
|---|
| 31 | from logilab.common.modutils import get_module_part, is_relative, \ |
|---|
| 32 | is_standard_module |
|---|
| 33 | |
|---|
| 34 | from logilab import astng |
|---|
| 35 | from logilab.astng.utils import LocalsVisitor |
|---|
| 36 | |
|---|
| 37 | class IdGeneratorMixIn: |
|---|
| 38 | """ |
|---|
| 39 | Mixin adding the ability to generate integer uid |
|---|
| 40 | """ |
|---|
| 41 | def __init__(self, start_value=0): |
|---|
| 42 | self.id_count = start_value |
|---|
| 43 | |
|---|
| 44 | def init_counter(self, start_value=0): |
|---|
| 45 | """init the id counter |
|---|
| 46 | """ |
|---|
| 47 | self.id_count = start_value |
|---|
| 48 | |
|---|
| 49 | def generate_id(self): |
|---|
| 50 | """generate a new identifer |
|---|
| 51 | """ |
|---|
| 52 | self.id_count += 1 |
|---|
| 53 | return self.id_count |
|---|
| 54 | |
|---|
| 55 | |
|---|
| 56 | class Linker(IdGeneratorMixIn, LocalsVisitor): |
|---|
| 57 | """ |
|---|
| 58 | walk on the project tree and resolve relationships. |
|---|
| 59 | |
|---|
| 60 | According to options the following attributes may be added to visited nodes: |
|---|
| 61 | |
|---|
| 62 | * uid, |
|---|
| 63 | a unique identifier for the node (on astng.Project, astng.Module, |
|---|
| 64 | astng.Class and astng.locals_type). Only if the linker has been instantiad |
|---|
| 65 | with tag=True parameter (False by default). |
|---|
| 66 | |
|---|
| 67 | * Function |
|---|
| 68 | a mapping from locals'names to their bounded value, which may be a |
|---|
| 69 | constant like a string or an integer, or an astng node (on astng.Module, |
|---|
| 70 | astng.Class and astng.Function). |
|---|
| 71 | |
|---|
| 72 | * instance_attrs_type |
|---|
| 73 | as locals_type but for klass member attributes (only on astng.Class) |
|---|
| 74 | |
|---|
| 75 | * implements, |
|---|
| 76 | list of implemented interfaces _objects_ (only on astng.Class nodes) |
|---|
| 77 | """ |
|---|
| 78 | |
|---|
| 79 | def __init__(self, project, inherited_interfaces=0, tag=False): |
|---|
| 80 | IdGeneratorMixIn.__init__(self) |
|---|
| 81 | LocalsVisitor.__init__(self) |
|---|
| 82 | # take inherited interface in consideration or not |
|---|
| 83 | self.inherited_interfaces = inherited_interfaces |
|---|
| 84 | # tag nodes or not |
|---|
| 85 | self.tag = tag |
|---|
| 86 | # visited project |
|---|
| 87 | self.project = project |
|---|
| 88 | |
|---|
| 89 | |
|---|
| 90 | def visit_project(self, node): |
|---|
| 91 | """visit an astng.Project node |
|---|
| 92 | |
|---|
| 93 | * optionaly tag the node wth a unique id |
|---|
| 94 | """ |
|---|
| 95 | if self.tag: |
|---|
| 96 | node.uid = self.generate_id() |
|---|
| 97 | for module in node.modules: |
|---|
| 98 | self.visit(module) |
|---|
| 99 | |
|---|
| 100 | def visit_package(self, node): |
|---|
| 101 | """visit an astng.Package node |
|---|
| 102 | |
|---|
| 103 | * optionaly tag the node wth a unique id |
|---|
| 104 | """ |
|---|
| 105 | if self.tag: |
|---|
| 106 | node.uid = self.generate_id() |
|---|
| 107 | for subelmt in node.values(): |
|---|
| 108 | self.visit(subelmt) |
|---|
| 109 | |
|---|
| 110 | def visit_module(self, node): |
|---|
| 111 | """visit an astng.Module node |
|---|
| 112 | |
|---|
| 113 | * set the locals_type mapping |
|---|
| 114 | * set the depends mapping |
|---|
| 115 | * optionaly tag the node wth a unique id |
|---|
| 116 | """ |
|---|
| 117 | if hasattr(node, 'locals_type'): |
|---|
| 118 | return |
|---|
| 119 | node.locals_type = {} |
|---|
| 120 | node.depends = [] |
|---|
| 121 | if self.tag: |
|---|
| 122 | node.uid = self.generate_id() |
|---|
| 123 | |
|---|
| 124 | def visit_class(self, node): |
|---|
| 125 | """visit an astng.Class node |
|---|
| 126 | |
|---|
| 127 | * set the locals_type and instance_attrs_type mappings |
|---|
| 128 | * set the implements list and build it |
|---|
| 129 | * optionaly tag the node wth a unique id |
|---|
| 130 | """ |
|---|
| 131 | if hasattr(node, 'locals_type'): |
|---|
| 132 | return |
|---|
| 133 | node.locals_type = {} |
|---|
| 134 | if self.tag: |
|---|
| 135 | node.uid = self.generate_id() |
|---|
| 136 | # resolve ancestors |
|---|
| 137 | for baseobj in node.ancestors(recurs=False): |
|---|
| 138 | specializations = getattr(baseobj, 'specializations', []) |
|---|
| 139 | specializations.append(node) |
|---|
| 140 | baseobj.specializations = specializations |
|---|
| 141 | # resolve instance attributes |
|---|
| 142 | node.instance_attrs_type = {} |
|---|
| 143 | for assattrs in node.instance_attrs.values(): |
|---|
| 144 | for assattr in assattrs: |
|---|
| 145 | self.visit_assattr(assattr, node) |
|---|
| 146 | # resolve implemented interface |
|---|
| 147 | try: |
|---|
| 148 | node.implements = list(node.interfaces(self.inherited_interfaces)) |
|---|
| 149 | except TypeError: |
|---|
| 150 | node.implements = () |
|---|
| 151 | |
|---|
| 152 | def visit_function(self, node): |
|---|
| 153 | """visit an astng.Function node |
|---|
| 154 | |
|---|
| 155 | * set the locals_type mapping |
|---|
| 156 | * optionaly tag the node wth a unique id |
|---|
| 157 | """ |
|---|
| 158 | if hasattr(node, 'locals_type'): |
|---|
| 159 | return |
|---|
| 160 | node.locals_type = {} |
|---|
| 161 | if self.tag: |
|---|
| 162 | node.uid = self.generate_id() |
|---|
| 163 | |
|---|
| 164 | link_project = visit_project |
|---|
| 165 | link_module = visit_module |
|---|
| 166 | link_class = visit_class |
|---|
| 167 | link_function = visit_function |
|---|
| 168 | |
|---|
| 169 | def visit_assname(self, node): |
|---|
| 170 | """visit an astng.AssName node |
|---|
| 171 | |
|---|
| 172 | handle locals_type |
|---|
| 173 | """ |
|---|
| 174 | frame = node.frame() |
|---|
| 175 | try: |
|---|
| 176 | values = list(node.infer()) |
|---|
| 177 | try: |
|---|
| 178 | already_infered = frame.locals_type[node.name] |
|---|
| 179 | for valnode in values: |
|---|
| 180 | if not valnode in already_infered: |
|---|
| 181 | already_infered.append(valnode) |
|---|
| 182 | except KeyError: |
|---|
| 183 | frame.locals_type[node.name] = values |
|---|
| 184 | except astng.InferenceError: |
|---|
| 185 | pass |
|---|
| 186 | |
|---|
| 187 | def visit_assattr(self, node, parent): |
|---|
| 188 | """visit an astng.AssAttr node |
|---|
| 189 | |
|---|
| 190 | handle instance_attrs_type |
|---|
| 191 | """ |
|---|
| 192 | try: |
|---|
| 193 | values = list(node.infer()) |
|---|
| 194 | try: |
|---|
| 195 | already_infered = parent.instance_attrs_type[node.attrname] |
|---|
| 196 | for valnode in values: |
|---|
| 197 | if not valnode in already_infered: |
|---|
| 198 | already_infered.append(valnode) |
|---|
| 199 | except KeyError: |
|---|
| 200 | parent.instance_attrs_type[node.attrname] = values |
|---|
| 201 | except astng.InferenceError: |
|---|
| 202 | pass |
|---|
| 203 | |
|---|
| 204 | def visit_import(self, node): |
|---|
| 205 | """visit an astng.Import node |
|---|
| 206 | |
|---|
| 207 | resolve module dependencies |
|---|
| 208 | """ |
|---|
| 209 | context_file = node.root().file |
|---|
| 210 | for name in node.names: |
|---|
| 211 | relative = is_relative(name[0], context_file) |
|---|
| 212 | self._imported_module(node, name[0], relative) |
|---|
| 213 | |
|---|
| 214 | |
|---|
| 215 | def visit_from(self, node): |
|---|
| 216 | """visit an astng.From node |
|---|
| 217 | |
|---|
| 218 | resolve module dependencies |
|---|
| 219 | """ |
|---|
| 220 | basename = node.modname |
|---|
| 221 | context_file = node.root().file |
|---|
| 222 | if context_file is not None: |
|---|
| 223 | relative = is_relative(basename, context_file) |
|---|
| 224 | else: |
|---|
| 225 | relative = False |
|---|
| 226 | for name in node.names: |
|---|
| 227 | if name[0] == '*': |
|---|
| 228 | continue |
|---|
| 229 | # analyze dependancies |
|---|
| 230 | fullname = '%s.%s' % (basename, name[0]) |
|---|
| 231 | if fullname.find('.') > -1: |
|---|
| 232 | try: |
|---|
| 233 | # XXX: don't use get_module_part, missing package precedence |
|---|
| 234 | fullname = get_module_part(fullname) |
|---|
| 235 | except ImportError: |
|---|
| 236 | continue |
|---|
| 237 | if fullname != basename: |
|---|
| 238 | self._imported_module(node, fullname, relative) |
|---|
| 239 | |
|---|
| 240 | |
|---|
| 241 | def compute_module(self, context_name, mod_path): |
|---|
| 242 | """return true if the module should be added to dependencies""" |
|---|
| 243 | package_dir = dirname(self.project.path) |
|---|
| 244 | if context_name == mod_path: |
|---|
| 245 | return 0 |
|---|
| 246 | elif is_standard_module(mod_path, (package_dir,)): |
|---|
| 247 | return 1 |
|---|
| 248 | return 0 |
|---|
| 249 | |
|---|
| 250 | # protected methods ######################################################## |
|---|
| 251 | |
|---|
| 252 | def _imported_module(self, node, mod_path, relative): |
|---|
| 253 | """notify an imported module, used to analyze dependancies |
|---|
| 254 | """ |
|---|
| 255 | module = node.root() |
|---|
| 256 | context_name = module.name |
|---|
| 257 | if relative: |
|---|
| 258 | mod_path = '%s.%s' % ('.'.join(context_name.split('.')[:-1]), |
|---|
| 259 | mod_path) |
|---|
| 260 | if self.compute_module(context_name, mod_path): |
|---|
| 261 | # handle dependancies |
|---|
| 262 | if not hasattr(module, 'depends'): |
|---|
| 263 | module.depends = [] |
|---|
| 264 | mod_paths = module.depends |
|---|
| 265 | if not mod_path in mod_paths: |
|---|
| 266 | mod_paths.append(mod_path) |
|---|