diff options
author | Claudiu Popa <cpopa@cloudbasesolutions.com> | 2015-07-03 15:18:22 +0300 |
---|---|---|
committer | Claudiu Popa <cpopa@cloudbasesolutions.com> | 2015-07-03 15:18:22 +0300 |
commit | 91ec294414a2ad75c3f0afaef6a57a4e46a11330 (patch) | |
tree | 0208a5ef8aa8b5af4546257a7d8a456d56c5603c /pylint/pyreverse | |
parent | 54e4b98eae6108e59dbbc754b5ed80dacc23bfe4 (diff) | |
download | pylint-91ec294414a2ad75c3f0afaef6a57a4e46a11330.tar.gz |
New imported features from astroid into pyreverse.
We moved pyreverse.inspector.Project, pyreverse.inspector.project_from_files
and pyreverse.inspector.interfaces. These were moved since they didn't belong in astroid
and they can be better maintained inside pyreverse itself.
Diffstat (limited to 'pylint/pyreverse')
-rw-r--r-- | pylint/pyreverse/diadefslib.py | 6 | ||||
-rw-r--r-- | pylint/pyreverse/inspector.py | 111 | ||||
-rw-r--r-- | pylint/pyreverse/main.py | 15 | ||||
-rw-r--r-- | pylint/pyreverse/utils.py | 5 |
4 files changed, 124 insertions, 13 deletions
diff --git a/pylint/pyreverse/diadefslib.py b/pylint/pyreverse/diadefslib.py index ab6c946..2c6d15b 100644 --- a/pylint/pyreverse/diadefslib.py +++ b/pylint/pyreverse/diadefslib.py @@ -134,7 +134,7 @@ class DefaultDiadefGenerator(LocalsVisitor, DiaDefGenerator): LocalsVisitor.__init__(self) def visit_project(self, node): - """visit an astroid.Project node + """visit an pyreverse.utils.Project node create a diagram definition for packages """ @@ -146,7 +146,7 @@ class DefaultDiadefGenerator(LocalsVisitor, DiaDefGenerator): self.classdiagram = ClassDiagram('classes %s' % node.name, mode) def leave_project(self, node): # pylint: disable=unused-argument - """leave the astroid.Project node + """leave the pyreverse.utils.Project node return the generated diagram definition """ @@ -218,7 +218,7 @@ class DiadefsHandler(object): def get_diadefs(self, project, linker): """get the diagrams configuration data :param linker: pyreverse.inspector.Linker(IdGeneratorMixIn, LocalsVisitor) - :param project: astroid.manager.Project + :param project: pyreverse.utils.Project """ # read and interpret diagram definitions (Diadefs) diff --git a/pylint/pyreverse/inspector.py b/pylint/pyreverse/inspector.py index 812fe42..208f405 100644 --- a/pylint/pyreverse/inspector.py +++ b/pylint/pyreverse/inspector.py @@ -14,20 +14,64 @@ # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - """ Visitor doing some postprocessing on the astroid tree. Try to resolve definitions (namespace) dictionary, relationship... """ +from __future__ import print_function + import collections import os +import traceback import astroid +from astroid import bases +from astroid import exceptions +from astroid import manager from astroid import modutils +from astroid import node_classes + from pylint.pyreverse import utils +def _iface_hdlr(_): + """Handler used by interfaces to handle suspicious interface nodes.""" + return True + + +def _astroid_wrapper(func, modname): + print('parsing %s...' % modname) + try: + return func(modname) + except exceptions.AstroidBuildingException as exc: + print(exc) + except Exception as exc: # pylint: disable=broad-except + traceback.print_exc() + + +def interfaces(node, herited=True, handler_func=_iface_hdlr): + """Return an iterator on interfaces implemented by the given class node.""" + # FIXME: what if __implements__ = (MyIFace, MyParent.__implements__)... + try: + implements = bases.Instance(node).getattr('__implements__')[0] + except exceptions.NotFoundError: + return + if not herited and not implements.frame() is node: + return + found = set() + missing = False + for iface in node_classes.unpack_infer(implements): + if iface is bases.YES: + missing = True + continue + if iface not in found and handler_func(iface): + found.add(iface) + yield iface + if missing: + raise exceptions.InferenceError() + + class IdGeneratorMixIn(object): """Mixin adding the ability to generate integer uid.""" @@ -80,7 +124,7 @@ class Linker(IdGeneratorMixIn, utils.LocalsVisitor): self.project = project def visit_project(self, node): - """visit an astroid.Project node + """visit an pyreverse.utils.Project node * optionally tag the node with a unique id """ @@ -137,7 +181,7 @@ class Linker(IdGeneratorMixIn, utils.LocalsVisitor): self.handle_assattr_type(assattr, node) # resolve implemented interface try: - node.implements = list(node.interfaces(self.inherited_interfaces)) + node.implements = list(interfaces(node, self.inherited_interfaces)) except astroid.InferenceError: node.implements = () @@ -265,3 +309,64 @@ class Linker(IdGeneratorMixIn, utils.LocalsVisitor): mod_paths = module.depends if mod_path not in mod_paths: mod_paths.append(mod_path) + + +class Project(object): + """a project handle a set of modules / packages""" + def __init__(self, name=''): + self.name = name + self.path = None + self.modules = [] + self.locals = {} + self.__getitem__ = self.locals.__getitem__ + self.__iter__ = self.locals.__iter__ + self.values = self.locals.values + self.keys = self.locals.keys + self.items = self.locals.items + + def add_module(self, node): + self.locals[node.name] = node + self.modules.append(node) + + def get_module(self, name): + return self.locals[name] + + def get_children(self): + return self.modules + + def __repr__(self): + return '<Project %r at %s (%s modules)>' % (self.name, id(self), + len(self.modules)) + + +def project_from_files(files, func_wrapper=_astroid_wrapper, + project_name="no name", + black_list=('CVS',)): + """return a Project from a list of files or modules""" + # build the project representation + astroid_manager = manager.AstroidManager() + project = Project(project_name) + for something in files: + if not os.path.exists(something): + fpath = modutils.file_from_modpath(something.split('.')) + elif os.path.isdir(something): + fpath = os.path.join(something, '__init__.py') + else: + fpath = something + ast = func_wrapper(astroid_manager.ast_from_file, fpath) + if ast is None: + continue + # XXX why is first file defining the project.path ? + project.path = project.path or ast.file + project.add_module(ast) + base_name = ast.name + # recurse in package except if __init__ was explicitly given + if ast.package and something.find('__init__') == -1: + # recurse on others packages / modules if this is a package + for fpath in modutils.get_module_files(os.path.dirname(ast.file), + black_list): + ast = func_wrapper(astroid_manager.ast_from_file, fpath) + if ast is None or ast.name == base_name: + continue + project.add_module(ast) + return project diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index f517d23..4cf51d6 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -22,9 +22,8 @@ from __future__ import print_function import sys, os from logilab.common.configuration import ConfigurationMixIn -from astroid.manager import AstroidManager -from pylint.pyreverse.inspector import Linker +from pylint.pyreverse.inspector import Linker, project_from_files from pylint.pyreverse.diadefslib import DiadefsHandler from pylint.pyreverse import writer from pylint.pyreverse.utils import insert_default_options @@ -79,6 +78,13 @@ this disables -f values")), ("output", dict(short="o", dest="output_format", action="store", default="dot", metavar="<format>", help="create a *.<format> output file if format available.")), + ("ignore", {'type' : "csv", 'metavar' : "<file>", + 'dest' : "black_list", "default" : ('CVS',), + 'help' : "add <file> (may be a directory) to the black list. " + "It should be a base name, not a path. You may set " + "this option multiple times."}), + ("project", {'default': "No Name", 'type' : 'string', 'short': 'p', + 'metavar': '<project name>', 'help' : 'set the project name.'}), ) # FIXME : quiet mode #( ('quiet', @@ -92,8 +98,6 @@ class Run(ConfigurationMixIn): def __init__(self, args): ConfigurationMixIn.__init__(self, usage=__doc__) insert_default_options() - self.manager = AstroidManager() - self.register_options_provider(self.manager) args = self.load_command_line_configuration() sys.exit(self.run(args)) @@ -106,7 +110,8 @@ class Run(ConfigurationMixIn): # dependencies to local modules even if cwd is not in the PYTHONPATH sys.path.insert(0, os.getcwd()) try: - project = self.manager.project_from_files(args) + project = project_from_files(args, project_name=self.config.project, + black_list=self.config.black_list) linker = Linker(project, tag=True) handler = DiadefsHandler(self.config) diadefs = handler.get_diadefs(project, linker) diff --git a/pylint/pyreverse/utils.py b/pylint/pyreverse/utils.py index 0127d27..9609ad0 100644 --- a/pylint/pyreverse/utils.py +++ b/pylint/pyreverse/utils.py @@ -18,9 +18,9 @@ generic classes/functions for pyreverse core/extensions """ from __future__ import print_function -import sys -import re import os +import re +import sys ########### pyreverse option utils ############################## @@ -110,6 +110,7 @@ MODES = { VIS_MOD = {'special': _SPECIAL, 'protected': _PROTECTED, 'private': _PRIVATE, 'public': 0} + class FilterMixIn(object): """filter nodes according to a mode and nodes' visibility """ |