summaryrefslogtreecommitdiff
path: root/pylint/pyreverse
diff options
context:
space:
mode:
authorClaudiu Popa <cpopa@cloudbasesolutions.com>2015-07-03 15:18:22 +0300
committerClaudiu Popa <cpopa@cloudbasesolutions.com>2015-07-03 15:18:22 +0300
commit91ec294414a2ad75c3f0afaef6a57a4e46a11330 (patch)
tree0208a5ef8aa8b5af4546257a7d8a456d56c5603c /pylint/pyreverse
parent54e4b98eae6108e59dbbc754b5ed80dacc23bfe4 (diff)
downloadpylint-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.py6
-rw-r--r--pylint/pyreverse/inspector.py111
-rw-r--r--pylint/pyreverse/main.py15
-rw-r--r--pylint/pyreverse/utils.py5
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
"""