adding new stuff
This commit is contained in:
parent
f84d7183aa
commit
9ef8a96f9a
1580 changed files with 0 additions and 0 deletions
|
|
@ -0,0 +1,5 @@
|
|||
"""
|
||||
pyreverse.extensions
|
||||
"""
|
||||
|
||||
__revision__ = "$Id $"
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
# Copyright (c) 2000-2013 LOGILAB S.A. (Paris, FRANCE).
|
||||
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation; either version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# 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.
|
||||
"""handle diagram generation options for class diagram or default diagrams
|
||||
"""
|
||||
|
||||
from logilab.common.compat import builtins
|
||||
|
||||
import astroid
|
||||
from astroid.utils import LocalsVisitor
|
||||
|
||||
from pylint.pyreverse.diagrams import PackageDiagram, ClassDiagram
|
||||
|
||||
BUILTINS_NAME = builtins.__name__
|
||||
|
||||
# diagram generators ##########################################################
|
||||
|
||||
class DiaDefGenerator(object):
|
||||
"""handle diagram generation options"""
|
||||
|
||||
def __init__(self, linker, handler):
|
||||
"""common Diagram Handler initialization"""
|
||||
self.config = handler.config
|
||||
self._set_default_options()
|
||||
self.linker = linker
|
||||
self.classdiagram = None # defined by subclasses
|
||||
|
||||
def get_title(self, node):
|
||||
"""get title for objects"""
|
||||
title = node.name
|
||||
if self.module_names:
|
||||
title = '%s.%s' % (node.root().name, title)
|
||||
return title
|
||||
|
||||
def _set_option(self, option):
|
||||
"""activate some options if not explicitly deactivated"""
|
||||
# if we have a class diagram, we want more information by default;
|
||||
# so if the option is None, we return True
|
||||
if option is None:
|
||||
if self.config.classes:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return option
|
||||
|
||||
def _set_default_options(self):
|
||||
"""set different default options with _default dictionary"""
|
||||
self.module_names = self._set_option(self.config.module_names)
|
||||
all_ancestors = self._set_option(self.config.all_ancestors)
|
||||
all_associated = self._set_option(self.config.all_associated)
|
||||
anc_level, ass_level = (0, 0)
|
||||
if all_ancestors:
|
||||
anc_level = -1
|
||||
if all_associated:
|
||||
ass_level = -1
|
||||
if self.config.show_ancestors is not None:
|
||||
anc_level = self.config.show_ancestors
|
||||
if self.config.show_associated is not None:
|
||||
ass_level = self.config.show_associated
|
||||
self.anc_level, self.ass_level = anc_level, ass_level
|
||||
|
||||
def _get_levels(self):
|
||||
"""help function for search levels"""
|
||||
return self.anc_level, self.ass_level
|
||||
|
||||
def show_node(self, node):
|
||||
"""true if builtins and not show_builtins"""
|
||||
if self.config.show_builtin:
|
||||
return True
|
||||
return node.root().name != BUILTINS_NAME
|
||||
|
||||
def add_class(self, node):
|
||||
"""visit one class and add it to diagram"""
|
||||
self.linker.visit(node)
|
||||
self.classdiagram.add_object(self.get_title(node), node)
|
||||
|
||||
def get_ancestors(self, node, level):
|
||||
"""return ancestor nodes of a class node"""
|
||||
if level == 0:
|
||||
return
|
||||
for ancestor in node.ancestors(recurs=False):
|
||||
if not self.show_node(ancestor):
|
||||
continue
|
||||
yield ancestor
|
||||
|
||||
def get_associated(self, klass_node, level):
|
||||
"""return associated nodes of a class node"""
|
||||
if level == 0:
|
||||
return
|
||||
for ass_nodes in list(klass_node.instance_attrs_type.values()) + \
|
||||
list(klass_node.locals_type.values()):
|
||||
for ass_node in ass_nodes:
|
||||
if isinstance(ass_node, astroid.Instance):
|
||||
ass_node = ass_node._proxied
|
||||
if not (isinstance(ass_node, astroid.Class)
|
||||
and self.show_node(ass_node)):
|
||||
continue
|
||||
yield ass_node
|
||||
|
||||
def extract_classes(self, klass_node, anc_level, ass_level):
|
||||
"""extract recursively classes related to klass_node"""
|
||||
if self.classdiagram.has_node(klass_node) or not self.show_node(klass_node):
|
||||
return
|
||||
self.add_class(klass_node)
|
||||
|
||||
for ancestor in self.get_ancestors(klass_node, anc_level):
|
||||
self.extract_classes(ancestor, anc_level-1, ass_level)
|
||||
|
||||
for ass_node in self.get_associated(klass_node, ass_level):
|
||||
self.extract_classes(ass_node, anc_level, ass_level-1)
|
||||
|
||||
|
||||
class DefaultDiadefGenerator(LocalsVisitor, DiaDefGenerator):
|
||||
"""generate minimum diagram definition for the project :
|
||||
|
||||
* a package diagram including project's modules
|
||||
* a class diagram including project's classes
|
||||
"""
|
||||
|
||||
def __init__(self, linker, handler):
|
||||
DiaDefGenerator.__init__(self, linker, handler)
|
||||
LocalsVisitor.__init__(self)
|
||||
|
||||
def visit_project(self, node):
|
||||
"""visit an astroid.Project node
|
||||
|
||||
create a diagram definition for packages
|
||||
"""
|
||||
mode = self.config.mode
|
||||
if len(node.modules) > 1:
|
||||
self.pkgdiagram = PackageDiagram('packages %s' % node.name, mode)
|
||||
else:
|
||||
self.pkgdiagram = None
|
||||
self.classdiagram = ClassDiagram('classes %s' % node.name, mode)
|
||||
|
||||
def leave_project(self, node): # pylint: disable=unused-argument
|
||||
"""leave the astroid.Project node
|
||||
|
||||
return the generated diagram definition
|
||||
"""
|
||||
if self.pkgdiagram:
|
||||
return self.pkgdiagram, self.classdiagram
|
||||
return self.classdiagram,
|
||||
|
||||
def visit_module(self, node):
|
||||
"""visit an astroid.Module node
|
||||
|
||||
add this class to the package diagram definition
|
||||
"""
|
||||
if self.pkgdiagram:
|
||||
self.linker.visit(node)
|
||||
self.pkgdiagram.add_object(node.name, node)
|
||||
|
||||
def visit_class(self, node):
|
||||
"""visit an astroid.Class node
|
||||
|
||||
add this class to the class diagram definition
|
||||
"""
|
||||
anc_level, ass_level = self._get_levels()
|
||||
self.extract_classes(node, anc_level, ass_level)
|
||||
|
||||
def visit_from(self, node):
|
||||
"""visit astroid.From and catch modules for package diagram
|
||||
"""
|
||||
if self.pkgdiagram:
|
||||
self.pkgdiagram.add_from_depend(node, node.modname)
|
||||
|
||||
|
||||
class ClassDiadefGenerator(DiaDefGenerator):
|
||||
"""generate a class diagram definition including all classes related to a
|
||||
given class
|
||||
"""
|
||||
|
||||
def __init__(self, linker, handler):
|
||||
DiaDefGenerator.__init__(self, linker, handler)
|
||||
|
||||
def class_diagram(self, project, klass):
|
||||
"""return a class diagram definition for the given klass and its
|
||||
related klasses
|
||||
"""
|
||||
|
||||
self.classdiagram = ClassDiagram(klass, self.config.mode)
|
||||
if len(project.modules) > 1:
|
||||
module, klass = klass.rsplit('.', 1)
|
||||
module = project.get_module(module)
|
||||
else:
|
||||
module = project.modules[0]
|
||||
klass = klass.split('.')[-1]
|
||||
klass = next(module.ilookup(klass))
|
||||
|
||||
anc_level, ass_level = self._get_levels()
|
||||
self.extract_classes(klass, anc_level, ass_level)
|
||||
return self.classdiagram
|
||||
|
||||
# diagram handler #############################################################
|
||||
|
||||
class DiadefsHandler(object):
|
||||
"""handle diagram definitions :
|
||||
|
||||
get it from user (i.e. xml files) or generate them
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
|
||||
def get_diadefs(self, project, linker):
|
||||
"""get the diagrams configuration data
|
||||
:param linker: astroid.inspector.Linker(IdGeneratorMixIn, LocalsVisitor)
|
||||
:param project: astroid.manager.Project
|
||||
"""
|
||||
|
||||
# read and interpret diagram definitions (Diadefs)
|
||||
diagrams = []
|
||||
generator = ClassDiadefGenerator(linker, self)
|
||||
for klass in self.config.classes:
|
||||
diagrams.append(generator.class_diagram(project, klass))
|
||||
if not diagrams:
|
||||
diagrams = DefaultDiadefGenerator(linker, self).visit(project)
|
||||
for diagram in diagrams:
|
||||
diagram.extract_relationships()
|
||||
return diagrams
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
# Copyright (c) 2004-2013 LOGILAB S.A. (Paris, FRANCE).
|
||||
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation; either version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# 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.
|
||||
"""diagram objects
|
||||
"""
|
||||
|
||||
import astroid
|
||||
from pylint.pyreverse.utils import is_interface, FilterMixIn
|
||||
|
||||
class Figure(object):
|
||||
"""base class for counter handling"""
|
||||
|
||||
class Relationship(Figure):
|
||||
"""a relation ship from an object in the diagram to another
|
||||
"""
|
||||
def __init__(self, from_object, to_object, relation_type, name=None):
|
||||
Figure.__init__(self)
|
||||
self.from_object = from_object
|
||||
self.to_object = to_object
|
||||
self.type = relation_type
|
||||
self.name = name
|
||||
|
||||
|
||||
class DiagramEntity(Figure):
|
||||
"""a diagram object, i.e. a label associated to an astroid node
|
||||
"""
|
||||
def __init__(self, title='No name', node=None):
|
||||
Figure.__init__(self)
|
||||
self.title = title
|
||||
self.node = node
|
||||
|
||||
class ClassDiagram(Figure, FilterMixIn):
|
||||
"""main class diagram handling
|
||||
"""
|
||||
TYPE = 'class'
|
||||
def __init__(self, title, mode):
|
||||
FilterMixIn.__init__(self, mode)
|
||||
Figure.__init__(self)
|
||||
self.title = title
|
||||
self.objects = []
|
||||
self.relationships = {}
|
||||
self._nodes = {}
|
||||
self.depends = []
|
||||
|
||||
def get_relationships(self, role):
|
||||
# sorted to get predictable (hence testable) results
|
||||
return sorted(self.relationships.get(role, ()),
|
||||
key=lambda x: (x.from_object.fig_id, x.to_object.fig_id))
|
||||
|
||||
def add_relationship(self, from_object, to_object,
|
||||
relation_type, name=None):
|
||||
"""create a relation ship
|
||||
"""
|
||||
rel = Relationship(from_object, to_object, relation_type, name)
|
||||
self.relationships.setdefault(relation_type, []).append(rel)
|
||||
|
||||
def get_relationship(self, from_object, relation_type):
|
||||
"""return a relation ship or None
|
||||
"""
|
||||
for rel in self.relationships.get(relation_type, ()):
|
||||
if rel.from_object is from_object:
|
||||
return rel
|
||||
raise KeyError(relation_type)
|
||||
|
||||
def get_attrs(self, node):
|
||||
"""return visible attributes, possibly with class name"""
|
||||
attrs = []
|
||||
for node_name, ass_nodes in list(node.instance_attrs_type.items()) + \
|
||||
list(node.locals_type.items()):
|
||||
if not self.show_attr(node_name):
|
||||
continue
|
||||
names = self.class_names(ass_nodes)
|
||||
if names:
|
||||
node_name = "%s : %s" % (node_name, ", ".join(names))
|
||||
attrs.append(node_name)
|
||||
return sorted(attrs)
|
||||
|
||||
def get_methods(self, node):
|
||||
"""return visible methods"""
|
||||
methods = [
|
||||
m for m in node.values()
|
||||
if isinstance(m, astroid.Function) and self.show_attr(m.name)
|
||||
]
|
||||
return sorted(methods, key=lambda n: n.name)
|
||||
|
||||
def add_object(self, title, node):
|
||||
"""create a diagram object
|
||||
"""
|
||||
assert node not in self._nodes
|
||||
ent = DiagramEntity(title, node)
|
||||
self._nodes[node] = ent
|
||||
self.objects.append(ent)
|
||||
|
||||
def class_names(self, nodes):
|
||||
"""return class names if needed in diagram"""
|
||||
names = []
|
||||
for ass_node in nodes:
|
||||
if isinstance(ass_node, astroid.Instance):
|
||||
ass_node = ass_node._proxied
|
||||
if isinstance(ass_node, astroid.Class) \
|
||||
and hasattr(ass_node, "name") and not self.has_node(ass_node):
|
||||
if ass_node.name not in names:
|
||||
ass_name = ass_node.name
|
||||
names.append(ass_name)
|
||||
return names
|
||||
|
||||
def nodes(self):
|
||||
"""return the list of underlying nodes
|
||||
"""
|
||||
return self._nodes.keys()
|
||||
|
||||
def has_node(self, node):
|
||||
"""return true if the given node is included in the diagram
|
||||
"""
|
||||
return node in self._nodes
|
||||
|
||||
def object_from_node(self, node):
|
||||
"""return the diagram object mapped to node
|
||||
"""
|
||||
return self._nodes[node]
|
||||
|
||||
def classes(self):
|
||||
"""return all class nodes in the diagram"""
|
||||
return [o for o in self.objects if isinstance(o.node, astroid.Class)]
|
||||
|
||||
def classe(self, name):
|
||||
"""return a class by its name, raise KeyError if not found
|
||||
"""
|
||||
for klass in self.classes():
|
||||
if klass.node.name == name:
|
||||
return klass
|
||||
raise KeyError(name)
|
||||
|
||||
def extract_relationships(self):
|
||||
"""extract relation ships between nodes in the diagram
|
||||
"""
|
||||
for obj in self.classes():
|
||||
node = obj.node
|
||||
obj.attrs = self.get_attrs(node)
|
||||
obj.methods = self.get_methods(node)
|
||||
# shape
|
||||
if is_interface(node):
|
||||
obj.shape = 'interface'
|
||||
else:
|
||||
obj.shape = 'class'
|
||||
# inheritance link
|
||||
for par_node in node.ancestors(recurs=False):
|
||||
try:
|
||||
par_obj = self.object_from_node(par_node)
|
||||
self.add_relationship(obj, par_obj, 'specialization')
|
||||
except KeyError:
|
||||
continue
|
||||
# implements link
|
||||
for impl_node in node.implements:
|
||||
try:
|
||||
impl_obj = self.object_from_node(impl_node)
|
||||
self.add_relationship(obj, impl_obj, 'implements')
|
||||
except KeyError:
|
||||
continue
|
||||
# associations link
|
||||
for name, values in list(node.instance_attrs_type.items()) + \
|
||||
list(node.locals_type.items()):
|
||||
for value in values:
|
||||
if value is astroid.YES:
|
||||
continue
|
||||
if isinstance(value, astroid.Instance):
|
||||
value = value._proxied
|
||||
try:
|
||||
ass_obj = self.object_from_node(value)
|
||||
self.add_relationship(ass_obj, obj, 'association', name)
|
||||
except KeyError:
|
||||
continue
|
||||
|
||||
|
||||
class PackageDiagram(ClassDiagram):
|
||||
"""package diagram handling
|
||||
"""
|
||||
TYPE = 'package'
|
||||
|
||||
def modules(self):
|
||||
"""return all module nodes in the diagram"""
|
||||
return [o for o in self.objects if isinstance(o.node, astroid.Module)]
|
||||
|
||||
def module(self, name):
|
||||
"""return a module by its name, raise KeyError if not found
|
||||
"""
|
||||
for mod in self.modules():
|
||||
if mod.node.name == name:
|
||||
return mod
|
||||
raise KeyError(name)
|
||||
|
||||
def get_module(self, name, node):
|
||||
"""return a module by its name, looking also for relative imports;
|
||||
raise KeyError if not found
|
||||
"""
|
||||
for mod in self.modules():
|
||||
mod_name = mod.node.name
|
||||
if mod_name == name:
|
||||
return mod
|
||||
#search for fullname of relative import modules
|
||||
package = node.root().name
|
||||
if mod_name == "%s.%s" % (package, name):
|
||||
return mod
|
||||
if mod_name == "%s.%s" % (package.rsplit('.', 1)[0], name):
|
||||
return mod
|
||||
raise KeyError(name)
|
||||
|
||||
def add_from_depend(self, node, from_module):
|
||||
"""add dependencies created by from-imports
|
||||
"""
|
||||
mod_name = node.root().name
|
||||
obj = self.module(mod_name)
|
||||
if from_module not in obj.node.depends:
|
||||
obj.node.depends.append(from_module)
|
||||
|
||||
def extract_relationships(self):
|
||||
"""extract relation ships between nodes in the diagram
|
||||
"""
|
||||
ClassDiagram.extract_relationships(self)
|
||||
for obj in self.classes():
|
||||
# ownership
|
||||
try:
|
||||
mod = self.object_from_node(obj.node.root())
|
||||
self.add_relationship(obj, mod, 'ownership')
|
||||
except KeyError:
|
||||
continue
|
||||
for obj in self.modules():
|
||||
obj.shape = 'package'
|
||||
# dependencies
|
||||
for dep_name in obj.node.depends:
|
||||
try:
|
||||
dep = self.get_module(dep_name, obj.node)
|
||||
except KeyError:
|
||||
continue
|
||||
self.add_relationship(obj, dep, 'depends')
|
||||
124
plugins/bundle/python-mode/pymode/libs/pylint/pyreverse/main.py
Normal file
124
plugins/bundle/python-mode/pymode/libs/pylint/pyreverse/main.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
# # Copyright (c) 2000-2013 LOGILAB S.A. (Paris, FRANCE).
|
||||
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation; either version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# 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.
|
||||
"""
|
||||
%prog [options] <packages>
|
||||
|
||||
create UML diagrams for classes and modules in <packages>
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
import sys, os
|
||||
from logilab.common.configuration import ConfigurationMixIn
|
||||
from astroid.manager import AstroidManager
|
||||
from astroid.inspector import Linker
|
||||
|
||||
from pylint.pyreverse.diadefslib import DiadefsHandler
|
||||
from pylint.pyreverse import writer
|
||||
from pylint.pyreverse.utils import insert_default_options
|
||||
|
||||
OPTIONS = (
|
||||
("filter-mode",
|
||||
dict(short='f', default='PUB_ONLY', dest='mode', type='string',
|
||||
action='store', metavar='<mode>',
|
||||
help="""filter attributes and functions according to
|
||||
<mode>. Correct modes are :
|
||||
'PUB_ONLY' filter all non public attributes
|
||||
[DEFAULT], equivalent to PRIVATE+SPECIAL_A
|
||||
'ALL' no filter
|
||||
'SPECIAL' filter Python special functions
|
||||
except constructor
|
||||
'OTHER' filter protected and private
|
||||
attributes""")),
|
||||
|
||||
("class",
|
||||
dict(short='c', action="append", metavar="<class>", dest="classes", default=[],
|
||||
help="create a class diagram with all classes related to <class>;\
|
||||
this uses by default the options -ASmy")),
|
||||
|
||||
("show-ancestors",
|
||||
dict(short="a", action="store", metavar='<ancestor>', type='int',
|
||||
help='show <ancestor> generations of ancestor classes not in <projects>')),
|
||||
("all-ancestors",
|
||||
dict(short="A", default=None,
|
||||
help="show all ancestors off all classes in <projects>")),
|
||||
("show-associated",
|
||||
dict(short='s', action="store", metavar='<ass_level>', type='int',
|
||||
help='show <ass_level> levels of associated classes not in <projects>')),
|
||||
("all-associated",
|
||||
dict(short='S', default=None,
|
||||
help='show recursively all associated off all associated classes')),
|
||||
("show-builtin",
|
||||
dict(short="b", action="store_true", default=False,
|
||||
help='include builtin objects in representation of classes')),
|
||||
|
||||
("module-names",
|
||||
dict(short="m", default=None, type='yn', metavar='[yn]',
|
||||
help='include module name in representation of classes')),
|
||||
# TODO : generate dependencies like in pylint
|
||||
# ("package-dependencies",
|
||||
# dict(short="M", action="store", metavar='<package_depth>', type='int',
|
||||
# help='show <package_depth> module dependencies beyond modules in \
|
||||
# <projects> (for the package diagram)')),
|
||||
("only-classnames",
|
||||
dict(short='k', action="store_true", default=False,
|
||||
help="don't show attributes and methods in the class boxes; \
|
||||
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.")),
|
||||
)
|
||||
# FIXME : quiet mode
|
||||
#( ('quiet',
|
||||
#dict(help='run quietly', action='store_true', short='q')), )
|
||||
|
||||
class Run(ConfigurationMixIn):
|
||||
"""base class providing common behaviour for pyreverse commands"""
|
||||
|
||||
options = OPTIONS
|
||||
|
||||
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))
|
||||
|
||||
def run(self, args):
|
||||
"""checking arguments and run project"""
|
||||
if not args:
|
||||
print(self.help())
|
||||
return 1
|
||||
# insert current working directory to the python path to recognize
|
||||
# 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)
|
||||
linker = Linker(project, tag=True)
|
||||
handler = DiadefsHandler(self.config)
|
||||
diadefs = handler.get_diadefs(project, linker)
|
||||
finally:
|
||||
sys.path.pop(0)
|
||||
|
||||
if self.config.output_format == "vcg":
|
||||
writer.VCGWriter(self.config).write(diadefs)
|
||||
else:
|
||||
writer.DotWriter(self.config).write(diadefs)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
Run(sys.argv[1:])
|
||||
132
plugins/bundle/python-mode/pymode/libs/pylint/pyreverse/utils.py
Normal file
132
plugins/bundle/python-mode/pymode/libs/pylint/pyreverse/utils.py
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
# Copyright (c) 2002-2013 LOGILAB S.A. (Paris, FRANCE).
|
||||
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation; either version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# 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.
|
||||
"""
|
||||
generic classes/functions for pyreverse core/extensions
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
|
||||
########### pyreverse option utils ##############################
|
||||
|
||||
|
||||
RCFILE = '.pyreverserc'
|
||||
|
||||
def get_default_options():
|
||||
"""
|
||||
Read config file and return list of options
|
||||
"""
|
||||
options = []
|
||||
home = os.environ.get('HOME', '')
|
||||
if home:
|
||||
rcfile = os.path.join(home, RCFILE)
|
||||
try:
|
||||
options = open(rcfile).read().split()
|
||||
except IOError:
|
||||
pass # ignore if no config file found
|
||||
return options
|
||||
|
||||
def insert_default_options():
|
||||
"""insert default options to sys.argv
|
||||
"""
|
||||
options = get_default_options()
|
||||
options.reverse()
|
||||
for arg in options:
|
||||
sys.argv.insert(1, arg)
|
||||
|
||||
|
||||
|
||||
# astroid utilities ###########################################################
|
||||
|
||||
SPECIAL = re.compile('^__[A-Za-z0-9]+[A-Za-z0-9_]*__$')
|
||||
PRIVATE = re.compile('^__[_A-Za-z0-9]*[A-Za-z0-9]+_?$')
|
||||
PROTECTED = re.compile('^_[_A-Za-z0-9]*$')
|
||||
|
||||
def get_visibility(name):
|
||||
"""return the visibility from a name: public, protected, private or special
|
||||
"""
|
||||
if SPECIAL.match(name):
|
||||
visibility = 'special'
|
||||
elif PRIVATE.match(name):
|
||||
visibility = 'private'
|
||||
elif PROTECTED.match(name):
|
||||
visibility = 'protected'
|
||||
|
||||
else:
|
||||
visibility = 'public'
|
||||
return visibility
|
||||
|
||||
ABSTRACT = re.compile('^.*Abstract.*')
|
||||
FINAL = re.compile('^[A-Z_]*$')
|
||||
|
||||
def is_abstract(node):
|
||||
"""return true if the given class node correspond to an abstract class
|
||||
definition
|
||||
"""
|
||||
return ABSTRACT.match(node.name)
|
||||
|
||||
def is_final(node):
|
||||
"""return true if the given class/function node correspond to final
|
||||
definition
|
||||
"""
|
||||
return FINAL.match(node.name)
|
||||
|
||||
def is_interface(node):
|
||||
# bw compat
|
||||
return node.type == 'interface'
|
||||
|
||||
def is_exception(node):
|
||||
# bw compat
|
||||
return node.type == 'exception'
|
||||
|
||||
|
||||
# Helpers #####################################################################
|
||||
|
||||
_CONSTRUCTOR = 1
|
||||
_SPECIAL = 2
|
||||
_PROTECTED = 4
|
||||
_PRIVATE = 8
|
||||
MODES = {
|
||||
'ALL' : 0,
|
||||
'PUB_ONLY' : _SPECIAL + _PROTECTED + _PRIVATE,
|
||||
'SPECIAL' : _SPECIAL,
|
||||
'OTHER' : _PROTECTED + _PRIVATE,
|
||||
}
|
||||
VIS_MOD = {'special': _SPECIAL, 'protected': _PROTECTED,
|
||||
'private': _PRIVATE, 'public': 0}
|
||||
|
||||
class FilterMixIn(object):
|
||||
"""filter nodes according to a mode and nodes' visibility
|
||||
"""
|
||||
def __init__(self, mode):
|
||||
"init filter modes"
|
||||
__mode = 0
|
||||
for nummod in mode.split('+'):
|
||||
try:
|
||||
__mode += MODES[nummod]
|
||||
except KeyError as ex:
|
||||
print('Unknown filter mode %s' % ex, file=sys.stderr)
|
||||
self.__mode = __mode
|
||||
|
||||
|
||||
def show_attr(self, node):
|
||||
"""return true if the node should be treated
|
||||
"""
|
||||
visibility = get_visibility(getattr(node, 'name', node))
|
||||
return not self.__mode & VIS_MOD[visibility]
|
||||
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2008-2013 LOGILAB S.A. (Paris, FRANCE).
|
||||
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation; either version 2 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# 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.
|
||||
"""Utilities for creating VCG and Dot diagrams"""
|
||||
|
||||
from logilab.common.vcgutils import VCGPrinter
|
||||
from logilab.common.graph import DotBackend
|
||||
|
||||
from pylint.pyreverse.utils import is_exception
|
||||
|
||||
class DiagramWriter(object):
|
||||
"""base class for writing project diagrams
|
||||
"""
|
||||
def __init__(self, config, styles):
|
||||
self.config = config
|
||||
self.pkg_edges, self.inh_edges, self.imp_edges, self.ass_edges = styles
|
||||
self.printer = None # defined in set_printer
|
||||
|
||||
def write(self, diadefs):
|
||||
"""write files for <project> according to <diadefs>
|
||||
"""
|
||||
for diagram in diadefs:
|
||||
basename = diagram.title.strip().replace(' ', '_')
|
||||
file_name = '%s.%s' % (basename, self.config.output_format)
|
||||
self.set_printer(file_name, basename)
|
||||
if diagram.TYPE == 'class':
|
||||
self.write_classes(diagram)
|
||||
else:
|
||||
self.write_packages(diagram)
|
||||
self.close_graph()
|
||||
|
||||
def write_packages(self, diagram):
|
||||
"""write a package diagram"""
|
||||
# sorted to get predictable (hence testable) results
|
||||
for i, obj in enumerate(sorted(diagram.modules(), key=lambda x: x.title)):
|
||||
self.printer.emit_node(i, label=self.get_title(obj), shape='box')
|
||||
obj.fig_id = i
|
||||
# package dependencies
|
||||
for rel in diagram.get_relationships('depends'):
|
||||
self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id,
|
||||
**self.pkg_edges)
|
||||
|
||||
def write_classes(self, diagram):
|
||||
"""write a class diagram"""
|
||||
# sorted to get predictable (hence testable) results
|
||||
for i, obj in enumerate(sorted(diagram.objects, key=lambda x: x.title)):
|
||||
self.printer.emit_node(i, **self.get_values(obj))
|
||||
obj.fig_id = i
|
||||
# inheritance links
|
||||
for rel in diagram.get_relationships('specialization'):
|
||||
self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id,
|
||||
**self.inh_edges)
|
||||
# implementation links
|
||||
for rel in diagram.get_relationships('implements'):
|
||||
self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id,
|
||||
**self.imp_edges)
|
||||
# generate associations
|
||||
for rel in diagram.get_relationships('association'):
|
||||
self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id,
|
||||
label=rel.name, **self.ass_edges)
|
||||
|
||||
def set_printer(self, file_name, basename):
|
||||
"""set printer"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_title(self, obj):
|
||||
"""get project title"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_values(self, obj):
|
||||
"""get label and shape for classes."""
|
||||
raise NotImplementedError
|
||||
|
||||
def close_graph(self):
|
||||
"""finalize the graph"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class DotWriter(DiagramWriter):
|
||||
"""write dot graphs from a diagram definition and a project
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
styles = [dict(arrowtail='none', arrowhead="open"),
|
||||
dict(arrowtail='none', arrowhead='empty'),
|
||||
dict(arrowtail='node', arrowhead='empty', style='dashed'),
|
||||
dict(fontcolor='green', arrowtail='none',
|
||||
arrowhead='diamond', style='solid'),
|
||||
]
|
||||
DiagramWriter.__init__(self, config, styles)
|
||||
|
||||
def set_printer(self, file_name, basename):
|
||||
"""initialize DotWriter and add options for layout.
|
||||
"""
|
||||
layout = dict(rankdir="BT")
|
||||
self.printer = DotBackend(basename, additionnal_param=layout)
|
||||
self.file_name = file_name
|
||||
|
||||
def get_title(self, obj):
|
||||
"""get project title"""
|
||||
return obj.title
|
||||
|
||||
def get_values(self, obj):
|
||||
"""get label and shape for classes.
|
||||
|
||||
The label contains all attributes and methods
|
||||
"""
|
||||
label = obj.title
|
||||
if obj.shape == 'interface':
|
||||
label = u'«interface»\\n%s' % label
|
||||
if not self.config.only_classnames:
|
||||
label = r'%s|%s\l|' % (label, r'\l'.join(obj.attrs))
|
||||
for func in obj.methods:
|
||||
label = r'%s%s()\l' % (label, func.name)
|
||||
label = '{%s}' % label
|
||||
if is_exception(obj.node):
|
||||
return dict(fontcolor='red', label=label, shape='record')
|
||||
return dict(label=label, shape='record')
|
||||
|
||||
def close_graph(self):
|
||||
"""print the dot graph into <file_name>"""
|
||||
self.printer.generate(self.file_name)
|
||||
|
||||
|
||||
class VCGWriter(DiagramWriter):
|
||||
"""write vcg graphs from a diagram definition and a project
|
||||
"""
|
||||
def __init__(self, config):
|
||||
styles = [dict(arrowstyle='solid', backarrowstyle='none',
|
||||
backarrowsize=0),
|
||||
dict(arrowstyle='solid', backarrowstyle='none',
|
||||
backarrowsize=10),
|
||||
dict(arrowstyle='solid', backarrowstyle='none',
|
||||
linestyle='dotted', backarrowsize=10),
|
||||
dict(arrowstyle='solid', backarrowstyle='none',
|
||||
textcolor='green'),
|
||||
]
|
||||
DiagramWriter.__init__(self, config, styles)
|
||||
|
||||
def set_printer(self, file_name, basename):
|
||||
"""initialize VCGWriter for a UML graph"""
|
||||
self.graph_file = open(file_name, 'w+')
|
||||
self.printer = VCGPrinter(self.graph_file)
|
||||
self.printer.open_graph(title=basename, layoutalgorithm='dfs',
|
||||
late_edge_labels='yes', port_sharing='no',
|
||||
manhattan_edges='yes')
|
||||
self.printer.emit_node = self.printer.node
|
||||
self.printer.emit_edge = self.printer.edge
|
||||
|
||||
def get_title(self, obj):
|
||||
"""get project title in vcg format"""
|
||||
return r'\fb%s\fn' % obj.title
|
||||
|
||||
def get_values(self, obj):
|
||||
"""get label and shape for classes.
|
||||
|
||||
The label contains all attributes and methods
|
||||
"""
|
||||
if is_exception(obj.node):
|
||||
label = r'\fb\f09%s\fn' % obj.title
|
||||
else:
|
||||
label = r'\fb%s\fn' % obj.title
|
||||
if obj.shape == 'interface':
|
||||
shape = 'ellipse'
|
||||
else:
|
||||
shape = 'box'
|
||||
if not self.config.only_classnames:
|
||||
attrs = obj.attrs
|
||||
methods = [func.name for func in obj.methods]
|
||||
# box width for UML like diagram
|
||||
maxlen = max(len(name) for name in [obj.title] + methods + attrs)
|
||||
line = '_' * (maxlen + 2)
|
||||
label = r'%s\n\f%s' % (label, line)
|
||||
for attr in attrs:
|
||||
label = r'%s\n\f08%s' % (label, attr)
|
||||
if attrs:
|
||||
label = r'%s\n\f%s' % (label, line)
|
||||
for func in methods:
|
||||
label = r'%s\n\f10%s()' % (label, func)
|
||||
return dict(label=label, shape=shape)
|
||||
|
||||
def close_graph(self):
|
||||
"""close graph and file"""
|
||||
self.printer.close_graph()
|
||||
self.graph_file.close()
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue