summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.hgignore4
-rw-r--r--ChangeLog124
-rw-r--r--DEPENDS1
-rw-r--r--MANIFEST.in8
-rw-r--r--README52
-rw-r--r--__init__.py207
-rw-r--r--__pkginfo__.py61
-rw-r--r--_exceptions.py54
-rw-r--r--announce.txt23
-rw-r--r--builder.py488
-rw-r--r--debian/changelog51
-rw-r--r--debian/control28
-rw-r--r--debian/copyright28
-rw-r--r--debian/python-logilab-astng.dirs6
-rw-r--r--debian/python-logilab-astng.postinst26
-rw-r--r--debian/python-logilab-astng.prerm14
-rwxr-xr-xdebian/rules71
-rw-r--r--debian/watch3
-rw-r--r--inference.py388
-rw-r--r--inspector.py267
-rw-r--r--lookup.py215
-rw-r--r--manager.py345
-rw-r--r--nodes.py768
-rw-r--r--raw_building.py214
-rw-r--r--scoped_nodes.py644
-rw-r--r--setup.py186
-rw-r--r--test/data/SSL1/Connection1.py14
-rw-r--r--test/data/SSL1/__init__.py1
-rw-r--r--test/data/__init__.py1
-rw-r--r--test/data/all.py10
-rw-r--r--test/data/appl/__init__.py4
-rw-r--r--test/data/appl/myConnection.py11
-rw-r--r--test/data/module.py89
-rw-r--r--test/data/module2.py90
-rw-r--r--test/data/noendingnewline.py38
-rw-r--r--test/data/nonregr.py57
-rw-r--r--test/data/notall.py9
-rw-r--r--test/data2/__init__.py0
-rw-r--r--test/data2/clientmodule_test.py32
-rw-r--r--test/data2/suppliermodule_test.py13
-rwxr-xr-xtest/fulltest.sh17
-rw-r--r--test/regrtest.py41
-rw-r--r--test/regrtest_data/descriptor_crash.py12
-rw-r--r--test/regrtest_data/import_package_subpackage_module.py49
-rw-r--r--test/regrtest_data/package/__init__.py5
-rw-r--r--test/regrtest_data/package/subpackage/__init__.py1
-rw-r--r--test/regrtest_data/package/subpackage/module.py1
-rw-r--r--test/runtests.py5
-rw-r--r--test/unittest_builder.py314
-rw-r--r--test/unittest_inference.py557
-rw-r--r--test/unittest_inspector.py88
-rw-r--r--test/unittest_lookup.py122
-rw-r--r--test/unittest_manager.py70
-rw-r--r--test/unittest_nodes.py77
-rw-r--r--test/unittest_scoped_nodes.py272
-rw-r--r--utils.py175
56 files changed, 6451 insertions, 0 deletions
diff --git a/.hgignore b/.hgignore
new file mode 100644
index 00000000..2849616a
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,4 @@
+(^|/)\.svn($|/)
+(^|/)\.hg($|/)
+(^|/)\.hgtags($|/)
+^log$
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 00000000..d5e3ccb7
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,124 @@
+Change log for the astng package
+================================
+
+2006-04-19 -- 0.16.0
+ * fix living object building to consider classes such as property as
+ a class instead of a data descriptor
+
+ * fix multiple assignment inference which was discarding some solutions
+
+ * added some line manipulation methods to handle pylint's block messages
+ control feature (Node.last_source_line(), None.block_range(lineno)
+
+
+2006-03-10 -- 0.15.1
+ * fix avoiding to load everything from living objects... Thanks Amaury!
+
+ * fix a possible NameError in Instance.infer_call_result
+
+
+2006-03-06 -- 0.15.0
+ * fix possible infinite recursion on global statements (close #10342)
+ and in various other cases...
+
+ * fix locals/globals interactions when the global statement is used
+ (close #10434)
+
+ * multiple inference related bug fixes
+
+ * associate List, Tuple and Dict and Const nodes to their respective
+ classes
+
+ * new .ass_type method on assignment related node, returning the
+ assigment type node (Assign, For, ListCompFor, GenExprFor,
+ TryExcept)
+
+ * more API refactoring... .resolve method has disappeared, now you
+ have .ilookup on every nodes and .getattr/.igetattr on node
+ supporting the attribute protocol
+
+ * introduced a YES object that may be returned when there is ambiguity
+ on an inference path (typically function call when we don't know
+ arguments value)
+
+ * builder try to instantiate builtin exceptions subclasses to get their
+ instance attribute
+
+
+2006-01-10 -- 0.14.0
+ * some major inference improvments and refactoring ! The drawback is
+ the introduction of some non backward compatible change in the API
+ but it's imho much cleaner and powerful now :)
+
+ * new boolean property .newstyle on Class nodes (implements #10073)
+
+ * new .import_module method on Module node to help in .resolve
+ refactoring
+
+ * .instance_attrs has list of assignments to instance attribute
+ dictionary as value instead of one
+
+ * added missing GenExprIf and GenExprInner nodes, and implements
+ as_string for each generator expression related nodes
+
+ * specifically catch KeyboardInterrupt to reraise it in some places
+
+ * fix so that module names are always absolute
+
+ * fix .resolve on package where a subpackage is imported in the
+ __init__ file
+
+ * fix a bug regarding construction of Function node from living object
+ with realier version of python 2.4
+
+ * fix a NameError on Import and From self_resolve method
+
+ * fix a bug occuring when building an astng from a living object with
+ a property
+
+ * lint fixes
+
+
+2005-11-07 -- 0.13.1
+ * fix bug on building from living module the same object in
+ encountered more than once time (eg builtins.object) (close #10069)
+
+ * fix bug in Class.ancestors() regarding inner classes (close #10072)
+
+ * fix .self_resolve() on From and Module nodes to handle package
+ precedence over module (close #10066)
+
+ * locals dict for package contains __path__ definition (close #10065)
+
+ * astng provide GenExpr and GenExprFor nodes with python >= 2.4
+ (close #10063)
+
+ * fix python2.2 compatibility (close #9922)
+
+ * link .__contains__ to .has_key on scoped node to speed up execution
+
+ * remove no more necessary .module_object() method on From and Module
+ nodes
+
+ * normalize parser.ParserError to SyntaxError with python 2.2
+
+
+2005-10-21 -- 0.13.0
+ * .locals and .globals on scoped node handle now a list of references
+ to each assigment statements instead of a single reference to the
+ first assigment statement.
+
+ * fix bug with manager.astng_from_module_name when a context file is
+ given (notably fix ZODB 3.4 crash with pylint/pyreverse)
+
+ * fix Compare.as_string method
+
+ * fix bug with lambda object missing the "type" attribute
+
+ * some minor refactoring
+
+ * This package has been extracted from the logilab-common package, which
+ will be kept for some time for backward compatibility but will no
+ longer be maintained (this explains that this package is starting with
+ the 0.13 version number, since the fork occurs with the version
+ released in logilab-common 0.12).
diff --git a/DEPENDS b/DEPENDS
new file mode 100644
index 00000000..a2522761
--- /dev/null
+++ b/DEPENDS
@@ -0,0 +1 @@
+python-logilab-common
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 00000000..380fbf8f
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,8 @@
+include ChangeLog
+include README
+include COPYING
+include DEPENDS
+include test/fulltest.sh
+recursive-include test/data *.py
+recursive-include test/data2 *.py
+recursive-include test/regrtest_data *.py
diff --git a/README b/README
new file mode 100644
index 00000000..4755e73e
--- /dev/null
+++ b/README
@@ -0,0 +1,52 @@
+ASTNG
+=====
+
+What's this ?
+-------------
+
+The aim of this module is to provide a common base representation of
+python source code for projects such as pychecker, pyreverse,
+pylint... Well, actually the development of this library is essentialy
+governed by pylint's needs.
+
+It extends class defined in the compiler.ast [1] module with some
+additional methods and attributes. Instance attributes are added by a
+builder object, which can either generate extended ast (let's call
+them astng ;) by visiting an existant ast tree or by inspecting living
+object. Methods are added by monkey patching ast classes.
+
+Main modules are:
+
+* nodes and scoped_nodes for more information about methods and
+ attributes added to different node classes
+
+* the manager contains a high level object to get astng trees from
+ source files and living objects. It maintains a cache of previously
+ constructed tree for quick access
+
+* builder contains the class responsible to build astng trees
+
+
+Notice
+------
+This package has been extracted from the logilab-common package, which
+will be kept for some time for backward compatibility but will no
+longer be maintained.
+
+
+Installation
+------------
+
+Extract the tarball, jump into the created directory and run ::
+
+ python setup.py install
+
+For installation options, see ::
+
+ python setup.py install --help
+
+
+If you have any questions, please mail devel@logilab.fr for support.
+
+Sylvain Thénault
+Oct 21, 2005
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 00000000..a06671eb
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1,207 @@
+# 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""Python Abstract Syntax Tree New Generation
+
+The aim of this module is to provide a common base representation of
+python source code for projects such as pychecker, pyreverse,
+pylint... Well, actually the development of this library is essentialy
+governed by pylint's needs.
+
+It extends class defined in the compiler.ast [1] module with some
+additional methods and attributes. Instance attributes are added by a
+builder object, which can either generate extended ast (let's call
+them astng ;) by visiting an existant ast tree or by inspecting living
+object. Methods are added by monkey patching ast classes.
+
+Main modules are:
+
+* nodes and scoped_nodes for more information about methods and
+ attributes added to different node classes
+
+* the manager contains a high level object to get astng trees from
+ source files and living objects. It maintains a cache of previously
+ constructed tree for quick access
+
+* builder contains the class responsible to build astng trees
+
+
+:version: $Revision: 1.24 $
+:author: Sylvain Thenault
+:copyright: 2003-2006 LOGILAB S.A. (Paris, FRANCE)
+:contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org
+:copyright: 2003-2006 Sylvain Thenault
+:contact: mailto:thenault@gmail.com
+"""
+
+from __future__ import generators
+
+__revision__ = "$Id: __init__.py,v 1.24 2006-04-20 07:37:28 syt Exp $"
+__doctype__ = "restructuredtext en"
+
+from logilab.common.compat import chain, imap
+
+# WARNING: internal imports order matters !
+
+from logilab.astng._exceptions import *
+
+
+def unpack_infer(stmt, path=None):
+ """return an iterator on nodes infered by the given statement
+ if the infered value is a list or a tuple, recurse on it to
+ get values infered by its content
+ """
+ if isinstance(stmt, (List, Tuple)):
+ return chain(*imap(unpack_infer, stmt.nodes))
+ infered = stmt.infer(path=path).next()
+ if infered is stmt:
+ return iter( (stmt,) )
+ return chain(*imap(unpack_infer, stmt.infer(path=path)))
+
+def _infer_stmts(stmts, name=None, path=None, frame=None):
+ """return an iterator on statements infered by each statement in <stmts>
+ """
+ stmt = None
+ one_infered = False
+ for stmt in stmts:
+ if stmt is YES:
+ yield stmt
+ one_infered = True
+ continue
+ try:
+ for infered in stmt.infer(stmt._infer_name(frame, name), path):
+ yield infered
+ one_infered = True
+ except UnresolvableName:
+ continue
+ except InferenceError:
+ yield YES
+ one_infered = True
+ if not one_infered:
+ raise InferenceError(str(stmt))
+
+# special inference objects ###################################################
+
+class Yes(object):
+ """a yes object"""
+ def __str__(self):
+ return 'YES'
+ def __getattribute__(self, name):
+ return self
+ def __call__(self, *args, **kwargs):
+ return self
+YES = Yes()
+
+class Proxy:
+ """a simple proxy object"""
+ def __init__(self, proxied):
+ self._proxied = proxied
+
+ def __getattr__(self, name):
+ return getattr(self._proxied, name)
+
+ def infer(self, name=None, path=None):
+ yield self
+
+class Instance(Proxy):
+ """a special node representing a class instance"""
+ def getattr(self, name, path=None, lookupclass=True):
+ try:
+ return self._proxied.instance_attr(name, path)
+ except NotFoundError:
+ if name == '__class__':
+ return [self._proxied]
+ if name == '__name__':
+ # access to __name__ gives undefined member on class
+ # instances but not on class objects
+ raise NotFoundError(name)
+ if lookupclass:
+ return self._proxied.getattr(name, path)
+ raise NotFoundError(name)
+
+ def igetattr(self, name, path=None):
+ """infered getattr"""
+ try:
+ # XXX frame should be self._proxied, or not ?
+ return _infer_stmts(self.getattr(name, path, lookupclass=False), name,
+ frame=self, path=path)
+ except NotFoundError:
+ try:
+ # fallback to class'igetattr since it has some logic to handle
+ # descriptors
+ return self._proxied.igetattr(name, path=path)
+ except NotFoundError:
+ raise InferenceError(name)
+
+ def infer_call_result(self, caller, inf_path=None):
+ """infer what's a class instance is returning when called"""
+ one_infered = False
+ for node in self._proxied.igetattr('__call__', inf_path):
+ for res in node.infer_call_result(caller, inf_path):
+ one_infered = True
+ yield res
+ if not one_infered:
+ raise InferenceError()
+
+ def __repr__(self):
+ return 'Instance of %s' % self._proxied.name
+
+ def callable(self):
+ try:
+ self._proxied.getattr('__call__')
+ return True
+ except NotFoundError:
+ return False
+
+class Generator(Proxy):
+ """a special node representing a generator"""
+ def callable(self):
+ return True
+
+# imports #####################################################################
+
+from logilab.astng.manager import ASTNGManager, Project, Package
+MANAGER = ASTNGManager()
+
+from logilab.astng.nodes import *
+from logilab.astng import nodes
+from logilab.astng.scoped_nodes import *
+from logilab.astng import inference
+from logilab.astng import lookup
+lookup._decorate(nodes)
+
+List._proxied = MANAGER.astng_from_class(list)
+List.__bases__ += (inference.Instance,)
+Tuple._proxied = MANAGER.astng_from_class(tuple)
+Tuple.__bases__ += (inference.Instance,)
+Dict._proxied = MANAGER.astng_from_class(dict)
+Dict.__bases__ += (inference.Instance,)
+Dict._proxied = MANAGER.astng_from_class(dict)
+
+builtin_astng = Dict._proxied.root()
+
+Const.__bases__ += (inference.Instance,)
+Const._proxied = None
+def Const___getattr__(self, name):
+ if self.value is None:
+ raise AttributeError(name)
+ if self._proxied is None:
+ self._proxied = MANAGER.astng_from_class(self.value.__class__)
+ return getattr(self._proxied, name)
+Const.__getattr__ = Const___getattr__
+def Const_getattr(self, name, path=None, lookupclass=None):
+ if self.value is None:
+ raise NotFoundError(name)
+ if self._proxied is None:
+ self._proxied = MANAGER.astng_from_class(self.value.__class__)
+ return self._proxied.getattr(name, path)
+Const.getattr = Const_getattr
diff --git a/__pkginfo__.py b/__pkginfo__.py
new file mode 100644
index 00000000..a79af825
--- /dev/null
+++ b/__pkginfo__.py
@@ -0,0 +1,61 @@
+# pylint: disable-msg=W0622
+#
+# Copyright (c) 2003-2006 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""
+logilab.astng packaging information
+"""
+
+__revision__ = "$Id: __pkginfo__.py,v 1.12 2006-04-19 14:31:37 syt Exp $"
+
+modname = 'astng'
+numversion = (0, 16, 0)
+version = '.'.join([str(num) for num in numversion])
+pyversions = ["2.2", "2.3", "2.4"]
+
+license = 'GPL'
+copyright = '''Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE).
+http://www.logilab.fr/ -- mailto:contact@logilab.fr'''
+
+author = 'Sylvain Thenault'
+author_email = 'sylvain.thenault@logilab.fr'
+
+short_desc = "extend python's abstract syntax tree"
+
+long_desc = """The aim of this module is to provide a common base \
+representation of
+python source code for projects such as pychecker, pyreverse,
+pylint... Well, actually the development of this library is essentialy
+governed by pylint's needs.
+
+It extends class defined in the compiler.ast [1] module with some
+additional methods and attributes. Instance attributes are added by a
+builder object, which can either generate extended ast (let's call
+them astng ;) by visiting an existant ast tree or by inspecting living
+object. Methods are added by monkey patching ast classes."""
+
+
+web = "http://www.logilab.org/projects/%s" % modname
+ftp = "ftp://ftp.logilab.org/pub/%s" % modname
+mailinglist = "mailto://python-projects@lists.logilab.org"
+
+subpackage_of = 'logilab'
+
+from os.path import join
+include_dirs = [join('test', 'regrtest_data'),
+ join('test', 'data'), join('test', 'data2')]
+
+debian_uploader = 'Alexandre Fayolle <afayolle@debian.org>'
diff --git a/_exceptions.py b/_exceptions.py
new file mode 100644
index 00000000..8e2ee582
--- /dev/null
+++ b/_exceptions.py
@@ -0,0 +1,54 @@
+# 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""this module contains exceptions used in the astng library
+
+:version: $Revision: 1.4 $
+:author: Sylvain Thenault
+:copyright: 2003-2006 LOGILAB S.A. (Paris, FRANCE)
+:contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org
+:copyright: 2003-2006 Sylvain Thenault
+:contact: mailto:thenault@gmail.com
+"""
+
+__revision__ = "$Id: _exceptions.py,v 1.4 2006-03-06 08:57:52 syt Exp $"
+__doctype__ = "restructuredtext en"
+
+class ASTNGError(Exception):
+ """base exception class for all astng related exceptions
+ """
+
+class ASTNGBuildingException(ASTNGError):
+ """exception class when we are not able to build an astng representation"""
+
+class ResolveError(ASTNGError):
+ """base class of astng resolution/inference error"""
+
+class NotFoundError(ResolveError):
+ """raised when we are unabled to resolve a name"""
+
+class InferenceError(ResolveError):
+ """raised when we are unabled to infer a node"""
+
+class UnresolvableName(InferenceError):
+ """raised when we are unabled to resolve a name"""
+
+
+class NoDefault(ASTNGError):
+ """raised by function's `default_value` method when an argument has
+ no default value
+ """
+
+class IgnoreChild(Exception):
+ """exception that maybe raised by visit methods to avoid children traversal
+ """
+
diff --git a/announce.txt b/announce.txt
new file mode 100644
index 00000000..27ebadb1
--- /dev/null
+++ b/announce.txt
@@ -0,0 +1,23 @@
+What's new ?
+------------
+%CHANGELOG%
+
+
+What is %SOURCEPACKAGE% ?
+------------------------
+%LONG_DESC%
+
+
+Home page
+---------
+%WEB%
+
+Download
+--------
+%FTP%
+
+Mailing list
+------------
+%MAILINGLIST%
+
+%ADDITIONAL_DESCR% \ No newline at end of file
diff --git a/builder.py b/builder.py
new file mode 100644
index 00000000..e296e64c
--- /dev/null
+++ b/builder.py
@@ -0,0 +1,488 @@
+# 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""The ASTNGBuilder makes astng from living object and / or from compiler.ast
+
+The builder is not thread safe and can't be used to parse different sources
+at the same time.
+
+TODO:
+ - more complet representation on inspect build
+ (imported modules ? use dis.dis ?)
+
+
+:version: $Revision: 1.54 $
+:author: Sylvain Thenault
+:copyright: 2003-2005 LOGILAB S.A. (Paris, FRANCE)
+:contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org
+:copyright: 2003-2005 Sylvain Thenault
+:contact: mailto:thenault@gmail.com
+"""
+
+__revision__ = "$Id: builder.py,v 1.54 2006-03-14 15:21:33 syt Exp $"
+__doctype__ = "restructuredtext en"
+
+import sys
+from os.path import splitext, basename, dirname, exists, abspath
+from parser import ParserError
+from compiler import parse
+from inspect import isfunction, ismethod, ismethoddescriptor, isclass, \
+ isbuiltin
+try: # python 2.2 inspect module doesn't have the isdatadescriptor function
+ from inspect import isdatadescriptor
+except ImportError:
+ def isdatadescriptor(_):
+ """fake isdatadescriptor function, always returning False"""
+ return False
+
+from logilab.common.fileutils import norm_read
+from logilab.common.modutils import modpath_from_file
+
+from logilab.astng import nodes
+from logilab.astng.utils import ASTWalker
+from logilab.astng._exceptions import ASTNGBuildingException
+from logilab.astng.raw_building import *
+
+# ast NG builder ##############################################################
+
+class ASTNGBuilder:
+ """provide astng building methods
+ """
+
+ def __init__(self): # XXX _ was the manager, keep for bw compat
+ self._module = None
+ self._file = None
+ self._done = None
+ self._stack, self._par_stack = None, None
+ self._metaclass = None
+ self._walker = ASTWalker(self)
+ self._dyn_modname_map = {'gtk': 'gtk._gtk'}
+
+ def module_build(self, module, modname=None):
+ """build an astng from a living module instance
+ """
+ node = None
+ self._module = module
+ path = getattr(module, '__file__', None)
+ if path is not None:
+ path_, ext = splitext(module.__file__)
+ if ext in ('.py', '.pyc', '.pyo') and exists(path_ + '.py'):
+ node = self.file_build(path_ + '.py', modname)
+ if node is None:
+ # this is a built-in module
+ # get a partial representation by introspection
+ node = self.inspect_build(module, modname=modname, path=path)
+ return node
+
+ def inspect_build(self, module, modname=None, path=None):
+ """build astng from a living module (i.e. using inspect)
+ this is used when there is no python source code available (either
+ because it's a built-in module or because the .py is not available)
+ """
+ self._module = module
+ node = build_module(modname or module.__name__, module.__doc__)
+ node.file = node.path = path and abspath(path) or path
+ node.package = hasattr(module, '__path__')
+ attach___dict__(node)
+ self._done = {}
+ self.object_build(node, module)
+ return node
+
+ def file_build(self, path, modname=None):
+ """build astng from a source code file (i.e. from an ast)
+
+ path is expected to be a python source file
+ """
+ try:
+ data = norm_read(path)
+ except IOError, ex:
+ msg = 'Unable to load file %r (%s)' % (path, ex)
+ raise ASTNGBuildingException(msg)
+ self._file = path
+ # get module name if necessary, *before modifying sys.path*
+ if modname is None:
+ try:
+ modname = '.'.join(modpath_from_file(path))
+ except ImportError:
+ modname = splitext(basename(path))[0]
+ # build astng representation
+ try:
+ sys.path.insert(0, dirname(path))
+ node = self.string_build(data, modname, path)
+ node.file = abspath(path)
+ finally:
+ self._file = None
+ sys.path.pop(0)
+ return node
+
+ def string_build(self, data, modname='', path=None):
+ """build astng from a source code stream (i.e. from an ast)"""
+ try:
+ return self.ast_build(parse(data + '\n'), modname, path)
+ except ParserError, ex:
+ # compiler.parse with python <= 2.2 raise ParserError instead of
+ # SyntaxError
+ ex = SyntaxError('invalid syntax')
+ ex.lineno = 1 # dummy line number
+ raise ex
+
+ def ast_build(self, node, modname=None, path=None):
+ """recurse on the ast (soon ng) to add some arguments et method
+ """
+ if path is not None:
+ node.file = node.path = abspath(path)
+ else:
+ node.file = node.path = '<?>'
+ if modname.endswith('.__init__'):
+ modname = modname[:-9]
+ node.package = True
+ else:
+ node.package = path and path.find('__init__.py') > -1 or False
+ node.name = modname
+ node.pure_python = True
+ self._walker.walk(node)
+ return node
+
+ # callbacks to build from an existing compiler.ast tree ###################
+
+ def visit_module(self, node):
+ """visit a stmt.Module node -> init node and push the corresponding
+ object or None on the top of the stack
+ """
+ self._stack = [self._module]
+ self._par_stack = [node]
+ self._metaclass = ['']
+ self._global_names = []
+ node.parent = None
+ node.globals = node.locals = {}
+ for name, value in ( ('__name__', node.name),
+ ('__file__', node.path),
+ ('__doc__', node.doc) ):
+ const = nodes.Const(value)
+ const.parent = node
+ node.locals[name] = [const]
+ attach___dict__(node)
+ if node.package:
+ # FIXME: List(Const())
+ const = nodes.Const(dirname(node.path))
+ const.parent = node
+ node.locals['__path__'] = [const]
+
+
+ def leave_module(self, _):
+ """leave a stmt.Module node -> pop the last item on the stack and check
+ the stack is empty
+ """
+ self._stack.pop()
+ assert not self._stack, 'Stack is not empty : %s' % self._stack
+ self._par_stack.pop()
+ assert not self._par_stack, \
+ 'Parent stack is not empty : %s' % self._par_stack
+
+ def visit_class(self, node):
+ """visit a stmt.Class node -> init node and push the corresponding
+ object or None on the top of the stack
+ """
+ self.visit_default(node)
+ node.instance_attrs = {}
+ node.basenames = [b_node.as_string() for b_node in node.bases]
+ self._push(node)
+ for name, value in ( ('__name__', node.name),
+ ('__module__', node.root().name),
+ ('__doc__', node.doc) ):
+ const = nodes.Const(value)
+ const.parent = node
+ node.locals[name] = [const]
+ attach___dict__(node)
+ self._metaclass.append(self._metaclass[-1])
+
+ def leave_class(self, node):
+ """leave a stmt.Class node -> pop the last item on the stack
+ """
+ self.leave_default(node)
+ self._stack.pop()
+ metaclass = self._metaclass.pop()
+ if not node.bases:
+ # no base classes, detect new / style old style according to
+ # current scope
+ node._newstyle = metaclass == 'type'
+
+ def visit_function(self, node):
+ """visit a stmt.Function node -> init node and push the corresponding
+ object or None on the top of the stack
+ """
+ self.visit_default(node)
+ self._global_names.append({})
+ node.argnames = list(node.argnames)
+ if isinstance(node.parent.frame(), nodes.Class):
+ node.type = 'method'
+ if node.name == '__new__':
+ node.type = 'classmethod'
+ self._push(node)
+ register_arguments(node, node.argnames)
+
+ def leave_function(self, node):
+ """leave a stmt.Function node -> pop the last item on the stack
+ """
+ self.leave_default(node)
+ self._stack.pop()
+ self._global_names.pop()
+
+ def visit_lambda(self, node):
+ """visit a stmt.Lambda node -> init node locals
+ """
+ self.visit_default(node)
+ node.argnames = list(node.argnames)
+ node.locals = {}
+ register_arguments(node, node.argnames)
+
+ def visit_global(self, node):
+ """visit a stmt.Global node -> add declared names to locals
+ """
+ self.visit_default(node)
+ if not self._global_names: # global at the module level, no effect
+ return
+ for name in node.names:
+ self._global_names[-1].setdefault(name, []).append(node)
+# node.parent.set_local(name, node)
+# module = node.root()
+# if module is not node.frame():
+# for name in node.names:
+# module.set_local(name, node)
+
+ def visit_import(self, node):
+ """visit a stmt.Import node -> add imported names to locals
+ """
+ self.visit_default(node)
+ for (name, asname) in node.names:
+ name = asname or name
+ node.parent.set_local(name.split('.')[0], node)
+
+ def visit_from(self, node):
+ """visit a stmt.From node -> add imported names to locals
+ """
+ self.visit_default(node)
+ # add names imported by the import to locals
+ for (name, asname) in node.names:
+ if name == '*':
+ try:
+ imported = node.root().import_module(node.modname)
+ except ASTNGBuildingException:
+ #import traceback
+ #traceback.print_exc()
+ continue
+ # FIXME: log error
+ #print >> sys.stderr, \
+ # 'Unable to get imported names for %r line %s"' % (
+ # node.modname, node.lineno)
+ for name in imported.wildcard_import_names():
+ node.parent.set_local(name, node)
+ else:
+ node.parent.set_local(asname or name, node)
+
+ def leave_decorators(self, node):
+ """python >= 2.4
+ visit a stmt.Decorator node -> check for classmethod and staticmethod
+ """
+ func = node.parent
+ for decorator_expr in node.nodes:
+ if isinstance(decorator_expr, nodes.Name) and \
+ decorator_expr.name in ('classmethod', 'staticmethod'):
+ func.type = decorator_expr.name
+ self.leave_default(node)
+
+ def visit_assign(self, node):
+ """visit a stmt.Assign node -> check for classmethod and staticmethod
+ + __metaclass__
+ """
+ self.visit_default(node)
+ klass = node.parent.frame()
+ #print node
+ if isinstance(klass, nodes.Class) and \
+ isinstance(node.expr, nodes.CallFunc) and \
+ isinstance(node.expr.node, nodes.Name):
+ func_name = node.expr.node.name
+ if func_name in ('classmethod', 'staticmethod'):
+ for ass_node in node.nodes:
+ if isinstance(ass_node, nodes.AssName):
+ try:
+ meth = klass[ass_node.name]
+ if isinstance(meth, nodes.Function):
+ meth.type = func_name
+ #else:
+ # print >> sys.stderr, 'FIXME 1', meth
+ except KeyError:
+ #print >> sys.stderr, 'FIXME 2', ass_node.name
+ continue
+ elif (isinstance(node.nodes[0], nodes.AssName)
+ and node.nodes[0].name == '__metaclass__'): # XXX check more...
+ self._metaclass[-1] = 'type'
+
+ def visit_assname(self, node):
+ """visit a stmt.AssName node -> add name to locals
+ """
+ self.visit_default(node)
+ self._add_local(node, node.name)
+
+ def visit_augassign(self, node):
+ """visit a stmt.AssName node -> add name to locals
+ """
+ self.visit_default(node)
+ if not isinstance(node.node, nodes.Name):
+ return # XXX
+ self._add_local(node, node.node.name)
+
+ def _add_local(self, node, name):
+ if self._global_names and name in self._global_names[-1]:
+ node.root().set_local(name, node)
+ else:
+ node.parent.set_local(name, node)
+
+ def visit_assattr(self, node):
+ """visit a stmt.AssAttr node -> add name to locals, handle members
+ definition
+ """
+ self.visit_default(node)
+ frame = node.frame()
+ if isinstance(frame, nodes.Function) and frame.type != 'function':
+ klass = frame.parent.frame()
+ # are we assigning to a (new ?) instance attribute ?
+ try:
+ _self = frame.argnames[0]
+ except IndexError:
+ # first argument is missing !
+ return
+ if isinstance(node.expr, nodes.Name) and node.expr.name == _self:
+ # unittest_scoped_nodes.ClassNodeTC.test_classmethod_attributes
+ #
+ # if frame.type == 'classmethod': XXX at this point we may have
+ # not encountered the classmethod decorator, so we havn't yet
+ # the correct type
+ # hack according to the argument name
+ if _self == 'self':
+ iattrs = klass.instance_attrs
+ else:
+ iattrs = klass.locals
+ # assign if not yet existant in others
+ if not iattrs.has_key(node.attrname):
+ iattrs[node.attrname] = [node]
+ # but always assign in __init__, except if previous assigment
+ # already come from __init__
+ elif frame.name == '__init__' and not \
+ iattrs[node.attrname][0].frame().name == '__init__':
+ iattrs[node.attrname].insert(0, node)
+ else:
+ iattrs[node.attrname].append(node)
+
+ def visit_default(self, node):
+ """default visit method, handle the parent attribute
+ """
+ node.parent = self._par_stack[-1]
+ assert node.parent is not node
+ self._par_stack.append(node)
+
+ def leave_default(self, _):
+ """default leave method, handle the parent attribute
+ """
+ self._par_stack.pop()
+
+ def _push(self, node):
+ """update the stack and init some parts of the Function or Class node
+ """
+ obj = getattr(self._stack[-1], node.name, None)
+ self._stack.append(obj)
+ node.locals = {}
+ node.parent.frame().set_local(node.name, node)
+
+ # astng from living objects ###############################################
+ #
+ # this is actually a really minimal representation, including only Module,
+ # Function and Class nodes and some others as guessed
+
+ def object_build(self, node, obj):
+ """recursive method which create a partial ast from real objects
+ (only function, class, and method are handled)
+ """
+ if self._done.has_key(obj):
+ return self._done[obj]
+ self._done[obj] = node
+ modname = self._module.__name__
+ modfile = getattr(self._module, '__file__', None)
+ for name in dir(obj):
+ try:
+ member = getattr(obj, name)
+ except AttributeError:
+ # damned ExtensionClass.Base, I know you're there !
+ attach_dummy_node(node, name)
+ continue
+ if ismethod(member):
+ member = member.im_func
+ if isfunction(member):
+ # verify this is not an imported function
+ if member.func_code.co_filename != modfile:
+ attach_dummy_node(node, name)
+ continue
+ object_build_function(node, member)
+ elif isbuiltin(member):
+ # verify this is not an imported member
+ if self._member_module(member) != modname:
+ imported_member(node, member, name)
+ continue
+ object_build_methoddescriptor(node, member)
+ elif isclass(member):
+ # verify this is not an imported class
+ if self._member_module(member) != modname:
+ imported_member(node, member, name)
+ continue
+ if member in self._done:
+ class_node = self._done[member]
+ node.add_local_node(class_node)
+ else:
+ class_node = object_build_class(node, member)
+ # recursion
+ self.object_build(class_node, member)
+ elif ismethoddescriptor(member):
+ assert isinstance(member, object)
+ object_build_methoddescriptor(node, member)
+ elif isdatadescriptor(member):
+ assert isinstance(member, object)
+ object_build_datadescriptor(node, member, name)
+ elif isinstance(member, (int, long, float, str, unicode)) or member is None:
+ attach_const_node(node, name, member)
+ else:
+ # create an empty node so that the name is actually defined
+ attach_dummy_node(node, name)
+
+ def _member_module(self, member):
+ modname = getattr(member, '__module__', None)
+ return self._dyn_modname_map.get(modname, modname)
+
+def imported_member(node, member, name):
+ """consider a class/builtin member where __module__ != current module name
+
+ check if it's sound valid and then add an import node, else use a dummy node
+ """
+ # /!\ some classes like ExtensionClass doesn't have a
+ # __module__ attribute !
+ member_module = getattr(member, '__module__', '__builtin__')
+ try:
+ getattr(sys.modules[member_module], name)
+ except (KeyError, AttributeError):
+ attach_dummy_node(node, name)
+ else:
+ attach_import_node(node, member_module, name)
+
+# optimize the tokenize module
+#from logilab.common.bind import optimize_module
+#import tokenize
+#optimize_module(sys.modules['tokenize'], tokenize.__dict__)
+#optimize_module(sys.modules[__name__], sys.modules[__name__].__dict__)
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 00000000..bd29ae6e
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,51 @@
+logilab-astng (0.16.0-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 19 Apr 2006 17:13:42 +0200
+
+logilab-astng (0.15.1-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 10 Mar 2006 10:15:49 +0100
+
+logilab-astng (0.15.0-1) unstable; urgency=low
+
+ * new upstream release (closes: #352889)
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 6 Mar 2006 09:18:47 +0100
+
+logilab-astng (0.14.0-2) unstable; urgency=low
+
+ * removed packaging bug
+ * upload to Debian
+
+ -- Alexandre Fayolle <afayolle@debian.org> Fri, 10 Feb 2006 15:29:00 +0100
+
+logilab-astng (0.14.0-1) unstable; urgency=low
+
+ * new upstream support
+ * reorganization to install into site-python, removing the need for
+ pythonX.X- packages
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Tue, 10 Jan 2006 14:18:38 +0100
+
+logilab-astng (0.13.1-2) unstable; urgency=low
+
+ * Dropped python2.2 support, and removed -test package, as suggested by ftpmaster
+
+ -- Alexandre Fayolle <afayolle@debian.org> Mon, 21 Nov 2005 10:14:38 +0100
+
+logilab-astng (0.13.1-1) unstable; urgency=low
+
+ * new upstream release (closes: #337960)
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 7 Nov 2005 15:38:42 +0100
+
+logilab-astng (0.13.0-1) unstable; urgency=low
+
+ * initial upstream/debian release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 21 Oct 2005 15:51:17 +0200
+
diff --git a/debian/control b/debian/control
new file mode 100644
index 00000000..ad8b8ec0
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,28 @@
+Source: logilab-astng
+Section: python
+Priority: optional
+Maintainer: Sylvain Thenault <sylvain.thenault@logilab.fr>
+Uploader: Alexandre Fayolle <afayolle@debian.org>
+Build-Depends: debhelper (>= 4.0.0), python-dev
+Standards-Version: 3.6.2
+
+Package: python-logilab-astng
+Architecture: all
+Depends: python, python-logilab-common
+Provides: python2.2-logilab-astng, python2.3-logilab-astng, python2.4-logilab-astng
+Conflicts: python2.2-logilab-astng, python2.3-logilab-astng, python2.4-logilab-astng
+Replaces: python2.2-logilab-astng, python2.3-logilab-astng, python2.4-logilab-astng
+Description: extend python's abstract syntax tree
+ The aim of this module is to provide a common base representation of
+ python source code for projects such as pychecker, pyreverse,
+ pylint... Well, actually the development of this library is essentialy
+ governed by pylint's needs.
+ .
+ It extends class defined in the compiler.ast [1] module with some
+ additional methods and attributes. Instance attributes are added by a
+ builder object, which can either generate extended ast (let's call
+ them astng ;) by visiting an existant ast tree or by inspecting living
+ object. Methods are added by monkey patching ast classes.
+ .
+ Homepage: http://www.logilab.org/projects/astng
+
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 00000000..f11944e2
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,28 @@
+This package was debianized by Sylvain Thenault <sylvain.thenault@logilab.fr> Sat, 13 Apr 2002 19:05:23 +0200.
+
+It was downloaded from ftp://ftp.logilab.org/pub/astng
+
+Upstream Author:
+
+ Sylvain Thenault <sylvain.thenault@logilab.fr>
+
+Copyright:
+
+Copyright (c) 2003-2006 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+On Debian systems, the complete text of the GNU General Public License
+may be found in '/usr/share/common-licenses/GPL'.
diff --git a/debian/python-logilab-astng.dirs b/debian/python-logilab-astng.dirs
new file mode 100644
index 00000000..ab6d53bb
--- /dev/null
+++ b/debian/python-logilab-astng.dirs
@@ -0,0 +1,6 @@
+usr/lib/site-python
+usr/lib/site-python/logilab
+usr/lib/site-python/logilab/astng
+usr/share/doc/python-logilab-astng
+usr/share/doc/python-logilab-astng
+usr/share/doc/python-logilab-astng/test
diff --git a/debian/python-logilab-astng.postinst b/debian/python-logilab-astng.postinst
new file mode 100644
index 00000000..783b00ea
--- /dev/null
+++ b/debian/python-logilab-astng.postinst
@@ -0,0 +1,26 @@
+#! /bin/sh -e
+#
+
+
+touch /usr/lib/site-python/logilab/__init__.py
+
+
+# precompile python files
+VERSION=2.3
+PACKAGEDIR=/usr/lib/site-python/logilab/astng
+case "$1" in
+ configure|abort-upgrade|abort-remove|abort-deconfigure)
+ python$VERSION -O /usr/lib/python$VERSION/compileall.py -q $PACKAGEDIR
+ python$VERSION /usr/lib/python$VERSION/compileall.py -q $PACKAGEDIR
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/python-logilab-astng.prerm b/debian/python-logilab-astng.prerm
new file mode 100644
index 00000000..c4cb31f8
--- /dev/null
+++ b/debian/python-logilab-astng.prerm
@@ -0,0 +1,14 @@
+#! /bin/sh -e
+#
+
+# remove .pyc and .pyo files
+dpkg --listfiles python-logilab-astng |
+ awk '$0~/\.py$/ {print $0"c\n" $0"o"}' |
+ xargs rm -f >&2
+
+
+
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 00000000..5cc257bc
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,71 @@
+#!/usr/bin/make -f
+# Sample debian/rules that uses debhelper.
+# GNU copyright 1997 to 1999 by Joey Hess.
+#
+# adapted by Logilab for automatic generation by debianize
+# (part of the devtools project, http://www.logilab.org/projects/devtools)
+#
+# Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+# This is the debhelper compatability version to use.
+export DH_COMPAT=4
+
+
+
+build: build-stamp
+build-stamp:
+ dh_testdir
+ python setup.py -q build
+ touch build-stamp
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp configure-stamp
+ rm -rf build
+ find . -name "*.pyc" | xargs rm -f
+ rm -f changelog.gz
+ dh_clean
+
+install: build
+ dh_testdir
+ dh_testroot
+ dh_clean -k
+ dh_installdirs
+ python setup.py -q install_lib --no-compile --install-dir=debian/python-logilab-astng/usr/lib/site-python
+ python setup.py -q install_headers --install-dir=debian/python-logilab-astng/usr/include/
+ # remove sub-package __init__ file (created in postinst)
+ rm debian/python-logilab-astng/usr/lib/site-python/logilab/__init__.py
+ # remove test directory (installed in a separated package)
+ rm -rf debian/python-logilab-astng/usr/lib/site-python/logilab/astng/test
+ # install tests
+ (cd test && find . -type f -not \( -path '*/CVS/*' -or -name '*.pyc' \) -exec install -D --mode=644 {} ../debian/python-logilab-astng/usr/share/doc/python-logilab-astng/test/{} \;)
+
+
+# Build architecture-independent files here.
+binary-indep: build install
+ dh_testdir
+ dh_testroot
+ dh_install -i
+ gzip -9 -c ChangeLog > changelog.gz
+ dh_installchangelogs -i
+ dh_installexamples -i
+ dh_installdocs -i README changelog.gz
+ dh_installman -i
+ dh_link -i
+ dh_compress -i -X.py -X.ini -X.xml -Xtest
+ dh_fixperms -i
+ dh_installdeb -i
+ dh_gencontrol -i
+ dh_md5sums -i
+ dh_builddeb -i
+
+
+
+binary: binary-indep
+.PHONY: build clean binary binary-indep
+
diff --git a/debian/watch b/debian/watch
new file mode 100644
index 00000000..b86b1270
--- /dev/null
+++ b/debian/watch
@@ -0,0 +1,3 @@
+version=2
+ftp://ftp.logilab.org/pub/astng/astng-(.*)\.tar\.gz debian uupdate
+
diff --git a/inference.py b/inference.py
new file mode 100644
index 00000000..6fca9e41
--- /dev/null
+++ b/inference.py
@@ -0,0 +1,388 @@
+# 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""this module contains a set of functions to handle inference on astng trees
+
+:version: $Revision: 1.25 $
+:author: Sylvain Thenault
+:copyright: 2003-2006 LOGILAB S.A. (Paris, FRANCE)
+:contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org
+:copyright: 2003-2006 Sylvain Thenault
+:contact: mailto:thenault@gmail.com
+"""
+
+from __future__ import generators
+
+__revision__ = "$Id: inference.py,v 1.25 2006-04-20 07:37:28 syt Exp $"
+__doctype__ = "restructuredtext en"
+
+from logilab.common.compat import imap
+
+from logilab.astng import YES, Instance, Generator, \
+ unpack_infer, _infer_stmts, nodes
+from logilab.astng import InferenceError, UnresolvableName, \
+ NoDefault, NotFoundError, ASTNGBuildingException
+
+def path_wrapper(func):
+ """return the given infer function wrapped to handle the path"""
+ def wrapped(node, name=None, path=None, _func=func, **kwargs):
+ """wrapper function handling path"""
+ if path is None:
+ path = [(node, name)]
+ else:
+ if (node, name) in path:
+ raise StopIteration()
+ path.append( (node, name) )
+ #print '--'*len(path),_func.__name__[6:], getattr(path[-1][0], 'name', ''), name
+ try:
+ for res in _func(node, name, path, **kwargs):
+ #print '--'*len(path), _func.__name__[6:], '-->', res
+ yield res
+ path.pop()
+ except:
+ path.pop()
+ raise
+ return wrapped
+
+# .infer method ###############################################################
+
+def infer_default(self, name=None, path=None):
+ """we don't know how to resolve a statement by default"""
+ #print 'inference error', self, name, path
+ raise InferenceError(self.__class__.__name__)
+
+#infer_default = infer_default
+nodes.Node.infer = infer_default
+
+
+def infer_end(self, name=None, path=None):
+ """inference's end for node such as Module, Class, Function, Const...
+ """
+ yield self
+
+#infer_end = path_wrapper(infer_end)
+nodes.Module.infer = nodes.Class.infer = infer_end
+nodes.List.infer = infer_end
+nodes.Tuple.infer = infer_end
+nodes.Dict.infer = infer_end
+nodes.Const.infer = infer_end
+
+
+def infer_function(self, name=None, path=None):
+ """infer on Function nodes must be take with care since it
+ may be called to infer one of it's argument (in which case <name>
+ should be given)
+ """
+ # no name is given, we are infering the function itself
+ if name is None:
+ yield self
+ return
+ # Function.argnames can be None in astng (means that we don't have
+ # information on argnames), in which case we can't do anything more
+ if self.argnames is None:
+ yield YES
+ return
+ if not name in self.argnames:
+ raise InferenceError()
+ # first argument of instance/class method
+ if name == self.argnames[0]:
+ if self.type == 'method':
+ yield Instance(self.parent.frame())
+ return
+ if self.type == 'classmethod':
+ yield self.parent.frame()
+ return
+ mularg = self.mularg_class(name)
+ if mularg is not None: # */** argument, no doubt it's a Tuple or Dict
+ yield mularg
+ return
+ # if there is a default value, yield it. And then yield YES to reflect
+ # we can't guess given argument value
+ try:
+ for infered in self.default_value(name).infer(name, path):
+ yield infered
+ yield YES
+ except NoDefault:
+ yield YES
+
+nodes.Function.infer = path_wrapper(infer_function)
+nodes.Lambda.infer = path_wrapper(infer_function)
+
+
+def infer_name(self, name=None, path=None):
+ """infer a Name: use name lookup rules"""
+ frame, stmts = self.lookup(self.name)
+ if not stmts:
+ raise UnresolvableName(name)
+ return _infer_stmts(stmts, self.name, path, frame)
+
+nodes.Name.infer = path_wrapper(infer_name)
+
+
+def infer_assname(self, name=None, path=None):
+ """infer a AssName/AssAttr: need to inspect the RHS part of the
+ assign node
+ """
+ stmts = self.assigned_stmts(inf_path=path)
+ return _infer_stmts(stmts, self.name, path)
+
+nodes.AssName.infer = path_wrapper(infer_assname)
+
+
+def infer_assattr(self, name=None, path=None):
+ """infer a AssName/AssAttr: need to inspect the RHS part of the
+ assign node
+ """
+ stmts = self.assigned_stmts(inf_path=path)
+ return _infer_stmts(stmts, self.attrname, path)
+
+nodes.AssAttr.infer = path_wrapper(infer_assattr)
+
+
+def infer_callfunc(self, name=None, path=None):
+ """infer a CallFunc node by trying to guess what's the function is
+ returning
+ """
+ one_infered = False
+ for callee in self.node.infer(name, path):
+ if callee is YES:
+ yield callee
+ one_infered = True
+ continue
+ try:
+ for infered in callee.infer_call_result(self, path):
+ yield infered
+ one_infered = True
+ except (AttributeError, InferenceError):
+ ## XXX log error ?
+ continue
+ if not one_infered:
+ raise InferenceError()
+
+nodes.CallFunc.infer = path_wrapper(infer_callfunc)
+
+
+def infer_getattr(self, name=None, path=None):
+ """infer a Getattr node by using getattr on the associated object
+ """
+ one_infered = False
+ for owner in self.expr.infer(name, path):
+ if owner is YES:
+ yield owner
+ one_infered = True
+ continue
+ try:
+ for obj in owner.igetattr(self.attrname, path=path):
+ yield obj
+ one_infered = True
+ except (NotFoundError, InferenceError):
+ continue
+ except AttributeError:
+ # XXX method / function
+ continue
+ if not one_infered:
+ raise InferenceError()
+
+nodes.Getattr.infer = path_wrapper(infer_getattr)
+
+
+def _imported_module_astng(node, modname):
+ """return the ast for a module whose name is <modname> imported by <node>
+ """
+ # handle special case where we are on a package node importing a module
+ # using the same name as the package, which may end in an infinite loop
+ # on relative imports
+ # XXX: no more needed ?
+ mymodule = node.root()
+ if mymodule.relative_name(modname) == mymodule.name:
+ # FIXME: I don't know what to do here...
+ raise InferenceError(modname)
+ try:
+ return mymodule.import_module(modname)
+ except (ASTNGBuildingException, SyntaxError):
+ raise InferenceError(modname)
+
+def infer_import(self, name, path=None, asname=True):
+ """self resolve on From / Import nodes return the imported module/object"""
+ if name is None:
+ infer_default(self, name, path)
+ if asname:
+ yield _imported_module_astng(self, self.real_name(name))
+ else:
+ yield _imported_module_astng(self, name)
+
+nodes.Import.infer = path_wrapper(infer_import)
+
+def infer_from(self, name, path=None, asname=True):
+ """self resolve on From / Import nodes return the imported module/object"""
+ if name is None:
+ infer_default(self, name, path)
+ module = _imported_module_astng(self, self.modname)
+ if asname:
+ name = self.real_name(name)
+ try:
+ return _infer_stmts(module.getattr(name), name, path)
+ except NotFoundError:
+ raise InferenceError(name)
+
+nodes.From.infer = path_wrapper(infer_from)
+
+
+def infer_global(self, name=None, path=None):
+ try:
+ return _infer_stmts(self.root().getattr(name), name, path)
+ except NotFoundError:
+ raise InferenceError()
+nodes.Global.infer = path_wrapper(infer_global)
+
+# .infer_call_result method ###################################################
+def callable_default(self):
+ return False
+nodes.Node.callable = callable_default
+def callable_true(self):
+ return True
+nodes.Function.callable = callable_true
+nodes.Lambda.callable = callable_true
+nodes.Class.callable = callable_true
+
+def infer_call_result_function(self, caller, inf_path=None):
+ """infer what's a function is returning wen called"""
+ if self.is_generator():
+ yield Generator(self)
+ return
+ returns = self.nodes_of_class(nodes.Return, skip_klass=nodes.Function)
+ #for infered in _infer_stmts(imap(lambda n:n.value, returns), path=inf_path):
+ # yield infered
+ for returnnode in returns:
+ try:
+ for infered in returnnode.value.infer(path=inf_path):
+ yield infered
+ except InferenceError:
+ yield YES
+nodes.Function.infer_call_result = infer_call_result_function
+
+def infer_call_result_class(self, caller, inf_path=None):
+ """infer what's a class is returning when called"""
+ yield Instance(self)
+
+nodes.Class.infer_call_result = infer_call_result_class
+
+
+# Assignment related nodes ####################################################
+
+def assend_assigned_stmts(self, inf_path=None):
+ # only infer *real* assignments
+ if self.flags == 'OP_DELETE':
+ raise InferenceError()
+ return self.parent.assigned_stmts(self, inf_path=inf_path)
+
+nodes.AssName.assigned_stmts = assend_assigned_stmts
+nodes.AssAttr.assigned_stmts = assend_assigned_stmts
+
+def mulass_assigned_stmts(self, node, path=None, inf_path=None):
+ if path is None:
+ path = []
+ node_idx = self.nodes.index(node)
+ path.insert(0, node_idx)
+ return self.parent.assigned_stmts(self, path, inf_path)
+nodes.AssTuple.assigned_stmts = mulass_assigned_stmts
+nodes.AssList.assigned_stmts = mulass_assigned_stmts
+
+def assign_assigned_stmts(self, node, path=None, inf_path=None):
+ """WARNING here `path` is a list of index to follow"""
+ if not path:
+ yield self.expr
+ return
+ found = False
+ for infered in _resolve_asspart(self.expr.infer(path=inf_path), path, inf_path):
+ found = True
+ yield infered
+ if not found:
+ raise InferenceError()
+
+nodes.Assign.assigned_stmts = assign_assigned_stmts
+
+def _resolve_asspart(parts, asspath, path):
+ """recursive function to resolve multiple assignments"""
+ asspath = asspath[:]
+ index = asspath.pop(0)
+ for part in parts:
+ try:
+ assigned = part.getitem(index)
+ except (AttributeError, IndexError):
+ return
+ if not asspath:
+ # we acheived to resolved the assigment path,
+ # don't infer the last part
+ found = True
+ yield assigned
+ elif assigned is YES:
+ return
+ else:
+ # we are not yet on the last part of the path
+ # search on each possibly infered value
+ try:
+ for infered in _resolve_asspart(assigned.infer(path=path), asspath, path):
+ yield infered
+ except InferenceError:
+ return
+
+def tryexcept_assigned_stmts(self, node, path=None, inf_path=None):
+ found = False
+ for exc_type, exc_obj, body in self.handlers:
+ if node is exc_obj:
+ for assigned in unpack_infer(exc_type):
+ if isinstance(assigned, nodes.Class):
+ assigned = Instance(assigned)
+ yield assigned
+ found = True
+ break
+ if not found:
+ raise InferenceError()
+nodes.TryExcept.assigned_stmts = tryexcept_assigned_stmts
+
+def XXX_assigned_stmts(self, node, path=None, inf_path=None):
+ raise InferenceError()
+nodes.For.assigned_stmts = XXX_assigned_stmts
+nodes.ListCompFor.assigned_stmts = XXX_assigned_stmts
+nodes.GenExprFor.assigned_stmts = XXX_assigned_stmts
+
+def end_ass_type(self):
+ return self
+nodes.For.ass_type = end_ass_type
+nodes.ListCompFor.ass_type = end_ass_type
+nodes.GenExprFor.ass_type = end_ass_type
+nodes.TryExcept.ass_type = end_ass_type
+nodes.Assign.ass_type = end_ass_type
+nodes.AugAssign.ass_type = end_ass_type
+def parent_ass_type(self):
+ return self.parent.ass_type()
+nodes.AssName.ass_type = parent_ass_type
+nodes.AssAttr.ass_type = parent_ass_type
+nodes.AssTuple.ass_type = parent_ass_type
+nodes.AssList.ass_type = parent_ass_type
+def assend_ass_type(self, inf_path=None):
+ # only infer *real* assignments
+ if self.flags == 'OP_DELETE':
+ return self
+ return self.parent.ass_type()
+nodes.AssName.ass_type = assend_ass_type
+nodes.AssAttr.ass_type = assend_ass_type
+
+# subscription protocol #######################################################
+
+def getitem(self, index):
+ return self.nodes[index]
+nodes.List.getitem = getitem
+nodes.Tuple.getitem = getitem
+#Dict.getitem = getitem XXX
+
diff --git a/inspector.py b/inspector.py
new file mode 100644
index 00000000..9693e4d3
--- /dev/null
+++ b/inspector.py
@@ -0,0 +1,267 @@
+# 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""visitor doing some postprocessing on the astng tree.
+Try to resolve definitions (namespace) dictionnary, relationship...
+
+This module has been imported from pyreverse
+
+
+:version: $Revision: 1.6 $
+:author: Sylvain Thenault
+:copyright: 2003-2005 LOGILAB S.A. (Paris, FRANCE)
+:contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org
+:copyright: 2003-2005 Sylvain Thenault
+:contact: mailto:thenault@gmail.com
+"""
+
+__revision__ = "$Id: inspector.py,v 1.6 2006-01-24 19:52:07 syt Exp $"
+__doctype__ = "restructuredtext en"
+
+from os.path import dirname
+
+from logilab.common.modutils import get_module_part, is_relative, \
+ is_standard_module
+
+from logilab import astng
+from logilab.astng.utils import LocalsVisitor
+
+class IdGeneratorMixIn:
+ """
+ Mixin adding the ability to generate integer uid
+ """
+ def __init__(self, start_value=0):
+ self.id_count = start_value
+
+ def init_counter(self, start_value=0):
+ """init the id counter
+ """
+ self.id_count = start_value
+
+ def generate_id(self):
+ """generate a new identifer
+ """
+ self.id_count += 1
+ return self.id_count
+
+
+class Linker(IdGeneratorMixIn, LocalsVisitor):
+ """
+ walk on the project tree and resolve relationships.
+
+ According to options the following attributes may be added to visited nodes:
+
+ * uid,
+ a unique identifier for the node (on astng.Project, astng.Module,
+ astng.Class and astng.locals_type). Only if the linker has been instantiad
+ with tag=True parameter (False by default).
+
+ * Function
+ a mapping from locals'names to their bounded value, which may be a
+ constant like a string or an integer, or an astng node (on astng.Module,
+ astng.Class and astng.Function).
+
+ * instance_attrs_type
+ as locals_type but for klass member attributes (only on astng.Class)
+
+ * implements,
+ list of implemented interfaces _objects_ (only on astng.Class nodes)
+ """
+
+ def __init__(self, project, inherited_interfaces=0, tag=False):
+ IdGeneratorMixIn.__init__(self)
+ LocalsVisitor.__init__(self)
+ # take inherited interface in consideration or not
+ self.inherited_interfaces = inherited_interfaces
+ # tag nodes or not
+ self.tag = tag
+ # visited project
+ self.project = project
+
+
+ def visit_project(self, node):
+ """visit an astng.Project node
+
+ * optionaly tag the node wth a unique id
+ """
+ if self.tag:
+ node.uid = self.generate_id()
+ for module in node.modules:
+ self.visit(module)
+
+ def visit_package(self, node):
+ """visit an astng.Package node
+
+ * optionaly tag the node wth a unique id
+ """
+ if self.tag:
+ node.uid = self.generate_id()
+ for subelmt in node.values():
+ self.visit(subelmt)
+
+ def visit_module(self, node):
+ """visit an astng.Module node
+
+ * set the locals_type mapping
+ * set the depends mapping
+ * optionaly tag the node wth a unique id
+ """
+ if hasattr(node, 'locals_type'):
+ return
+ node.locals_type = {}
+ node.depends = []
+ if self.tag:
+ node.uid = self.generate_id()
+
+ def visit_class(self, node):
+ """visit an astng.Class node
+
+ * set the locals_type and instance_attrs_type mappings
+ * set the implements list and build it
+ * optionaly tag the node wth a unique id
+ """
+ if hasattr(node, 'locals_type'):
+ return
+ node.locals_type = {}
+ if self.tag:
+ node.uid = self.generate_id()
+ # resolve ancestors
+ for baseobj in node.ancestors(recurs=False):
+ specializations = getattr(baseobj, 'specializations', [])
+ specializations.append(node)
+ baseobj.specializations = specializations
+ # resolve instance attributes
+ node.instance_attrs_type = {}
+ for assattrs in node.instance_attrs.values():
+ for assattr in assattrs:
+ self.visit_assattr(assattr, node)
+ # resolve implemented interface
+ try:
+ node.implements = list(node.interfaces(self.inherited_interfaces))
+ except TypeError:
+ node.implements = ()
+
+ def visit_function(self, node):
+ """visit an astng.Function node
+
+ * set the locals_type mapping
+ * optionaly tag the node wth a unique id
+ """
+ if hasattr(node, 'locals_type'):
+ return
+ node.locals_type = {}
+ if self.tag:
+ node.uid = self.generate_id()
+
+ link_project = visit_project
+ link_module = visit_module
+ link_class = visit_class
+ link_function = visit_function
+
+ def visit_assname(self, node):
+ """visit an astng.AssName node
+
+ handle locals_type
+ """
+ frame = node.frame()
+ try:
+ values = list(node.infer())
+ try:
+ already_infered = frame.locals_type[node.name]
+ for valnode in values:
+ if not valnode in already_infered:
+ already_infered.append(valnode)
+ except KeyError:
+ frame.locals_type[node.name] = values
+ except astng.InferenceError:
+ pass
+
+ def visit_assattr(self, node, parent):
+ """visit an astng.AssAttr node
+
+ handle instance_attrs_type
+ """
+ try:
+ values = list(node.infer())
+ try:
+ already_infered = parent.instance_attrs_type[node.attrname]
+ for valnode in values:
+ if not valnode in already_infered:
+ already_infered.append(valnode)
+ except KeyError:
+ parent.instance_attrs_type[node.attrname] = values
+ except astng.InferenceError:
+ pass
+
+ def visit_import(self, node):
+ """visit an astng.Import node
+
+ resolve module dependencies
+ """
+ context_file = node.root().file
+ for name in node.names:
+ relative = is_relative(name[0], context_file)
+ self._imported_module(node, name[0], relative)
+
+
+ def visit_from(self, node):
+ """visit an astng.From node
+
+ resolve module dependencies
+ """
+ basename = node.modname
+ context_file = node.root().file
+ if context_file is not None:
+ relative = is_relative(basename, context_file)
+ else:
+ relative = False
+ for name in node.names:
+ if name[0] == '*':
+ continue
+ # analyze dependancies
+ fullname = '%s.%s' % (basename, name[0])
+ if fullname.find('.') > -1:
+ try:
+ # XXX: don't use get_module_part, missing package precedence
+ fullname = get_module_part(fullname)
+ except ImportError:
+ continue
+ if fullname != basename:
+ self._imported_module(node, fullname, relative)
+
+
+ def compute_module(self, context_name, mod_path):
+ """return true if the module should be added to dependencies"""
+ package_dir = dirname(self.project.path)
+ if context_name == mod_path:
+ return 0
+ elif is_standard_module(mod_path, (package_dir,)):
+ return 1
+ return 0
+
+ # protected methods ########################################################
+
+ def _imported_module(self, node, mod_path, relative):
+ """notify an imported module, used to analyze dependancies
+ """
+ module = node.root()
+ context_name = module.name
+ if relative:
+ mod_path = '%s.%s' % ('.'.join(context_name.split('.')[:-1]),
+ mod_path)
+ if self.compute_module(context_name, mod_path):
+ # handle dependancies
+ if not hasattr(module, 'depends'):
+ module.depends = []
+ mod_paths = module.depends
+ if not mod_path in mod_paths:
+ mod_paths.append(mod_path)
diff --git a/lookup.py b/lookup.py
new file mode 100644
index 00000000..3a9c35c1
--- /dev/null
+++ b/lookup.py
@@ -0,0 +1,215 @@
+# 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""name lookup methods, available on Name ans scoped (Module, Class,
+Function...) nodes:
+
+* .lookup(name)
+* .ilookup(name)
+
+Be careful, lookup is kinda internal and return a tuple (frame, [stmts]), while
+ilookup return an iterator on infered values
+
+:version: $Revision: 1.6 $
+:author: Sylvain Thenault
+:copyright: 2003-2006 LOGILAB S.A. (Paris, FRANCE)
+:contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org
+:copyright: 2003-2006 Sylvain Thenault
+:contact: mailto:thenault@gmail.com
+"""
+
+from __future__ import generators
+
+__revision__ = "$Id: lookup.py,v 1.6 2006-03-06 08:57:53 syt Exp $"
+__doctype__ = "restructuredtext en"
+
+import __builtin__
+
+from logilab.astng.utils import are_exclusive
+from logilab.astng import MANAGER, _infer_stmts
+
+
+def lookup(self, name):
+ """lookup a variable name
+
+ return the frame and the list of assignments associated to the given name
+ according to the scope where it has been found (locals, globals or builtin)
+
+ The lookup is starting from self's frame. If self is not a frame itself and
+ the name is found in the inner frame locals, statements will be filtered
+ to remove ignorable statements according to self's location
+ """
+ #assert ID_RGX.match(name), '%r is not a valid identifier' % name
+ frame = self.frame()
+ offset = 0
+ # adjust frame for class'ancestors and function"s arguments
+ if isinstance(frame, Class):
+ if self in frame.bases:
+ #print 'frame swaping'
+ frame = frame.parent.frame()
+ # line offset to avoid that class A(A) resolve the ancestor to
+ # the defined class
+ offset = -1
+ elif isinstance(frame, (Function, Lambda)):
+ if self in frame.defaults:
+ frame = frame.parent.frame()
+ # line offset to avoid that def func(f=func) resolve the default
+ # value to the defined function
+ offset = -1
+ #print 'lookup', self.__class__, getattr(self, 'name', 'noname'), name
+ # resolve name into locals scope
+ try:
+ stmts = self._filter_stmts(frame.locals[name], frame, offset)
+ except KeyError:
+ stmts = []
+ # lookup name into globals if we were not already at the module level
+ if not stmts and frame.parent is not None:
+ try:
+ frame = frame.root()
+ stmts = self._filter_stmts(frame.locals[name], frame, 0)
+ #stmts = frame.locals[name]
+ except KeyError:
+ pass
+ # lookup name into builtins
+ if not stmts:
+ frame, stmts = builtin_lookup(name)
+ #print 'return', name, frame.name, [(stmt.__class__.__name__, getattr(stmt, 'name', '???')) for stmt in stmts]
+ return frame, stmts
+
+def builtin_lookup(name):
+ """lookup a name into the builtin module
+ return the list of matching statements and the astng for the builtin
+ module
+ """
+ builtinastng = MANAGER.astng_from_module(__builtin__)
+ try:
+ stmts = builtinastng.locals[name]
+ except KeyError:
+ stmts = ()
+ return builtinastng, stmts
+
+def ilookup(self, name, path=None):
+ """infered lookup
+
+ return an iterator on infered values of the statements returned by
+ the lookup method
+ """
+ frame, stmts = self.lookup(name)
+ return _infer_stmts(stmts, name, path, frame)
+
+
+def _filter_stmts(self, stmts, frame, offset):
+ """filter statements:
+
+ If self is not a frame itself and the name is found in the inner
+ frame locals, statements will be filtered to remove ignorable
+ statements according to self's location
+ """
+ # if offset == -1, my actual frame is not the inner frame but its parent
+ #
+ # class A(B): pass
+ #
+ # we need this to resolve B correctly
+ if offset == -1:
+ myframe = self.frame().parent.frame()
+ else:
+ myframe = self.frame()
+ if not myframe is frame or self is frame:
+ return stmts
+ #print self.name, frame.name
+ mystmt = self.statement()
+ # line filtering if we are in the same frame
+ if myframe is frame:
+ mylineno = mystmt.source_line() + offset
+ else:
+ # disabling lineno filtering
+ print 'disabling lineno filtering'
+ mylineno = 0
+ _stmts = []
+ _stmt_parents = []
+ #print '-'*60
+ #print 'filtering', stmts, mylineno
+ for node in stmts:
+ stmt = node.statement()
+ # line filtering is on and we have reached our location, break
+ if mylineno > 0 and stmt.source_line() > mylineno:
+ #print 'break', mylineno, stmt.source_line()
+ break
+ if isinstance(node, Class) and self in node.bases:
+ #print 'breaking on', self, node.bases
+ break
+ try:
+ ass_type = node.ass_type()
+ if ass_type is mystmt:
+ if not isinstance(ass_type, (ListCompFor, GenExprFor)):
+ #print 'break now2', self, ass_type
+ break
+ #print list(ass_type.assigned_stmts())
+ #if ass_type.assigned_stmts()
+ if isinstance(self, (Const, Name)):
+ _stmts = [self]
+ #print 'break now', ass_type, self, node
+ break
+ except AttributeError:
+ ass_type = None
+ # a loop assigment is hidding previous assigment
+ if isinstance(ass_type, (For, ListCompFor, GenExprFor)) and \
+ ass_type.parent_of(self):
+ _stmts = [node]
+ _stmt_parents = [stmt.parent]
+ continue
+ try:
+ pindex = _stmt_parents.index(stmt.parent)
+ except ValueError:
+ pass
+ else:
+ try:
+ if ass_type and _stmts[pindex].ass_type().parent_of(ass_type):
+ # print 'skipping', node, node.source_line()
+ continue
+ except AttributeError:
+ pass # name from Import, Function, Class...
+ if not are_exclusive(self, node):
+ ###print 'PARENT', stmt.parent
+ #print 'removing', _stmts[pindex]
+ del _stmt_parents[pindex]
+ del _stmts[pindex]
+ if isinstance(node, AssName):
+ if stmt.parent is mystmt.parent:
+ #print 'assign clear'
+ _stmts = []
+ _stmt_parents = []
+ if node.flags == 'OP_DELETE':
+ #print 'delete clear'
+ _stmts = []
+ _stmt_parents = []
+ continue
+
+ if not are_exclusive(self, node):
+ #print 'append', node, node.source_line()
+ _stmts.append(node)
+ _stmt_parents.append(stmt.parent)
+ #print '->', _stmts
+ stmts = _stmts
+ return stmts
+
+def _decorate(astmodule):
+ """add this module functionalities to necessary nodes"""
+ for klass in (astmodule.Name, astmodule.Module, astmodule.Class,
+ astmodule.Function, astmodule.Lambda):
+ klass.ilookup = ilookup
+ klass.lookup = lookup
+ klass._filter_stmts = _filter_stmts
+ for name in ('Class', 'Function', 'Lambda',
+ 'For', 'ListCompFor', 'GenExprFor',
+ 'AssName', 'Name', 'Const'):
+ globals()[name] = getattr(astmodule, name)
diff --git a/manager.py b/manager.py
new file mode 100644
index 00000000..49bd514f
--- /dev/null
+++ b/manager.py
@@ -0,0 +1,345 @@
+# 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""astng manager: avoid multible astng build of a same module when
+possible by providing a class responsible to get astng representation
+from various source and using a cache of built modules)
+
+:version: $Revision: 1.49 $
+:author: Sylvain Thenault
+:copyright: 2003-2005 LOGILAB S.A. (Paris, FRANCE)
+:contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org
+:copyright: 2003-2005 Sylvain Thenault
+:contact: mailto:thenault@gmail.com
+"""
+
+__revision__ = "$Id: manager.py,v 1.49 2006-03-08 15:52:30 syt Exp $"
+__doctype__ = "restructuredtext en"
+
+import sys
+import os
+from os.path import dirname, basename, abspath, join, isdir, exists
+
+from logilab.common.cache import Cache
+from logilab.common.modutils import NoSourceFile, is_python_source, \
+ file_from_modpath, load_module_from_name, \
+ get_module_files, get_source_file
+from logilab.common.configuration import OptionsProviderMixIn
+
+from logilab.astng._exceptions import ASTNGBuildingException
+
+def astng_wrapper(func, modname):
+ """wrapper to give to ASTNGManager.project_from_files"""
+ print 'parsing %s...' % modname
+ try:
+ return func(modname)
+ except ASTNGBuildingException, ex:
+ print ex
+ except KeyboardInterrupt:
+ raise
+ except Exception, ex:
+ import traceback
+ traceback.print_exc()
+
+
+class ASTNGManager(OptionsProviderMixIn):
+ """the astng manager, responsible to build astng from files
+ or modules.
+
+ Use the Borg pattern.
+ """
+ name = 'astng loader'
+ options = (("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.'}),
+ )
+ brain = {}
+ def __init__(self):
+ self.__dict__ = ASTNGManager.brain
+ if not self.__dict__:
+ OptionsProviderMixIn.__init__(self)
+ self._cache = None
+ self._mod_file_cache = None
+ self.set_cache_size(200)
+
+ def set_cache_size(self, cache_size):
+ """set the cache size (flush it as a side effect!)"""
+ self._cache = {} #Cache(cache_size)
+ self._mod_file_cache = {}
+
+ def from_directory(self, directory, modname=None):
+ """given a module name, return the astng object"""
+ modname = modname or basename(directory)
+ directory = abspath(directory)
+ return Package(directory, modname, self)
+
+ def astng_from_file(self, filepath, modname=None, fallback=True):
+ """given a module name, return the astng object"""
+ try:
+ filepath = get_source_file(filepath, include_no_ext=True)
+ source = True
+ except NoSourceFile:
+ source = False
+ try:
+ return self._cache[filepath]
+ except KeyError:
+ if source:
+ try:
+ from logilab.astng.builder import ASTNGBuilder
+ astng = ASTNGBuilder().file_build(filepath, modname)
+ except SyntaxError:
+ raise
+ except Exception, ex:
+ #if __debug__:
+ # import traceback
+ # traceback.print_exc()
+ msg = 'Unable to load module %s (%s)' % (modname, ex)
+ raise ASTNGBuildingException(msg), None, sys.exc_info()[-1]
+ elif fallback and modname:
+ return self.astng_from_module_name(modname)
+ else:
+ raise ASTNGBuildingException('unable to get astng for file %s' %
+ filepath)
+ self._cache[filepath] = astng
+ return astng
+
+ from_file = astng_from_file # backward compat
+
+ def astng_from_module_name(self, modname, context_file=None):
+ """given a module name, return the astng object"""
+ old_cwd = os.getcwd()
+ if context_file:
+ os.chdir(dirname(context_file))
+ try:
+ filepath = self.file_from_module_name(modname, context_file)
+ if filepath is None or not is_python_source(filepath):
+ try:
+ module = load_module_from_name(modname)
+ except ImportError, ex:
+ msg = 'Unable to load module %s (%s)' % (modname, ex)
+ raise ASTNGBuildingException(msg)
+ return self.astng_from_module(module, modname)
+ return self.astng_from_file(filepath, modname, fallback=False)
+ finally:
+ os.chdir(old_cwd)
+
+ def file_from_module_name(self, modname, contextfile):
+ try:
+ value = self._mod_file_cache[(modname, contextfile)]
+ except KeyError:
+ try:
+ value = file_from_modpath(modname.split('.'),
+ context_file=contextfile)
+ except ImportError, ex:
+ msg = 'Unable to load module %s (%s)' % (modname, ex)
+ value = ASTNGBuildingException(msg)
+ self._mod_file_cache[(modname, contextfile)] = value
+ if isinstance(value, ASTNGBuildingException):
+ raise value
+ return value
+
+ def astng_from_module(self, module, modname=None):
+ """given an imported module, return the astng object"""
+ modname = modname or module.__name__
+ filepath = modname
+ try:
+ # some builtin modules don't have __file__ attribute
+ filepath = module.__file__
+ if is_python_source(filepath):
+ return self.astng_from_file(filepath, modname)
+ except AttributeError:
+ pass
+ try:
+ return self._cache[filepath]
+ except KeyError:
+ from logilab.astng.builder import ASTNGBuilder
+ astng = ASTNGBuilder().module_build(module, modname)
+ # update caches (filepath and astng.file are not necessarily the
+ # same (.pyc pb))
+ self._cache[filepath] = self._cache[astng.file] = astng
+ return astng
+
+ def astng_from_class(self, klass, modname=None):
+ """get astng for the given class"""
+ if modname is None:
+ try:
+ modname = klass.__module__
+ except AttributeError:
+ raise ASTNGBuildingException(
+ 'Unable to get module for class %s' % klass)
+ modastng = self.astng_from_module_name(modname)
+ return modastng.getattr(klass.__name__)[0] # XXX
+
+ def project_from_files(self, files, func_wrapper=astng_wrapper,
+ project_name=None, black_list=None):
+ """return a Project from a list of files or modules"""
+ # insert current working directory to the python path to have a correct
+ # behaviour
+ sys.path.insert(0, os.getcwd())
+ try:
+ # build the project representation
+ project_name = project_name or self.config.project
+ black_list = black_list or self.config.black_list
+ project = Project(project_name)
+ for something in files:
+ if not exists(something):
+ fpath = file_from_modpath(something.split('.'))
+ elif isdir(something):
+ fpath = join(something, '__init__.py')
+ else:
+ fpath = something
+ astng = func_wrapper(self.astng_from_file, fpath)
+ if astng is None:
+ continue
+ project.path = project.path or astng.file
+ project.add_module(astng)
+ base_name = astng.name
+ # recurse in package except if __init__ was explicitly given
+ if astng.package and something.find('__init__') == -1:
+ # recurse on others packages / modules if this is a package
+ for fpath in get_module_files(dirname(astng.file),
+ black_list):
+ astng = func_wrapper(self.astng_from_file, fpath)
+ if astng is None or astng.name == base_name:
+ continue
+ project.add_module(astng)
+ return project
+ finally:
+ sys.path.pop(0)
+
+
+
+class Package:
+ """a package using a dictionary like interface
+
+ load submodules lazily, as they are needed
+ """
+
+ def __init__(self, path, name, manager):
+ self.name = name
+ self.path = abspath(path)
+ self.manager = manager
+ self.parent = None
+ self.lineno = 0
+ self.__keys = None
+ self.__subobjects = None
+
+ def fullname(self):
+ """return the full name of the package (i.e. prefix by the full name
+ of the parent package if any
+ """
+ if self.parent is None:
+ return self.name
+ return '%s.%s' % (self.parent.fullname(), self.name)
+
+ def get_subobject(self, name):
+ """method used to get sub-objects lazily : sub package or module are
+ only build once they are requested
+ """
+ if self.__subobjects is None:
+ try:
+ self.__subobjects = dict.fromkeys(self.keys())
+ except AttributeError:
+ # python <= 2.3
+ self.__subobjects = dict([(k, None) for k in self.keys()])
+ obj = self.__subobjects[name]
+ if obj is None:
+ objpath = join(self.path, name)
+ if isdir(objpath):
+ obj = Package(objpath, name, self.manager)
+ obj.parent = self
+ else:
+ modname = '%s.%s' % (self.fullname(), name)
+ obj = self.manager.astng_from_file(objpath + '.py', modname)
+ self.__subobjects[name] = obj
+ return obj
+
+ def get_module(self, modname):
+ """return the Module or Package object with the given name if any
+ """
+ path = modname.split('.')
+ if path[0] != self.name:
+ raise KeyError(modname)
+ obj = self
+ for part in path[1:]:
+ obj = obj.get_subobject(part)
+ return obj
+
+ def keys(self):
+ if self.__keys is None:
+ self.__keys = []
+ for fname in os.listdir(self.path):
+ if fname.endswith('.py'):
+ self.__keys.append(fname[:-3])
+ continue
+ fpath = join(self.path, fname)
+ if isdir(fpath) and exists(join(fpath, '__init__.py')):
+ self.__keys.append(fname)
+ self.__keys.sort()
+ return self.__keys[:]
+
+ def values(self):
+ return [self.get_subobject(name) for name in self.keys()]
+
+ def items(self):
+ return zip(self.keys(), self.values())
+
+ def has_key(self, name):
+ return bool(self.get(name))
+
+ def get(self, name, default=None):
+ try:
+ return self.get_subobject(name)
+ except KeyError:
+ return default
+
+ def __getitem__(self, name):
+ return self.get_subobject(name)
+ def __contains__(self, name):
+ return self.has_key(name)
+ def __iter__(self):
+ return iter(self.keys())
+
+
+class Project:
+ """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.has_key = self.locals.has_key
+
+ def add_module(self, node):
+ self.locals[node.name] = node
+ self.modules.append(node)
+
+ def get_module(self, name):
+ return self.locals[name]
+
+ def getChildNodes(self):
+ return self.modules
+
+ def __repr__(self):
+ return '<Project %r at %s (%s modules)>' % (self.name, id(self),
+ len(self.modules))
diff --git a/nodes.py b/nodes.py
new file mode 100644
index 00000000..f31be02a
--- /dev/null
+++ b/nodes.py
@@ -0,0 +1,768 @@
+# 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""
+on all nodes :
+ .is_statement(), returning true if the node should be considered as a
+ statement node
+ .root(), returning the root node of the tree (i.e. a Module)
+ .previous_sibling(), returning previous sibling statement node
+ .next_sibling(), returning next sibling statement node
+ .statement(), returning the first parent node marked as statement node
+ .frame(), returning the first node defining a new local scope (i.e.
+ Module, Function or Class)
+ .set_local(name, node), define an identifier <name> on the first parent frame,
+ with the node defining it. This is used by the astng builder and should not
+ be used from out there.
+ .as_string(), returning a string representation of the code (should be
+ executable).
+
+on From and Import :
+ .real_name(name),
+
+ [1] http://docs.python.org/lib/module-compiler.ast.html
+
+:version: $Revision: 1.103 $
+:author: Sylvain Thenault
+:copyright: 2003-2006 LOGILAB S.A. (Paris, FRANCE)
+:contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org
+:copyright: 2003-2006 Sylvain Thenault
+:contact: mailto:thenault@gmail.com
+"""
+
+from __future__ import generators
+
+__revision__ = "$Id: nodes.py,v 1.103 2006-04-19 14:31:38 syt Exp $"
+__doctype__ = "restructuredtext en"
+
+from compiler.ast import Assign, Add, And, AssAttr, AssList, AssName, \
+ AssTuple, Assert, Assign, AugAssign, \
+ Backquote, Bitand, Bitor, Bitxor, Break, CallFunc, Class, \
+ Compare, Const, Continue, Dict, Discard, Div, FloorDiv, \
+ Ellipsis, EmptyNode, Exec, \
+ For, From, Function, Getattr, Global, \
+ If, Import, Invert, Keyword, Lambda, LeftShift, \
+ List, ListComp, ListCompFor, ListCompIf, Mod, Module, Mul, Name, Node, \
+ Not, Or, Pass, Power, Print, Printnl, Raise, Return, RightShift, Slice, \
+ Sliceobj, Stmt, Sub, Subscript, TryExcept, TryFinally, Tuple, UnaryAdd, \
+ UnarySub, While, Yield
+try:
+ from compiler.ast import GenExpr, GenExprFor, GenExprIf, GenExprInner
+except:
+ class GenExpr:
+ """dummy GenExpr node, shouldn't be used since py < 2.4"""
+ class GenExprFor:
+ """dummy GenExprFor node, shouldn't be used since py < 2.4"""
+ class GenExprIf:
+ """dummy GenExprIf node, shouldn't be used since py < 2.4"""
+ class GenExprInner:
+ """dummy GenExprInner node, shouldn't be used since py < 2.4"""
+
+from logilab.astng._exceptions import NotFoundError, InferenceError
+from logilab.astng.utils import extend_class
+
+import re
+ID_RGX = re.compile('^[a-zA-Z_][a-zA-Z_0-9]*$')
+del re
+
+INFER_NEED_NAME_STMTS = (From, Import, Global, TryExcept)
+
+# Node ######################################################################
+
+class NodeNG:
+ """/!\ this class should not be used directly /!\ it's
+ only used as a methods and attribute container, and update the
+ original class from the compiler.ast module using its dictionnary
+ (see below the class definition)
+ """
+
+ # attributes below are set by the builder module or by raw factories
+
+ # parent node in the tree
+ parent = None
+
+ def __str__(self):
+ return '%s(%s)' % (self.__class__.__name__, getattr(self, 'name', ''))
+
+ def parent_of(self, node):
+ """return true if i'm a parent of the given node"""
+ parent = node.parent
+ while parent is not None:
+ if self is parent:
+ return True
+ parent = parent.parent
+ return False
+
+ def is_statement(self):
+ """return true if the node should be considered as statement node
+ """
+ if isinstance(self.parent, Stmt):
+ return self
+ return None
+
+ def statement(self):
+ """return the first parent node marked as statement node
+ """
+ if self.is_statement():
+ return self
+ return self.parent.statement()
+
+ def frame(self):
+ """return the first node defining a new local scope (i.e. Module,
+ Function, Lambda or Class)
+ """
+ return self.parent.frame()
+
+ def root(self):
+ """return the root node of the tree, (i.e. a Module)
+ """
+ if self.parent:
+ return self.parent.root()
+ return self
+
+ def next_sibling(self):
+ """return the previous sibling statement
+ """
+ while not self.is_statement():
+ self = self.parent
+ index = self.parent.nodes.index(self)
+ try:
+ return self.parent.nodes[index+1]
+ except IndexError:
+ return
+
+ def previous_sibling(self):
+ """return the next sibling statement
+ """
+ while not self.is_statement():
+ self = self.parent
+ index = self.parent.nodes.index(self)
+ if index > 0:
+ return self.parent.nodes[index-1]
+ return
+
+ def nearest(self, nodes):
+ """return the node which is the nearest before this one in the
+ given list of nodes
+ """
+ myroot = self.root()
+ mylineno = self.source_line()
+ nearest = None, 0
+ for node in nodes:
+ assert node.root() is myroot, \
+ 'not from the same module %s' % (self, node)
+ lineno = node.source_line()
+ if node.source_line() > mylineno:
+ break
+ if lineno > nearest[1]:
+ nearest = node, lineno
+ # FIXME: raise an exception if nearest is None ?
+ return nearest[0]
+
+ def source_line(self):
+ """return the line number where the given node appears
+
+ we need this method since not all nodes as the lineno attribute
+ correctly set...
+ """
+ line = self.lineno
+ if line is None:
+ _node = self
+ try:
+ while line is None:
+ _node = _node.getChildNodes()[0]
+ line = _node.lineno
+ except IndexError:
+ _node = self.parent
+ while _node and line is None:
+ line = _node.lineno
+ _node = _node.parent
+ self.lineno = line
+ return line
+
+ def last_source_line(self):
+ """return the last block line number for this node (i.e. including
+ children)
+ """
+ try:
+ return self.__dict__['_cached_last_source_line']
+ except KeyError:
+ line = self.source_line()
+ for node in self.getChildNodes():
+ line = max(line, node.last_source_line())
+ self._cached_last_source_line = line
+ return line
+
+ def block_range(self, lineno):
+ """handle block line numbers range for non block opening statements
+ """
+ return lineno, self.last_source_line()
+
+
+ def set_local(self, name, stmt):
+ """delegate to a scoped parent handling a locals dictionary
+ """
+ self.parent.set_local(name, stmt)
+
+ def nodes_of_class(self, klass, skip_klass=None):
+ """return an iterator on nodes which are instance of the given class(es)
+
+ klass may be a class object or a tuple of class objects
+ """
+ if isinstance(self, klass):
+ yield self
+ for child_node in self.getChildNodes():
+ if skip_klass is not None and isinstance(child_node, skip_klass):
+ continue
+ for matching in child_node.nodes_of_class(klass):
+ yield matching
+
+ def _infer_name(self, frame, name):
+ if isinstance(self, INFER_NEED_NAME_STMTS) or (
+ isinstance(self, (Function, Lambda)) and self is frame):
+ return name
+ return None
+
+
+extend_class(Node, NodeNG)
+
+# block range overrides #######################################################
+
+def function_block_range(node, lineno):
+ """handle block line numbers range for function statements
+ """
+ return node.source_line(), node.last_source_line()
+
+Function.block_range = function_block_range
+
+def if_block_range(node, lineno):
+ """handle block line numbers range for if/elif statements
+ """
+ last = None
+ for test, testbody in node.tests[1:]:
+ if lineno == testbody.source_line():
+ return lineno, lineno
+ if lineno <= testbody.last_source_line():
+ return lineno, testbody.last_source_line()
+ if last is None:
+ last = testbody.source_line() - 1
+ return elsed_block_range(node, lineno, last)
+
+If.block_range = if_block_range
+
+def try_except_block_range(node, lineno):
+ """handle block line numbers range for try/except statements
+ """
+ last = None
+ for excls, exinst, exbody in node.handlers:
+ if excls and lineno == excls.source_line():
+ return lineno, lineno
+ if exbody.source_line() <= lineno <= exbody.last_source_line():
+ return lineno, exbody.last_source_line()
+ if last is None:
+ last = exbody.source_line() - 1
+ return elsed_block_range(node, lineno, last)
+
+TryExcept.block_range = try_except_block_range
+
+def elsed_block_range(node, lineno, last=None):
+ """handle block line numbers range for try/finally, for and while
+ statements
+ """
+ if lineno == node.source_line():
+ return lineno, lineno
+ if node.else_:
+ if lineno >= node.else_.source_line():
+ return lineno, node.else_.last_source_line()
+ return lineno, node.else_.source_line() - 1
+ return lineno, last or node.last_source_line()
+
+TryFinally.block_range = elsed_block_range
+While.block_range = elsed_block_range
+For.block_range = elsed_block_range
+
+# From and Import #############################################################
+
+def real_name(node, asname):
+ """get name from 'as' name
+ """
+ for index in range(len(node.names)):
+ name, _asname = node.names[index]
+ if name == '*':
+ return asname
+ if not _asname:
+ name = name.split('.', 1)[0]
+ _asname = name
+ if asname == _asname:
+ return name
+ raise NotFoundError(asname)
+
+From.real_name = real_name
+Import.real_name = real_name
+
+
+# as_string ###################################################################
+
+def add_as_string(node):
+ """return an ast.Add node as string"""
+ return '(%s) + (%s)' % (node.left.as_string(), node.right.as_string())
+Add.as_string = add_as_string
+
+def and_as_string(node):
+ """return an ast.And node as string"""
+ return ' and '.join(['(%s)' % n.as_string() for n in node.nodes])
+And.as_string = and_as_string
+
+def assattr_as_string(node):
+ """return an ast.AssAttr node as string"""
+ if node.flags == 'OP_DELETE':
+ return 'del %s.%s' % (node.expr.as_string(), node.attrname)
+ return '%s.%s' % (node.expr.as_string(), node.attrname)
+AssAttr.as_string = assattr_as_string
+
+def asslist_as_string(node):
+ """return an ast.AssList node as string"""
+ string = ', '.join([n.as_string() for n in node.nodes])
+ return '[%s]' % string
+AssList.as_string = asslist_as_string
+
+def assname_as_string(node):
+ """return an ast.AssName node as string"""
+ if node.flags == 'OP_DELETE':
+ return 'del %s' % node.name
+ return node.name
+AssName.as_string = assname_as_string
+
+def asstuple_as_string(node):
+ """return an ast.AssTuple node as string"""
+ string = ', '.join([n.as_string() for n in node.nodes])
+ # fix for del statement
+ return string.replace(', del ', ', ')
+AssTuple.as_string = asstuple_as_string
+
+def assert_as_string(node):
+ """return an ast.Assert node as string"""
+ if node.fail:
+ return 'assert %s, %s' % (node.test.as_string(), node.fail.as_string())
+ return 'assert %s' % node.test.as_string()
+Assert.as_string = assert_as_string
+
+def assign_as_string(node):
+ """return an ast.Assign node as string"""
+ lhs = ' = '.join([n.as_string() for n in node.nodes])
+ return '%s = %s' % (lhs, node.expr.as_string())
+Assign.as_string = assign_as_string
+
+def augassign_as_string(node):
+ """return an ast.AugAssign node as string"""
+ return '%s %s %s' % (node.node.as_string(), node.op, node.expr.as_string())
+AugAssign.as_string = augassign_as_string
+
+def backquote_as_string(node):
+ """return an ast.Backquote node as string"""
+ return '`%s`' % node.expr.as_string()
+Backquote.as_string = backquote_as_string
+
+def bitand_as_string(node):
+ """return an ast.Bitand node as string"""
+ return ' & '.join(['(%s)' % n.as_string() for n in node.nodes])
+Bitand.as_string = bitand_as_string
+
+def bitor_as_string(node):
+ """return an ast.Bitor node as string"""
+ return ' | '.join(['(%s)' % n.as_string() for n in node.nodes])
+Bitor.as_string = bitor_as_string
+
+def bitxor_as_string(node):
+ """return an ast.Bitxor node as string"""
+ return ' ^ '.join(['(%s)' % n.as_string() for n in node.nodes])
+Bitxor.as_string = bitxor_as_string
+
+def break_as_string(node):
+ """return an ast.Break node as string"""
+ return 'break'
+Break.as_string = break_as_string
+
+def callfunc_as_string(node):
+ """return an ast.CallFunc node as string"""
+ expr_str = node.node.as_string()
+ args = ', '.join([arg.as_string() for arg in node.args])
+ if node.star_args:
+ args += ', *%s' % node.star_args.as_string()
+ if node.dstar_args:
+ args += ', **%s' % node.dstar_args.as_string()
+ return '%s(%s)' % (expr_str, args)
+CallFunc.as_string = callfunc_as_string
+
+def class_as_string(node):
+ """return an ast.Class node as string"""
+ bases = ', '.join([n.as_string() for n in node.bases])
+ bases = bases and '(%s)' % bases or ''
+ docs = node.doc and '\n """%s"""' % node.doc or ''
+ return 'class %s%s:%s\n %s\n' % (node.name, bases, docs,
+ node.code.as_string())
+Class.as_string = class_as_string
+
+def compare_as_string(node):
+ """return an ast.Compare node as string"""
+ rhs_str = ' '.join(['%s %s' % (op, expr.as_string())
+ for op, expr in node.ops])
+ return '%s %s' % (node.expr.as_string(), rhs_str)
+Compare.as_string = compare_as_string
+
+def const_as_string(node):
+ """return an ast.Const node as string"""
+ return repr(node.value)
+Const.as_string = const_as_string
+
+def continue_as_string(node):
+ """return an ast.Continue node as string"""
+ return 'continue'
+Continue.as_string = continue_as_string
+
+def dict_as_string(node):
+ """return an ast.Dict node as string"""
+ return '{%s}' % ', '.join(['%s: %s' % (key.as_string(), value.as_string())
+ for key, value in node.items])
+Dict.as_string = dict_as_string
+
+def discard_as_string(node):
+ """return an ast.Discard node as string"""
+ return node.expr.as_string()
+Discard.as_string = discard_as_string
+
+def div_as_string(node):
+ """return an ast.Div node as string"""
+ return '(%s) / (%s)' % (node.left.as_string(), node.right.as_string())
+Div.as_string = div_as_string
+
+def floordiv_as_string(node):
+ """return an ast.Div node as string"""
+ return '(%s) // (%s)' % (node.left.as_string(), node.right.as_string())
+FloorDiv.as_string = floordiv_as_string
+
+def ellipsis_as_string(node):
+ """return an ast.Ellipsis node as string"""
+ return '...'
+Ellipsis.as_string = ellipsis_as_string
+
+def empty_as_string(node):
+ return ''
+EmptyNode.as_string = empty_as_string
+
+def exec_as_string(node):
+ """return an ast.Exec node as string"""
+ if node.globals:
+ return 'exec %s in %s, %s' % (node.expr.as_string(),
+ node.locals.as_string(),
+ node.globals.as_string())
+ if node.locals:
+ return 'exec %s in %s' % (node.expr.as_string(),
+ node.locals.as_string())
+ return 'exec %s' % node.expr.as_string()
+Exec.as_string = exec_as_string
+
+def for_as_string(node):
+ """return an ast.For node as string"""
+ fors = 'for %s in %s:\n %s' % (node.assign.as_string(),
+ node.list.as_string(),
+ node.body.as_string())
+ if node.else_:
+ fors = '%s\nelse:\n %s' % (fors, node.else_.as_string())
+ return fors
+For.as_string = for_as_string
+
+def from_as_string(node):
+ """return an ast.From node as string"""
+ return 'from %s import %s' % (node.modname, _import_string(node.names))
+From.as_string = from_as_string
+
+def function_as_string(node):
+ """return an ast.Function node as string"""
+ fargs = node.format_args()
+ docs = node.doc and '\n """%s"""' % node.doc or ''
+ return 'def %s(%s):%s\n %s' % (node.name, fargs, docs,
+ node.code.as_string())
+Function.as_string = function_as_string
+
+def genexpr_as_string(node):
+ """return an ast.GenExpr node as string"""
+ return '(%s)' % node.code.as_string()
+GenExpr.as_string = genexpr_as_string
+
+def genexprinner_as_string(node):
+ """return an ast.GenExpr node as string"""
+ return '%s %s' % (node.expr.as_string(), ' '.join([n.as_string()
+ for n in node.quals]))
+GenExprInner.as_string = genexprinner_as_string
+
+def genexprfor_as_string(node):
+ """return an ast.GenExprFor node as string"""
+ return 'for %s in %s %s' % (node.assign.as_string(),
+ node.iter.as_string(),
+ ' '.join([n.as_string() for n in node.ifs]))
+GenExprFor.as_string = genexprfor_as_string
+
+def genexprif_as_string(node):
+ """return an ast.GenExprIf node as string"""
+ return 'if %s' % node.test.as_string()
+GenExprIf.as_string = genexprif_as_string
+
+def getattr_as_string(node):
+ """return an ast.Getattr node as string"""
+ return '%s.%s' % (node.expr.as_string(), node.attrname)
+Getattr.as_string = getattr_as_string
+
+def global_as_string(node):
+ """return an ast.Global node as string"""
+ return 'global %s' % ', '.join(node.names)
+Global.as_string = global_as_string
+
+def if_as_string(node):
+ """return an ast.If node as string"""
+ cond, body = node.tests[0]
+ ifs = ['if %s:\n %s' % (cond.as_string(), body.as_string())]
+ for cond, body in node.tests[1:]:
+ ifs.append('elif %s:\n %s' % (cond.as_string(), body.as_string()))
+ if node.else_:
+ ifs.append('else:\n %s' % node.else_.as_string())
+ return '\n'.join(ifs)
+If.as_string = if_as_string
+
+def import_as_string(node):
+ """return an ast.Import node as string"""
+ return 'import %s' % _import_string(node.names)
+Import.as_string = import_as_string
+
+def invert_as_string(node):
+ """return an ast.Invert node as string"""
+ return '~%s' % node.expr.as_string()
+Invert.as_string = invert_as_string
+
+def keyword_as_string(node):
+ """return an ast.Keyword node as string"""
+ return '%s=%s' % (node.name, node.expr.as_string())
+Keyword.as_string = keyword_as_string
+
+def lambda_as_string(node):
+ """return an ast.Lambda node as string"""
+ return 'lambda %s: %s' % (node.format_args(), node.code.as_string())
+Lambda.as_string = lambda_as_string
+
+def leftshift_as_string(node):
+ """return an ast.LeftShift node as string"""
+ return '(%s) << (%s)' % (node.left.as_string(), node.right.as_string())
+LeftShift.as_string = leftshift_as_string
+
+def list_as_string(node):
+ """return an ast.List node as string"""
+ return '[%s]' % ', '.join([child.as_string() for child in node.nodes])
+List.as_string = list_as_string
+
+def listcomp_as_string(node):
+ """return an ast.ListComp node as string"""
+ return '[%s %s]' % (node.expr.as_string(), ' '.join([n.as_string()
+ for n in node.quals]))
+ListComp.as_string = listcomp_as_string
+
+def listcompfor_as_string(node):
+ """return an ast.ListCompFor node as string"""
+ return 'for %s in %s %s' % (node.assign.as_string(),
+ node.list.as_string(),
+ ' '.join([n.as_string() for n in node.ifs]))
+ListCompFor.as_string = listcompfor_as_string
+
+def listcompif_as_string(node):
+ """return an ast.ListCompIf node as string"""
+ return 'if %s' % node.test.as_string()
+ListCompIf.as_string = listcompif_as_string
+
+def mod_as_string(node):
+ """return an ast.Mod node as string"""
+ return '(%s) %% (%s)' % (node.left.as_string(), node.right.as_string())
+Mod.as_string = mod_as_string
+
+def module_as_string(node):
+ """return an ast.Module node as string"""
+ docs = node.doc and '"""%s"""\n' % node.doc or ''
+ return '%s%s' % (docs, node.node.as_string())
+Module.as_string = module_as_string
+
+def mul_as_string(node):
+ """return an ast.Mul node as string"""
+ return '(%s) * (%s)' % (node.left.as_string(), node.right.as_string())
+Mul.as_string = mul_as_string
+
+def name_as_string(node):
+ """return an ast.Name node as string"""
+ return node.name
+Name.as_string = name_as_string
+
+def not_as_string(node):
+ """return an ast.Not node as string"""
+ return 'not %s' % node.expr.as_string()
+Not.as_string = not_as_string
+
+def or_as_string(node):
+ """return an ast.Or node as string"""
+ return ' or '.join(['(%s)' % n.as_string() for n in node.nodes])
+Or.as_string = or_as_string
+
+def pass_as_string(node):
+ """return an ast.Pass node as string"""
+ return 'pass'
+Pass.as_string = pass_as_string
+
+def power_as_string(node):
+ """return an ast.Power node as string"""
+ return '(%s) ** (%s)' % (node.left.as_string(), node.right.as_string())
+Power.as_string = power_as_string
+
+def print_as_string(node):
+ """return an ast.Print node as string"""
+ nodes = ', '.join([n.as_string() for n in node.nodes])
+ if node.dest:
+ return 'print >> %s, %s,' % (node.dest.as_string(), nodes)
+ return 'print %s,' % nodes
+Print.as_string = print_as_string
+
+def printnl_as_string(node):
+ """return an ast.Printnl node as string"""
+ nodes = ', '.join([n.as_string() for n in node.nodes])
+ if node.dest:
+ return 'print >> %s, %s' % (node.dest.as_string(), nodes)
+ return 'print %s' % nodes
+Printnl.as_string = printnl_as_string
+
+def raise_as_string(node):
+ """return an ast.Raise node as string"""
+ if node.expr1:
+ if node.expr2:
+ if node.expr3:
+ return 'raise %s, %s, %s' % (node.expr1.as_string(),
+ node.expr2.as_string(),
+ node.expr3.as_string())
+ return 'raise %s, %s' % (node.expr1.as_string(),
+ node.expr2.as_string())
+ return 'raise %s' % node.expr1.as_string()
+ return 'raise'
+Raise.as_string = raise_as_string
+
+def return_as_string(node):
+ """return an ast.Return node as string"""
+ return 'return %s' % node.value.as_string()
+Return.as_string = return_as_string
+
+def rightshift_as_string(node):
+ """return an ast.RightShift node as string"""
+ return '(%s) >> (%s)' % (node.left.as_string(), node.right.as_string())
+RightShift.as_string = rightshift_as_string
+
+def slice_as_string(node):
+ """return an ast.Slice node as string"""
+ # FIXME: use flags
+ lower = node.lower and node.lower.as_string() or ''
+ upper = node.upper and node.upper.as_string() or ''
+ return '%s[%s:%s]' % (node.expr.as_string(), lower, upper)
+Slice.as_string = slice_as_string
+
+def sliceobj_as_string(node):
+ """return an ast.Sliceobj node as string"""
+ return ':'.join([n.as_string() for n in node.nodes])
+Sliceobj.as_string = sliceobj_as_string
+
+def stmt_as_string(node):
+ """return an ast.Stmt node as string"""
+ stmts = '\n'.join([n.as_string() for n in node.nodes])
+ if isinstance(node.parent, Module):
+ return stmts
+ return stmts.replace('\n', '\n ')
+Stmt.as_string = stmt_as_string
+
+def sub_as_string(node):
+ """return an ast.Sub node as string"""
+ return '(%s) - (%s)' % (node.left.as_string(), node.right.as_string())
+Sub.as_string = sub_as_string
+
+def subscript_as_string(node):
+ """return an ast.Subscript node as string"""
+ # FIXME: flags ?
+ return '%s[%s]' % (node.expr.as_string(), ','.join([n.as_string()
+ for n in node.subs]))
+Subscript.as_string = subscript_as_string
+
+def tryexcept_as_string(node):
+ """return an ast.TryExcept node as string"""
+ trys = ['try:\n %s' % node.body.as_string()]
+ for exc_type, exc_obj, body in node.handlers:
+ if exc_type:
+ if exc_obj:
+ excs = 'except %s, %s' % (exc_type.as_string(),
+ exc_obj.as_string())
+ else:
+ excs = 'except %s' % exc_type.as_string()
+ else:
+ excs = 'except'
+ trys.append('%s:\n %s' % (excs, body.as_string()))
+ return '\n'.join(trys)
+TryExcept.as_string = tryexcept_as_string
+
+def tryfinally_as_string(node):
+ """return an ast.TryFinally node as string"""
+ return 'try:\n %s\nfinally:\n %s' % (node.body.as_string(),
+ node.final.as_string())
+TryFinally.as_string = tryfinally_as_string
+
+def tuple_as_string(node):
+ """return an ast.Tuple node as string"""
+ return '(%s)' % ', '.join([child.as_string() for child in node.nodes])
+Tuple.as_string = tuple_as_string
+
+def unaryadd_as_string(node):
+ """return an ast.UnaryAdd node as string"""
+ return '+%s' % node.expr.as_string()
+UnaryAdd.as_string = unaryadd_as_string
+
+def unarysub_as_string(node):
+ """return an ast.UnarySub node as string"""
+ return '-%s' % node.expr.as_string()
+UnarySub.as_string = unarysub_as_string
+
+def while_as_string(node):
+ """return an ast.While node as string"""
+ whiles = 'while %s:\n %s' % (node.test.as_string(),
+ node.body.as_string())
+ if node.else_:
+ whiles = '%s\nelse:\n %s' % (whiles, node.else_.as_string())
+ return whiles
+While.as_string = while_as_string
+
+def yield_as_string(node):
+ """yield an ast.Yield node as string"""
+ return 'yield %s' % node.value.as_string()
+Yield.as_string = yield_as_string
+
+
+def _import_string(names):
+ """return a list of (name, asname) formatted as a string
+ """
+ _names = []
+ for name, asname in names:
+ if asname is not None:
+ _names.append('%s as %s' % (name, asname))
+ else:
+ _names.append(name)
+ return ', '.join(_names)
+
+# to backport into compiler ###################################################
+
+EmptyNode.getChildNodes = lambda self: ()
diff --git a/raw_building.py b/raw_building.py
new file mode 100644
index 00000000..d00fdfb1
--- /dev/null
+++ b/raw_building.py
@@ -0,0 +1,214 @@
+# 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""this module contains a set of functions to create astng trees from scratch
+(build_* functions) or from living object (object_build_* functions)
+
+:version: $Revision: 1.12 $
+:author: Sylvain Thenault
+:copyright: 2003-2005 LOGILAB S.A. (Paris, FRANCE)
+:contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org
+:copyright: 2003-2005 Sylvain Thenault
+:contact: mailto:thenault@gmail.com
+"""
+
+__revision__ = "$Id: raw_building.py,v 1.12 2006-03-05 14:44:15 syt Exp $"
+__doctype__ = "restructuredtext en"
+
+import sys
+from inspect import getargspec
+
+from logilab.astng import nodes
+
+def attach___dict__(node):
+ """attach the __dict__ attribute to Class and Module objects"""
+ dictn = nodes.Dict([])
+ dictn.parent = node
+ node.locals['__dict__'] = [dictn]
+
+def attach_dummy_node(node, name):
+ """create a dummy node and register it in the locals of the given
+ node with the specified name
+ """
+ _attach_local_node(node, nodes.EmptyNode(), name)
+
+def attach_const_node(node, name, value):
+ """create a Const node and register it in the locals of the given
+ node with the specified name
+ """
+ _attach_local_node(node, nodes.Const(value), name)
+
+def attach_import_node(node, modname, membername):
+ """create a From node and register it in the locals of the given
+ node with the specified name
+ """
+ _attach_local_node(node, nodes.From(modname, ( (membername, None), ) ),
+ membername)
+
+def _attach_local_node(parent, node, name):
+ node.name = name # needed by add_local_node
+ node.parent = parent
+ node.lineno = 1
+ parent.add_local_node(node)
+
+
+def build_module(name, doc=None):
+ """create and initialize a astng Module node"""
+ node = nodes.Module(doc, nodes.Stmt([]))
+ node.node.parent = node
+ node.name = name
+ node.pure_python = False
+ node.package = False
+ node.parent = None
+ node.globals = node.locals = {}
+ return node
+
+def build_class(name, basenames=None, doc=None):
+ """create and initialize a astng Class node"""
+ klass = nodes.Class(name, [], doc, nodes.Stmt([]))
+ bases = [nodes.Name(base) for base in basenames]
+ for base in bases:
+ base.parent = klass
+ klass.basenames = basenames
+ klass.bases = bases
+ klass.code.parent = klass
+ klass.locals = {}
+ klass.instance_attrs = {}
+ for name, value in ( ('__name__', name),
+ #('__module__', node.root().name),
+ ):
+ const = nodes.Const(value)
+ const.parent = klass
+ klass.locals[name] = [const]
+ return klass
+
+# introduction of decorators has changed the Function initializer arguments
+if sys.version_info >= (2, 4):
+ try:
+ from compiler.ast import Decorators as BaseDecorators
+ class Decorators(BaseDecorators):
+ def __init__(self):
+ BaseDecorators.__init__(self, [], 0)
+ except ImportError:
+ Decorators = list
+
+ def build_function(name, args=None, defaults=None, flag=0, doc=None):
+ """create and initialize a astng Function node"""
+ args, defaults = args or [], defaults or []
+ # first argument is now a list of decorators
+ func = nodes.Function(Decorators(), name, args, defaults, flag, doc,
+ nodes.Stmt([]))
+ func.code.parent = func
+ func.locals = {}
+ if args:
+ register_arguments(func, args)
+ return func
+
+else:
+ def build_function(name, args=None, defaults=None, flag=0, doc=None):
+ """create and initialize a astng Function node"""
+ args, defaults = args or [], defaults or []
+ func = nodes.Function(name, args, defaults, flag, doc, nodes.Stmt([]))
+ func.code.parent = func
+ func.locals = {}
+ if args:
+ register_arguments(func, args)
+ return func
+
+
+def build_name_assign(name, value):
+ """create and initialize an astng Assign for a name assignment"""
+ return nodes.Assign([nodes.AssName(name, 'OP_ASSIGN')], nodes.Const(value))
+
+def build_attr_assign(name, value, attr='self'):
+ """create and initialize an astng Assign for an attribute assignment"""
+ return nodes.Assign([nodes.AssAttr(nodes.Name(attr), name, 'OP_ASSIGN')],
+ nodes.Const(value))
+
+def build_from_import(fromname, names):
+ """create and intialize an astng From import statement"""
+ return nodes.From(fromname, [(name, None) for name in names])
+
+def register_arguments(node, args):
+ """add given arguments to local
+
+ args is a list that may contains nested lists
+ (i.e. def func(a, (b, c, d)): ...)
+ """
+ for arg in args:
+ if type(arg) is type(''):
+ node.set_local(arg, node)
+ else:
+ register_arguments(node, arg)
+
+
+def object_build_class(node, member):
+ """create astng for a living class object"""
+ basenames = [base.__name__ for base in member.__bases__]
+ return _base_class_object_build(node, member, basenames)
+
+def object_build_function(node, member):
+ """create astng for a living function object"""
+ args, varargs, varkw, defaults = getargspec(member)
+ if varargs is not None:
+ args.append(varargs)
+ if varkw is not None:
+ args.append(varkw)
+ func = build_function(member.__name__, args, defaults,
+ member.func_code.co_flags, member.__doc__)
+ node.add_local_node(func)
+
+def object_build_datadescriptor(node, member, name):
+ """create astng for a living data descriptor object"""
+ return _base_class_object_build(node, member, [], name)
+
+def object_build_methoddescriptor(node, member):
+ """create astng for a living method descriptor object"""
+ # FIXME get arguments ?
+ func = build_function(member.__name__, doc=member.__doc__)
+ # set argnames to None to notice that we have no information, not
+ # and empty argument list
+ func.argnames = None
+ node.add_local_node(func)
+
+def _base_class_object_build(node, member, basenames, name=None):
+ """create astng for a living class object, with a given set of base names
+ (e.g. ancestors)
+ """
+ klass = build_class(name or member.__name__, basenames, member.__doc__)
+ klass._newstyle = isinstance(member, type)
+ node.add_local_node(klass)
+ try:
+ # limit the instantiation trick since it's too dangerous
+ # (such as infinite test execution...)
+ # this at least resolves common case such as Exception.args,
+ # OSError.errno
+ if issubclass(member, Exception):
+ instdict = member().__dict__
+ else:
+ raise TypeError
+ except:
+ pass
+ else:
+ for name in instdict.keys():
+ valnode = nodes.EmptyNode()
+ valnode.parent = klass
+ valnode.lineno = 1
+ klass.instance_attrs[name] = [valnode]
+ return klass
+
+
+__all__ = ('register_arguments', 'build_module',
+ 'object_build_class', 'object_build_function',
+ 'object_build_datadescriptor', 'object_build_methoddescriptor',
+ 'attach___dict__', 'attach_dummy_node',
+ 'attach_const_node', 'attach_import_node')
diff --git a/scoped_nodes.py b/scoped_nodes.py
new file mode 100644
index 00000000..e86ddd71
--- /dev/null
+++ b/scoped_nodes.py
@@ -0,0 +1,644 @@
+# 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""This module extends ast "scoped" node, i.e. which are opening a new
+local scope in the language definition : Module, Class, Function (and
+Lambda in some extends).
+
+Each new methods and attributes added on each class are documented
+below.
+
+
+:version: $Revision: 1.28 $
+:author: Sylvain Thenault
+:copyright: 2003-2006 LOGILAB S.A. (Paris, FRANCE)
+:contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org
+:copyright: 2003-2006 Sylvain Thenault
+:contact: mailto:thenault@gmail.com
+"""
+
+from __future__ import generators
+
+__revision__ = "$Id: scoped_nodes.py,v 1.28 2006-04-20 07:37:28 syt Exp $"
+__doctype__ = "restructuredtext en"
+
+import sys
+from compiler.ast import Module, Class, Function, Lambda, Dict, Tuple, Raise, \
+ Pass, Raise, Yield, Name, Const
+
+from logilab.common.compat import chain
+
+from logilab.astng._exceptions import NotFoundError, NoDefault, \
+ ASTNGBuildingException, InferenceError
+from logilab.astng.utils import extend_class
+from logilab.astng import YES, MANAGER, Instance, unpack_infer, _infer_stmts
+
+# module class dict/iterator interface ########################################
+
+class LocalsDictMixIn(object):
+ """ this class provides locals handling common to Module, Function
+ and Class nodes, including a dict like interface for direct access
+ to locals information
+
+ /!\ this class should not be used directly /!\ it's
+ only used as a methods and attribute container, and update the
+ original class from the compiler.ast module using its dictionnary
+ (see below the class definition)
+ """
+
+ # attributes below are set by the builder module or by raw factories
+
+ # dictionary of locals with name as key and node defining the local as
+ # value
+ locals = None
+
+
+ def frame(self):
+ """return the first node defining a new local scope (i.e. Module,
+ Function or Class)
+ """
+ return self
+
+ def set_local(self, name, stmt):
+ """define <name> in locals (<stmt> is the node defining the name)
+ if the node is a Module node (i.e. has globals), add the name to
+ globals
+
+ if the name is already defined, ignore it
+ """
+ self.locals.setdefault(name, []).append(stmt)
+
+ __setitem__ = set_local
+
+ def add_local_node(self, child_node):
+ """append a child which should alter locals to the given node"""
+ self._append_node(child_node)
+ self.set_local(child_node.name, child_node)
+
+ def _append_node(self, child_node):
+ """append a child, linking it in the tree"""
+ self.code.nodes.append(child_node)
+ child_node.parent = self
+
+ def __getitem__(self, item):
+ """method from the `dict` interface returning the first node
+ associated with the given name in the locals dictionnary
+
+ :type item: str
+ :param item: the name of the locally defined object
+ :raises KeyError: if the name is not defined
+ """
+ return self.locals[item][0]
+
+ def __iter__(self):
+ """method from the `dict` interface returning an iterator on
+ `self.keys()`
+ """
+ return iter(self.keys())
+
+ def keys(self):
+ """method from the `dict` interface returning a tuple containing
+ locally defined names
+ """
+ return self.locals.keys()
+## associated to nodes which are instance of `Function` or
+## `Class`
+## """
+## # FIXME: sort keys according to line number ?
+## try:
+## return self.__keys
+## except AttributeError:
+## keys = [member.name for member in self.locals.values()
+## if (isinstance(member, Function)
+## or isinstance(member, Class))
+## and member.parent.frame() is self]
+## self.__keys = tuple(keys)
+## return keys
+
+ def values(self):
+ """method from the `dict` interface returning a tuple containing
+ locally defined nodes which are instance of `Function` or `Class`
+ """
+ return [self[key] for key in self.keys()]
+
+ def items(self):
+ """method from the `dict` interface returning a list of tuple
+ containing each locally defined name with its associated node,
+ which is an instance of `Function` or `Class`
+ """
+ return zip(self.keys(), self.values())
+
+ def has_key(self, name):
+ """method from the `dict` interface returning True if the given
+ name is defined in the locals dictionary
+ """
+ return self.locals.has_key(name)
+
+ __contains__ = has_key
+
+extend_class(Module, LocalsDictMixIn)
+extend_class(Class, LocalsDictMixIn)
+extend_class(Function, LocalsDictMixIn)
+extend_class(Lambda, LocalsDictMixIn)
+
+class GetattrMixIn(object):
+ def getattr(self, name, path=None):
+ try:
+ return self.locals[name]
+ except KeyError:
+ raise NotFoundError(name)
+
+ def igetattr(self, name, path=None):
+ """infered getattr"""
+ try:
+ return _infer_stmts(self.getattr(name, path), name, frame=self, path=path)
+ except NotFoundError:
+ raise InferenceError(name)
+extend_class(Module, GetattrMixIn)
+extend_class(Class, GetattrMixIn)
+
+# Module #####################################################################
+
+class ModuleNG(object):
+ """/!\ this class should not be used directly /!\ it's
+ only used as a methods and attribute container, and update the
+ original class from the compiler.ast module using its dictionnary
+ (see below the class definition)
+ """
+
+ # attributes below are set by the builder module or by raw factories
+
+ # the file from which as been extracted the astng representation. It may
+ # be None if the representation has been built from a built-in module
+ file = None
+ # the module name
+ name = None
+ # boolean for astng built from source (i.e. ast)
+ pure_python = None
+ # boolean for package module
+ package = None
+ # dictionary of globals with name as key and node defining the global
+ # as value
+ globals = None
+
+ def getattr(self, name, path=None):
+ try:
+ return self.locals[name]
+ except KeyError:
+ if self.package:
+ try:
+ return [self.import_module(name, relative_only=True)]
+ except KeyboardInterrupt:
+ raise
+ except:
+ pass
+ raise NotFoundError(name)
+
+ def _append_node(self, child_node):
+ """append a child version specific to Module node"""
+ self.node.nodes.append(child_node)
+ child_node.parent = self
+
+ def source_line(self):
+ """return the source line number, 0 on a module"""
+ return 0
+
+ def fully_defined(self):
+ """return True if this module has been built from a .py file
+ and so contains a complete representation including the code
+ """
+ return self.file is not None and self.file.endswith('.py')
+
+ def statement(self):
+ """return the first parent node marked as statement node
+ consider a module as a statement...
+ """
+ return self
+
+ def import_module(self, modname, relative_only=False):
+ """import the given module considering self as context"""
+ try:
+ return MANAGER.astng_from_module_name(self.relative_name(modname))
+ except ASTNGBuildingException:
+ if relative_only:
+ raise
+ module = MANAGER.astng_from_module_name(modname)
+ return module
+
+ def relative_name(self, modname):
+ if self.package:
+ return '%s.%s' % (self.name, modname)
+ package_name = '.'.join(self.name.split('.')[:-1])
+ if package_name:
+ return '%s.%s' % (package_name, modname)
+ return modname
+
+ def wildcard_import_names(self):
+ """return the list of imported names when this module is 'wildard
+ imported'
+
+ It doesn't include the '__builtins__' name which is added by the
+ current CPython implementation of wildcard imports.
+ """
+ # take advantage of a living module if it exists
+ try:
+ living = sys.modules[self.name]
+ except KeyError:
+ pass
+ else:
+ try:
+ return living.__all__
+ except AttributeError:
+ return [name for name in living.__dict__.keys()
+ if not name.startswith('_')]
+ # else lookup the astng
+ try:
+ explicit = self['__all__'].assigned_stmts().next()
+ # should be a tuple of constant string
+ return [const.value for const in explicit.nodes]
+ except (KeyError, AttributeError, InferenceError):
+ # XXX should admit we have lost if there is something like
+ # __all__ that we've not been able to analyse (such as
+ # dynamically constructed __all__)
+ return [name for name in self.keys()
+ if not name.startswith('_')]
+
+extend_class(Module, ModuleNG)
+
+# Function ###################################################################
+
+class FunctionNG(object):
+ """/!\ this class should not be used directly /!\ it's
+ only used as a methods and attribute container, and update the
+ original class from the compiler.ast module using its dictionnary
+ (see below the class definition)
+ """
+
+ # attributes below are set by the builder module or by raw factories
+
+ # function's type, 'function' | 'method' | 'staticmethod' | 'classmethod'
+ type = 'function'
+ # list of argument names. MAY BE NONE on some builtin functions where
+ # arguments are unknown
+ argnames = None
+
+ def is_method(self):
+ """return true if the function node should be considered as a method"""
+ return self.type != 'function'
+
+ def is_abstract(self, pass_is_abstract=True):
+ """return true if the method is abstract
+ It's considered as abstract if the only statement is a raise of
+ NotImplementError, or, if pass_is_abstract, a pass statement
+ """
+ for child_node in self.code.getChildNodes():
+ if isinstance(child_node, Raise) and child_node.expr1:
+ try:
+ name = child_node.expr1.nodes_of_class(Name).next()
+ if name.name == 'NotImplementedError':
+ return True
+ except StopIteration:
+ pass
+ if pass_is_abstract and isinstance(child_node, Pass):
+ return True
+ return False
+ # empty function is the same as function with a single "pass" statement
+ if pass_is_abstract:
+ return True
+
+ def is_generator(self):
+ """return true if this is a generator function"""
+ try:
+ return self.nodes_of_class(Yield, skip_klass=Function).next()
+ except StopIteration:
+ return False
+
+ def format_args(self):
+ """return arguments formatted as string"""
+ if self.argnames is None: # information is missing
+ return ''
+ result = []
+ args, kwargs, last, default_idx = self._pos_information()
+ for i in range(len(self.argnames)):
+ name = self.argnames[i]
+ if type(name) is type(()):
+ name = '(%s)' % ','.join(name)
+ if i == last and kwargs:
+ name = '**%s' % name
+ elif args and i == last or (kwargs and i == last - 1):
+ name = '*%s' % name
+ elif i >= default_idx:
+ default_str = self.defaults[i - default_idx].as_string()
+ name = '%s=%s' % (name, default_str)
+ result.append(name)
+ return ', '.join(result)
+
+ def default_value(self, argname):
+ """return the default value for an argument
+
+ :raise `NoDefault`: if there is no default value defined
+ """
+ if self.argnames is None: # information is missing
+ raise NoDefault()
+ args, kwargs, last, defaultidx = self._pos_information()
+ i = self.argnames.index(argname)
+ if i >= defaultidx and (i - defaultidx) < len(self.defaults):
+ return self.defaults[i - defaultidx]
+ raise NoDefault()
+
+ def mularg_class(self, argname):
+ """if the given argument is a * or ** argument, return respectivly
+ a Tuple or Dict instance, else return None
+ """
+ args, kwargs, last, defaultidx = self._pos_information()
+ i = self.argnames.index(argname)
+ if i == last and kwargs:
+ valnode = Dict([])
+ valnode.parent = self
+ return valnode
+ if args and (i == last or (kwargs and i == last - 1)):
+ valnode = Tuple([])
+ valnode.parent = self
+ return valnode
+ return None
+
+ def _pos_information(self):
+ """return a 4-uple with positional information about arguments:
+ (true if * is used,
+ true if ** is used,
+ index of the last argument,
+ index of the first argument having a default value)
+ """
+ args = self.flags & 4
+ kwargs = self.flags & 8
+ last = len(self.argnames) - 1
+ defaultidx = len(self.argnames) - (len(self.defaults) +
+ (args and 1 or 0) +
+ (kwargs and 1 or 0))
+ return args, kwargs, last, defaultidx
+
+extend_class(Function, FunctionNG)
+
+# lambda nodes may also need some of the function members
+Lambda._pos_information = FunctionNG._pos_information.im_func
+Lambda.format_args = FunctionNG.format_args.im_func
+Lambda.default_value = FunctionNG.default_value.im_func
+Lambda.mularg_class = FunctionNG.mularg_class.im_func
+Lambda.type = 'function'
+
+# Class ######################################################################
+
+def _class_type(klass):
+ """return a Class node type to differ metaclass, interface and exception
+ from 'regular' classes
+ """
+ if klass._type is not None:
+ return klass._type
+ if klass.name == 'type':
+ klass._type = 'metaclass'
+ elif klass.name.endswith('Interface'):
+ klass._type = 'interface'
+ elif klass.name.endswith('Exception'):
+ klass._type = 'exception'
+ else:
+ for base in klass.ancestors(recurs=False):
+ if base.type != 'class':
+ klass._type = base.type
+ break
+ if klass._type is None:
+ klass._type = 'class'
+ return klass._type
+
+def _class_newstyle(klass):
+ """return a if the given class is new-style or not
+ """
+ if klass._newstyle is not None:
+ return klass._newstyle
+ for base in klass.ancestors(recurs=False):
+ if base.newstyle:
+ klass._newstyle = base.newstyle
+ break
+ if klass._newstyle is None:
+ klass._newstyle = False
+ return klass._newstyle
+
+def _iface_hdlr(iface_node):
+ """a handler function used by interfaces to handle suspicious
+ interface nodes
+ """
+ return True
+
+class ClassNG(object):
+ """/!\ this class should not be used directly /!\ it's
+ only used as a methods and attribute container, and update the
+ original class from the compiler.ast module using its dictionnary
+ (see below the class definition)
+ """
+
+ _type = None
+ type = property(_class_type,
+ doc="class'type, possible values are 'class' | "
+ "'metaclass' | 'interface' | 'exception'")
+
+ _newstyle = None
+ newstyle = property(_class_newstyle,
+ doc="boolean indicating if it's a new style class"
+ "or not")
+
+ # attributes below are set by the builder module or by raw factories
+
+ # a dictionary of class instances attributes
+ instance_attrs = None
+ # list of parent class as a list of string (ie names as they appears
+ # in the class definition)
+ basenames = None
+
+ def ancestors(self, recurs=True, path=None):
+ """return an iterator on the node base classes in a prefixed
+ depth first order
+
+ :param recurs:
+ boolean indicating if it should recurse or return direct
+ ancestors only
+ """
+ # FIXME: should be possible to choose the resolution order
+ if path is None:
+ path = []
+ for stmt in self.bases:
+ try:
+ for baseobj in stmt.infer(path=path):
+ if not isinstance(baseobj, Class):
+ # duh ?
+ continue
+ yield baseobj
+ if recurs:
+ for grandpa in baseobj.ancestors(True, path):
+ yield grandpa
+ except InferenceError:
+ #import traceback
+ #traceback.print_exc()
+ # XXX log error ?
+ continue
+
+ def local_attr_ancestors(self, name, path=None):
+ """return an iterator on astng representation of parent classes
+ which have <name> defined in their locals
+ """
+ for astng in self.ancestors(path=path):
+ if astng.locals.has_key(name):
+ yield astng
+
+ def instance_attr_ancestors(self, name, path=None):
+ """return an iterator on astng representation of parent classes
+ which have <name> defined in their instance attribute dictionary
+ """
+ for astng in self.ancestors(path=path):
+ if astng.instance_attrs.has_key(name):
+ yield astng
+
+ def local_attr(self, name, path=None):
+ """return the astng associated to name in this class locals or
+ in its parents
+
+ :raises `NotFoundError`:
+ if no attribute with this name has been find in this class or
+ its parent classes
+ """
+ try:
+ return self[name]
+ except KeyError:
+ # get if from the first parent implementing it if any
+ for class_node in self.local_attr_ancestors(name, path):
+ return class_node[name]
+ raise NotFoundError(name)
+
+ def instance_attr(self, name, path=None):
+ """return the astng nodes associated to name in this class instance
+ attributes dictionary or in its parents
+
+ :raises `NotFoundError`:
+ if no attribute with this name has been find in this class or
+ its parent classes
+ """
+ try:
+ return self.instance_attrs[name]
+ except KeyError:
+ # get if from the first parent implementing it if any
+ for class_node in self.instance_attr_ancestors(name, path):
+ return class_node.instance_attrs[name]
+ raise NotFoundError(name)
+
+ def getattr(self, name, path=None):
+ """this method doesn't look in the instance_attrs dictionary since it's
+ done by an Instance proxy at inference time.
+
+ It may return a YES object if the attribute has not been actually
+ found but a __getattr__ or __getattribute__ method is defined
+ """
+ if name in self.locals:
+ return self.locals[name]
+ for classnode in self.ancestors(recurs=False, path=path):
+ try:
+ return classnode.getattr(name, path)
+ except NotFoundError:
+ continue
+ raise NotFoundError(name)
+
+ def igetattr(self, name, path=None):
+ """infered getattr, need special treatment in class to handle
+ descriptors
+ """
+ try:
+ for infered in _infer_stmts(self.getattr(name, path), name,
+ frame=self, path=path):
+ # yield YES object instead of descriptors when necessary
+ if not isinstance(infered, Const) and isinstance(infered, Instance):
+ try:
+ infered._proxied.getattr('__get__', path)
+ except NotFoundError:
+ yield infered
+ else:
+ yield YES
+ else:
+ yield infered
+ except NotFoundError:
+ if not name.startswith('__') and self.has_dynamic_getattr(path):
+ # class handle some dynamic attributes, return a YES object
+ yield YES
+ else:
+ raise InferenceError(name)
+
+ def has_dynamic_getattr(self, path=None):
+ """return True if the class has a custom __getattr__ or
+ __getattribute__ method
+ """
+ try:
+ self.getattr('__getattr__', path)
+ return True
+ except NotFoundError:
+ try:
+ getattribute = self.getattr('__getattribute__', path)[0]
+ if getattribute.root().name != '__builtin__':
+ # class has a custom __getattribute__ defined
+ return True
+ except NotFoundError:
+ pass
+ return False
+
+ def methods(self):
+ """return an iterator on all methods defined in the class and
+ its ancestors
+ """
+ done = {}
+ for astng in chain(iter((self,)), self.ancestors()):
+ for meth in astng.mymethods():
+ if done.has_key(meth.name):
+ continue
+ done[meth.name] = None
+ yield meth
+
+ def mymethods(self):
+ """return an iterator on all methods defined in the class"""
+ for member in self.values():
+ if isinstance(member, Function):
+ yield member
+
+ def interfaces(self, 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 = Instance(self).getattr('__implements__')[0]
+ except NotFoundError:
+ return
+ if not herited and not implements.frame() is self:
+ return
+ oneinf = False
+ for iface in unpack_infer(implements):
+ if iface is YES:
+ continue
+ if handler_func(iface):
+ oneinf = True
+ yield iface
+ if not oneinf:
+ raise InferenceError()
+## if hasattr(implements, 'nodes'):
+## implements = implements.nodes
+## else:
+## implements = (implements,)
+## for iface in implements:
+## # let the handler function take care of this....
+## for iface in handler_func(iface):
+## yield iface
+
+extend_class(Class, ClassNG)
diff --git a/setup.py b/setup.py
new file mode 100644
index 00000000..6eb65124
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python
+# pylint: disable-msg=W0142,W0403,W0404,E0611,W0613,W0622,W0622,W0704,R0904
+#
+# Copyright (c) 2003 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+""" Generic Setup script, takes package info from __pkginfo__.py file """
+
+from __future__ import nested_scopes
+
+__revision__ = '$Id: setup.py,v 1.3 2006-01-03 14:30:39 syt Exp $'
+
+import os
+import sys
+import shutil
+from distutils.core import setup
+from distutils.command import install_lib
+from os.path import isdir, exists, join, walk
+
+# import required features
+from __pkginfo__ import modname, version, license, short_desc, long_desc, \
+ web, author, author_email
+# import optional features
+try:
+ from __pkginfo__ import distname
+except ImportError:
+ distname = modname
+try:
+ from __pkginfo__ import scripts
+except ImportError:
+ scripts = []
+try:
+ from __pkginfo__ import data_files
+except ImportError:
+ data_files = None
+try:
+ from __pkginfo__ import subpackage_of
+except ImportError:
+ subpackage_of = None
+try:
+ from __pkginfo__ import include_dirs
+except ImportError:
+ include_dirs = []
+try:
+ from __pkginfo__ import ext_modules
+except ImportError:
+ ext_modules = None
+
+BASE_BLACKLIST = ('CVS', 'debian', 'dist', 'build', '__buildlog')
+IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc')
+
+
+def ensure_scripts(linux_scripts):
+ """
+ Creates the proper script names required for each platform
+ (taken from 4Suite)
+ """
+ from distutils import util
+ if util.get_platform()[:3] == 'win':
+ scripts_ = [script + '.bat' for script in linux_scripts]
+ else:
+ scripts_ = linux_scripts
+ return scripts_
+
+
+def get_packages(directory, prefix):
+ """return a list of subpackages for the given directory
+ """
+ result = []
+ for package in os.listdir(directory):
+ absfile = join(directory, package)
+ if isdir(absfile):
+ if exists(join(absfile, '__init__.py')) or \
+ package in ('test', 'tests'):
+ if prefix:
+ result.append('%s.%s' % (prefix, package))
+ else:
+ result.append(package)
+ result += get_packages(absfile, result[-1])
+ return result
+
+def export(from_dir, to_dir,
+ blacklist=BASE_BLACKLIST,
+ ignore_ext=IGNORED_EXTENSIONS):
+ """make a mirror of from_dir in to_dir, omitting directories and files
+ listed in the black list
+ """
+ def make_mirror(arg, directory, fnames):
+ """walk handler"""
+ for norecurs in blacklist:
+ try:
+ fnames.remove(norecurs)
+ except ValueError:
+ pass
+ for filename in fnames:
+ # don't include binary files
+ if filename[-4:] in ignore_ext:
+ continue
+ if filename[-1] == '~':
+ continue
+ src = '%s/%s' % (directory, filename)
+ dest = to_dir + src[len(from_dir):]
+ print >> sys.stderr, src, '->', dest
+ if os.path.isdir(src):
+ if not exists(dest):
+ os.mkdir(dest)
+ else:
+ if exists(dest):
+ os.remove(dest)
+ shutil.copy2(src, dest)
+ try:
+ os.mkdir(to_dir)
+ except OSError, ex:
+ # file exists ?
+ import errno
+ if ex.errno != errno.EEXIST:
+ raise
+ walk(from_dir, make_mirror, None)
+
+
+EMPTY_FILE = '"""generated file, don\'t modify or your data will be lost"""\n'
+
+class MyInstallLib(install_lib.install_lib):
+ """extend install_lib command to handle package __init__.py and
+ include_dirs variable if necessary
+ """
+ def run(self):
+ """overriden from install_lib class"""
+ install_lib.install_lib.run(self)
+ # create Products.__init__.py if needed
+ if subpackage_of:
+ product_init = join(self.install_dir, subpackage_of, '__init__.py')
+ if not exists(product_init):
+ self.announce('creating %s' % product_init)
+ stream = open(product_init, 'w')
+ stream.write(EMPTY_FILE)
+ stream.close()
+ # manually install included directories if any
+ if include_dirs:
+ if subpackage_of:
+ base = join(subpackage_of, modname)
+ else:
+ base = modname
+ for directory in include_dirs:
+ dest = join(self.install_dir, base, directory)
+ export(directory, dest)
+
+def install(**kwargs):
+ """setup entry point"""
+ if subpackage_of:
+ package = subpackage_of + '.' + modname
+ kwargs['package_dir'] = {package : '.'}
+ packages = [package] + get_packages(os.getcwd(), package)
+ else:
+ kwargs['package_dir'] = {modname : '.'}
+ packages = [modname] + get_packages(os.getcwd(), modname)
+ kwargs['packages'] = packages
+ return setup(name = distname,
+ version = version,
+ license =license,
+ description = short_desc,
+ long_description = long_desc,
+ author = author,
+ author_email = author_email,
+ url = web,
+ scripts = ensure_scripts(scripts),
+ data_files=data_files,
+ ext_modules=ext_modules,
+ cmdclass={'install_lib': MyInstallLib},
+ **kwargs
+ )
+
+if __name__ == '__main__' :
+ install()
diff --git a/test/data/SSL1/Connection1.py b/test/data/SSL1/Connection1.py
new file mode 100644
index 00000000..29347e3e
--- /dev/null
+++ b/test/data/SSL1/Connection1.py
@@ -0,0 +1,14 @@
+"""M2Crypto.SSL.Connection
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+RCS_id='$Id: Connection1.py,v 1.1 2005-06-13 20:55:22 syt Exp $'
+
+#Some code deleted here
+
+class Connection:
+
+ """An SSL connection."""
+
+ def __init__(self, ctx, sock=None):
+ print 'init Connection'
diff --git a/test/data/SSL1/__init__.py b/test/data/SSL1/__init__.py
new file mode 100644
index 00000000..dd588804
--- /dev/null
+++ b/test/data/SSL1/__init__.py
@@ -0,0 +1 @@
+from Connection1 import Connection
diff --git a/test/data/__init__.py b/test/data/__init__.py
new file mode 100644
index 00000000..332e2e72
--- /dev/null
+++ b/test/data/__init__.py
@@ -0,0 +1 @@
+__revision__="$Id: __init__.py,v 1.1 2005-06-13 20:55:20 syt Exp $"
diff --git a/test/data/all.py b/test/data/all.py
new file mode 100644
index 00000000..2ba3ae45
--- /dev/null
+++ b/test/data/all.py
@@ -0,0 +1,10 @@
+
+
+name = 'a'
+_bla = 2
+other = 'o'
+class Aaa: pass
+
+def func(): print 'yo'
+
+__all__ = 'Aaa', '_bla', 'name'
diff --git a/test/data/appl/__init__.py b/test/data/appl/__init__.py
new file mode 100644
index 00000000..480109c3
--- /dev/null
+++ b/test/data/appl/__init__.py
@@ -0,0 +1,4 @@
+#
+"""
+Init
+"""
diff --git a/test/data/appl/myConnection.py b/test/data/appl/myConnection.py
new file mode 100644
index 00000000..41fd1565
--- /dev/null
+++ b/test/data/appl/myConnection.py
@@ -0,0 +1,11 @@
+import SSL1
+class MyConnection(SSL1.Connection):
+
+ """An SSL connection."""
+
+ def __init__(self, dummy):
+ print 'MyConnection init'
+
+if __name__ == '__main__':
+ myConnection = MyConnection(' ')
+ raw_input('Press Enter to continue...')
diff --git a/test/data/module.py b/test/data/module.py
new file mode 100644
index 00000000..f6a840bb
--- /dev/null
+++ b/test/data/module.py
@@ -0,0 +1,89 @@
+# -*- coding: Latin-1 -*-
+"""test module for astng
+"""
+
+__revision__ = '$Id: module.py,v 1.2 2005-11-02 11:56:54 syt Exp $'
+
+from logilab.common import modutils, Execute as spawn
+from logilab.common.astutils import *
+import os.path
+
+MY_DICT = {}
+
+
+def global_access(key, val):
+ """function test"""
+ local = 1
+ MY_DICT[key] = val
+ for i in val:
+ if i:
+ del MY_DICT[i]
+ continue
+ else:
+ break
+ else:
+ print '!!!'
+
+class YO:
+ """hehe"""
+ a=1
+ def __init__(self):
+ try:
+ self.yo = 1
+ except ValueError, ex:
+ pass
+ except (NameError, TypeError):
+ raise XXXError()
+ except:
+ raise
+
+#print '*****>',YO.__dict__
+class YOUPI(YO):
+ class_attr = None
+
+ def __init__(self):
+ self.member = None
+
+ def method(self):
+ """method test"""
+ global MY_DICT
+ try:
+ MY_DICT = {}
+ local = None
+ autre = [a for a, b in MY_DICT if b]
+ if b in autre:
+ print 'yo',
+ elif a in autre:
+ print 'hehe'
+ global_access(local, val=autre)
+ finally:
+ return local
+
+ def static_method():
+ """static method test"""
+ assert MY_DICT, '???'
+ static_method = staticmethod(static_method)
+
+ def class_method(cls):
+ """class method test"""
+ exec a in b
+ class_method = classmethod(class_method)
+
+
+def nested_args(a, (b, c, d)):
+ """nested arguments test"""
+ print a, b, c, d
+ while 1:
+ if a:
+ break
+ a += +1
+ else:
+ b += -2
+ if c:
+ d = a and b or c
+ else:
+ c = a and b or d
+ map(lambda x, y: (y, x), a)
+
+redirect = nested_args
+
diff --git a/test/data/module2.py b/test/data/module2.py
new file mode 100644
index 00000000..cb0b9509
--- /dev/null
+++ b/test/data/module2.py
@@ -0,0 +1,90 @@
+from __future__ import generators
+
+from data.module import YO, YOUPI
+import data
+
+class Specialization(YOUPI, YO): pass
+
+class Metaclass(type): pass
+
+class Interface: pass
+
+class MyIFace(Interface): pass
+
+class AnotherIFace(Interface): pass
+
+class MyException(Exception): pass
+class MyError(MyException): pass
+
+class AbstractClass(object):
+
+ def to_override(self, whatever):
+ raise NotImplementedError()
+
+ def return_something(self, param):
+ if param:
+ return 'toto'
+ return
+
+class Concrete0:
+ __implements__ = MyIFace
+class Concrete1:
+ __implements__ = MyIFace, AnotherIFace
+class Concrete2:
+ __implements__ = (MyIFace,
+ AnotherIFace)
+class Concrete23(Concrete1): pass
+
+del YO.member
+
+del YO
+[SYN1, SYN2] = Concrete0, Concrete1
+assert `1`
+b = 1 | 2 & 3 ^ 8
+exec 'c = 3'
+exec 'c = 3' in {}, {}
+
+def raise_string(a=2, *args, **kwargs):
+ raise 'pas glop'
+ raise Exception, 'yo'
+ yield 'coucou'
+
+a = b + 2
+c = b * 2
+c = b / 2
+c = b // 2
+c = b - 2
+c = b % 2
+c = b ** 2
+c = b << 2
+c = b >> 2
+c = ~b
+
+c = not b
+
+d = [c]
+e = d[:]
+e = d[a:b:c]
+
+raise_string(*args, **kwargs)
+
+print >> stream, 'bonjour'
+print >> stream, 'salut',
+
+
+def make_class(any, base=data.module.YO, *args, **kwargs):
+ """check base is correctly resolved to Concrete0"""
+ class Aaaa(base):
+ """dynamic class"""
+ return Aaaa
+
+from os.path import abspath
+
+import os as myos
+
+
+class A:
+ pass
+
+class A(A):
+ pass
diff --git a/test/data/noendingnewline.py b/test/data/noendingnewline.py
new file mode 100644
index 00000000..353ded4e
--- /dev/null
+++ b/test/data/noendingnewline.py
@@ -0,0 +1,38 @@
+
+
+import unittest
+
+
+class TestCase(unittest.TestCase):
+
+ def setUp(self):
+ unittest.TestCase.setUp(self)
+
+
+ def tearDown(self):
+ unittest.TestCase.tearDown(self)
+
+ def testIt(self):
+ self.a = 10
+ self.xxx()
+
+
+ def xxx(self):
+ if False:
+ pass
+ print 'a'
+
+ if False:
+ pass
+ pass
+
+ if False:
+ pass
+ print 'rara'
+
+
+if __name__ == '__main__':
+ print 'test2'
+ unittest.main()
+
+ \ No newline at end of file
diff --git a/test/data/nonregr.py b/test/data/nonregr.py
new file mode 100644
index 00000000..a670e39f
--- /dev/null
+++ b/test/data/nonregr.py
@@ -0,0 +1,57 @@
+from __future__ import generators
+
+try:
+ enumerate = enumerate
+except NameError:
+
+ def enumerate(iterable):
+ """emulates the python2.3 enumerate() function"""
+ i = 0
+ for val in iterable:
+ yield i, val
+ i += 1
+
+def toto(value):
+ for k, v in value:
+ print v.get('yo')
+
+
+import imp
+fp, mpath, desc = imp.find_module('optparse',a)
+s_opt = imp.load_module('std_optparse', fp, mpath, desc)
+
+class OptionParser(s_opt.OptionParser):
+
+ def parse_args(self, args=None, values=None, real_optparse=False):
+ if real_optparse:
+ pass
+## return super(OptionParser, self).parse_args()
+ else:
+ import optcomp
+ optcomp.completion(self)
+
+
+class Aaa(object):
+ """docstring"""
+ def __init__(self):
+ self.__setattr__('a','b')
+ pass
+
+ def one_public(self):
+ """docstring"""
+ pass
+
+ def another_public(self):
+ """docstring"""
+ pass
+
+class Ccc(Aaa):
+ """docstring"""
+
+ class Ddd(Aaa):
+ """docstring"""
+ pass
+
+ class Eee(Ddd):
+ """docstring"""
+ pass
diff --git a/test/data/notall.py b/test/data/notall.py
new file mode 100644
index 00000000..578495e7
--- /dev/null
+++ b/test/data/notall.py
@@ -0,0 +1,9 @@
+
+
+name = 'a'
+_bla = 2
+other = 'o'
+class Aaa: pass
+
+def func(): print 'yo'
+
diff --git a/test/data2/__init__.py b/test/data2/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/test/data2/__init__.py
diff --git a/test/data2/clientmodule_test.py b/test/data2/clientmodule_test.py
new file mode 100644
index 00000000..71279a80
--- /dev/null
+++ b/test/data2/clientmodule_test.py
@@ -0,0 +1,32 @@
+""" docstring for file clientmodule.py """
+from data2.suppliermodule_test import Interface as IFace, DoNothing
+
+class Toto: pass
+
+class Ancestor:
+ """ Ancestor method """
+ __implements__ = (IFace,)
+
+ def __init__(self, value):
+ local_variable = 0
+ self.attr = 'this method shouldn\'t have a docstring'
+ self.__value = value
+
+ def get_value(self):
+ """ nice docstring ;-) """
+ return self.__value
+
+ def set_value(self, value):
+ self.__value = value
+ return 'this method shouldn\'t have a docstring'
+
+class Specialization(Ancestor):
+ TYPE = 'final class'
+ top = 'class'
+
+ def __init__(self, value, _id):
+ Ancestor.__init__(self, value)
+ self._id = _id
+ self.relation = DoNothing()
+ self.toto = Toto()
+
diff --git a/test/data2/suppliermodule_test.py b/test/data2/suppliermodule_test.py
new file mode 100644
index 00000000..ddacb477
--- /dev/null
+++ b/test/data2/suppliermodule_test.py
@@ -0,0 +1,13 @@
+""" file suppliermodule.py """
+
+class NotImplemented(Exception):
+ pass
+
+class Interface:
+ def get_value(self):
+ raise NotImplemented()
+
+ def set_value(self, value):
+ raise NotImplemented()
+
+class DoNothing : pass
diff --git a/test/fulltest.sh b/test/fulltest.sh
new file mode 100755
index 00000000..a6253391
--- /dev/null
+++ b/test/fulltest.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+if [ $@ ] ; then
+ PYVERSIONS=$@
+else
+ PYVERSIONS="2.2 2.3 2.4"
+fi
+
+for ver in $PYVERSIONS; do
+ echo "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
+ echo `python$ver -V`
+ echo "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"
+ python$ver runtests.py
+ echo "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"
+ echo `python$ver -V` -OO
+ python$ver -OO runtests.py
+done \ No newline at end of file
diff --git a/test/regrtest.py b/test/regrtest.py
new file mode 100644
index 00000000..f2982483
--- /dev/null
+++ b/test/regrtest.py
@@ -0,0 +1,41 @@
+__revision__ = '$Id: regrtest.py,v 1.8 2006-01-24 19:52:08 syt Exp $'
+
+import unittest
+
+from logilab.astng import ResolveError, MANAGER as m
+from logilab.astng.builder import ASTNGBuilder, build_module
+
+import sys
+from os.path import abspath
+sys.path.insert(1, abspath('regrtest_data'))
+
+class NonRegressionTC(unittest.TestCase):
+
+## def test_resolve1(self):
+## mod = m.astng_from_module_name('data.nonregr')
+## cls = mod['OptionParser']
+## self.assertRaises(ResolveError, cls.resolve_dotted, cls.basenames[0])
+## #self.assert_(cls is not cls.resolve_dotted(cls.basenames[0]))
+
+ def test_module_path(self):
+ mod = m.astng_from_module_name('import_package_subpackage_module')
+ package = mod.igetattr('package').next()
+ self.failUnlessEqual(package.name, 'package')
+ subpackage = package.igetattr('subpackage').next()
+ self.failUnlessEqual(subpackage.name, 'package.subpackage')
+ module = subpackage.igetattr('module').next()
+ self.failUnlessEqual(module.name, 'package.subpackage.module')
+
+
+ def test_living_property(self):
+ builder = ASTNGBuilder()
+ builder._done = {}
+ builder._module = sys.modules[__name__]
+ builder.object_build(build_module('module_name', ''), Whatever)
+
+
+class Whatever(object):
+ a = property(lambda x: x, lambda x: x)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/regrtest_data/descriptor_crash.py b/test/regrtest_data/descriptor_crash.py
new file mode 100644
index 00000000..9cf30124
--- /dev/null
+++ b/test/regrtest_data/descriptor_crash.py
@@ -0,0 +1,12 @@
+# -*- coding: iso-8859-1 -*-
+
+import urllib
+
+class Page(object):
+ _urlOpen = staticmethod(urllib.urlopen)
+
+ def getPage(self, url):
+ handle = self._urlOpen(url)
+ data = handle.read()
+ handle.close()
+ return data
diff --git a/test/regrtest_data/import_package_subpackage_module.py b/test/regrtest_data/import_package_subpackage_module.py
new file mode 100644
index 00000000..8b354c6d
--- /dev/null
+++ b/test/regrtest_data/import_package_subpackage_module.py
@@ -0,0 +1,49 @@
+# pylint: disable-msg=I0011,C0301,W0611
+"""I found some of my scripts trigger off an AttributeError in pylint
+0.8.1 (with common 0.12.0 and astng 0.13.1).
+
+Traceback (most recent call last):
+ File "/usr/bin/pylint", line 4, in ?
+ lint.Run(sys.argv[1:])
+ File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 729, in __init__
+ linter.check(args)
+ File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 412, in check
+ self.check_file(filepath, modname, checkers)
+ File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 426, in check_file
+ astng = self._check_file(filepath, modname, checkers)
+ File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 450, in _check_file
+ self.check_astng_module(astng, checkers)
+ File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 494, in check_astng_module
+ self.astng_events(astng, [checker for checker in checkers
+ File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astng_events
+ self.astng_events(child, checkers, _reversed_checkers)
+ File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astng_events
+ self.astng_events(child, checkers, _reversed_checkers)
+ File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 508, in astng_events
+ checker.visit(astng)
+ File "/usr/lib/python2.4/site-packages/logilab/astng/utils.py", line 84, in visit
+ method(node)
+ File "/usr/lib/python2.4/site-packages/pylint/checkers/variables.py", line 295, in visit_import
+ self._check_module_attrs(node, module, name_parts[1:])
+ File "/usr/lib/python2.4/site-packages/pylint/checkers/variables.py", line 357, in _check_module_attrs
+ self.add_message('E0611', args=(name, module.name),
+AttributeError: Import instance has no attribute 'name'
+
+
+You can reproduce it by:
+(1) create package structure like the following:
+
+package/
+ __init__.py
+ subpackage/
+ __init__.py
+ module.py
+
+(2) in package/__init__.py write:
+
+import subpackage
+
+(3) run pylint with a script importing package.subpackage.module.
+"""
+__revision__ = '$Id: import_package_subpackage_module.py,v 1.1 2005-11-10 15:59:32 syt Exp $'
+import package.subpackage.module
diff --git a/test/regrtest_data/package/__init__.py b/test/regrtest_data/package/__init__.py
new file mode 100644
index 00000000..aa5227bd
--- /dev/null
+++ b/test/regrtest_data/package/__init__.py
@@ -0,0 +1,5 @@
+# pylint: disable-msg=R0903
+"""package's __init__ file"""
+
+
+import subpackage
diff --git a/test/regrtest_data/package/subpackage/__init__.py b/test/regrtest_data/package/subpackage/__init__.py
new file mode 100644
index 00000000..dc4782e6
--- /dev/null
+++ b/test/regrtest_data/package/subpackage/__init__.py
@@ -0,0 +1 @@
+"""package.subpackage"""
diff --git a/test/regrtest_data/package/subpackage/module.py b/test/regrtest_data/package/subpackage/module.py
new file mode 100644
index 00000000..4b7244ba
--- /dev/null
+++ b/test/regrtest_data/package/subpackage/module.py
@@ -0,0 +1 @@
+"""package.subpackage.module"""
diff --git a/test/runtests.py b/test/runtests.py
new file mode 100644
index 00000000..67d66367
--- /dev/null
+++ b/test/runtests.py
@@ -0,0 +1,5 @@
+from logilab.common.testlib import main
+
+if __name__ == '__main__':
+ import sys, os
+ main(os.path.dirname(sys.argv[0]) or '.')
diff --git a/test/unittest_builder.py b/test/unittest_builder.py
new file mode 100644
index 00000000..93887e70
--- /dev/null
+++ b/test/unittest_builder.py
@@ -0,0 +1,314 @@
+# 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""tests for the astng builder module
+
+Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+__revision__ = "$Id: unittest_builder.py,v 1.20 2006-04-19 14:31:41 syt Exp $"
+
+import unittest
+from os.path import join, abspath
+
+from logilab.common.testlib import TestCase, unittest_main
+
+from logilab.astng import builder, nodes, Module
+
+import data
+from data import module as test_module
+
+
+class BuilderTC(TestCase):
+
+ def setUp(self):
+ self.builder = builder.ASTNGBuilder()
+
+ def test_border_cases(self):
+ """check that a file with no trailing new line is parseable"""
+ self.builder.file_build('data/noendingnewline.py', 'data.noendingnewline')
+ self.assertRaises(builder.ASTNGBuildingException,
+ self.builder.file_build, 'data/inexistant.py', 'whatever')
+
+ def test_inspect_build(self):
+ """test astng tree build from a living object"""
+ import __builtin__
+ builtin_astng = self.builder.inspect_build(__builtin__)
+ fclass = builtin_astng['file']
+ self.assert_('name' in fclass)
+ self.assert_('mode' in fclass)
+ self.assert_('read' in fclass)
+ self.assert_(fclass.newstyle)
+ self.assert_(isinstance(fclass['read'], nodes.Function))
+ self.assert_(isinstance(fclass['__doc__'], nodes.Const), fclass['__doc__'])
+ # check builtin function has argnames == None
+ dclass = builtin_astng['dict']
+ self.assertEquals(dclass['has_key'].argnames, None)
+ # just check type and object are there
+ builtin_astng.getattr('type')
+ builtin_astng.getattr('object')
+ # check property has __init__
+ pclass = builtin_astng['property']
+ self.assert_('__init__' in pclass)
+ #
+ import time
+ time_astng = self.builder.module_build(time)
+ self.assert_(time_astng)
+ #
+ unittest_astng = self.builder.inspect_build(unittest)
+ self.failUnless(isinstance(builtin_astng['None'], nodes.Const), builtin_astng['None'])
+ self.failUnless(isinstance(builtin_astng['Exception'], nodes.From), builtin_astng['Exception'])
+ self.failUnless(isinstance(builtin_astng['NotImplementedError'], nodes.From))
+
+
+ def test_inspect_build2(self):
+ """test astng tree build from a living object"""
+ try:
+ from mx import DateTime
+ except ImportError:
+ self.skip('test skipped: mxDateTime is not available')
+ else:
+ dt_astng = self.builder.inspect_build(DateTime)
+ dt_astng.getattr('DateTime')
+ # this one is failing since DateTimeType.__module__ = 'builtins' !
+ #dt_astng.getattr('DateTimeType')
+
+ def test_inspect_build_instance(self):
+ """test astng tree build from a living object"""
+ import exceptions
+ builtin_astng = self.builder.inspect_build(exceptions)
+ fclass = builtin_astng['OSError']
+ self.assert_('errno' in fclass.instance_attrs)
+ self.assert_('strerror' in fclass.instance_attrs)
+ self.assert_('filename' in fclass.instance_attrs)
+
+ def test_package_name(self):
+ """test base properties and method of a astng module"""
+ datap = self.builder.file_build('data/__init__.py', 'data')
+ self.assertEquals(datap.name, 'data')
+ self.assertEquals(datap.package, 1)
+ datap = self.builder.file_build('data/__init__.py', 'data.__init__')
+ self.assertEquals(datap.name, 'data')
+ self.assertEquals(datap.package, 1)
+
+ def test_object(self):
+ obj_astng = self.builder.inspect_build(object)
+ self.failUnless('__setattr__' in obj_astng)
+
+ def test_newstyle_detection(self):
+ data = '''
+class A:
+ "old style"
+
+class B(A):
+ "old style"
+
+class C(object):
+ "new style"
+
+class D(C):
+ "new style"
+
+__metaclass__ = type
+
+class E(A):
+ "old style"
+
+class F:
+ "new style"
+'''
+ mod_astng = self.builder.string_build(data, __name__, __file__)
+ self.failIf(mod_astng['A'].newstyle)
+ self.failIf(mod_astng['B'].newstyle)
+ self.failUnless(mod_astng['C'].newstyle)
+ self.failUnless(mod_astng['D'].newstyle)
+ self.failIf(mod_astng['E'].newstyle)
+ self.failUnless(mod_astng['F'].newstyle)
+
+ def test_globals(self):
+ data = '''
+CSTE = 1
+
+def update_global():
+ global CSTE
+ CSTE += 1
+
+def global_no_effect():
+ global CSTE2
+ print CSTE
+'''
+ astng = self.builder.string_build(data, __name__, __file__)
+ self.failUnlessEqual(len(astng.getattr('CSTE')), 2)
+ self.failUnlessEqual(astng.getattr('CSTE')[0].source_line(), 2)
+ self.failUnlessEqual(astng.getattr('CSTE')[1].source_line(), 6)
+ self.assertRaises(nodes.NotFoundError,
+ astng.getattr, 'CSTE2')
+ self.assertRaises(nodes.InferenceError,
+ astng['global_no_effect'].ilookup('CSTE2').next)
+
+ def test_socket_build(self):
+ import socket
+ astng = self.builder.module_build(socket)
+ for fclass in astng.igetattr('socket'):
+ print fclass.root().name, fclass.name, fclass.lineno
+ self.assert_('connect' in fclass)
+ self.assert_('send' in fclass)
+ self.assert_('close' in fclass)
+
+
+class FileBuildTC(TestCase):
+
+ def setUp(self):
+ abuilder = builder.ASTNGBuilder()
+ self.module = abuilder.file_build('data/module.py', 'data.module')
+
+ def test_module_base_props(self):
+ """test base properties and method of a astng module"""
+ module = self.module
+ self.assertEquals(module.name, 'data.module')
+ self.assertEquals(module.doc, "test module for astng\n")
+ self.assertEquals(module.source_line(), 0)
+ self.assertEquals(module.parent, None)
+ self.assertEquals(module.frame(), module)
+ self.assertEquals(module.root(), module)
+ self.assertEquals(module.file, join(abspath(data.__path__[0]), 'module.py'))
+ self.assertEquals(module.pure_python, 1)
+ self.assertEquals(module.package, 0)
+ self.assert_(not module.is_statement())
+ self.assertEquals(module.statement(), module)
+ self.assertEquals(module.node.statement(), module)
+
+ def test_module_locals(self):
+ """test the 'locals' dictionary of a astng module"""
+ module = self.module
+ _locals = module.locals
+ self.assertEquals(len(_locals), 17)
+ self.assert_(_locals is module.globals)
+ keys = _locals.keys()
+ keys.sort()
+ self.assertEquals(keys, ['MY_DICT', 'YO', 'YOUPI', '__dict__',
+ '__doc__', '__file__', '__name__', '__revision__',
+ 'clean', 'cvrtr', 'debuild', 'global_access',
+ 'modutils', 'nested_args', 'os', 'redirect',
+ 'spawn'])
+
+ def test_function_base_props(self):
+ """test base properties and method of a astng function"""
+ module = self.module
+ function = module['global_access']
+ self.assertEquals(function.name, 'global_access')
+ self.assertEquals(function.doc, 'function test')
+ self.assertEquals(function.source_line(), 14)
+ self.assert_(function.parent)
+ self.assertEquals(function.frame(), function)
+ self.assertEquals(function.parent.frame(), module)
+ self.assertEquals(function.root(), module)
+ self.assertEquals(function.argnames, ['key', 'val'])
+ self.assertEquals(function.type, 'function')
+
+ def test_function_locals(self):
+ """test the 'locals' dictionary of a astng function"""
+ _locals = self.module['global_access'].locals
+ self.assertEquals(len(_locals), 4)
+ keys = _locals.keys()
+ keys.sort()
+ self.assertEquals(keys, ['i', 'key', 'local', 'val'])
+
+ def test_class_base_props(self):
+ """test base properties and method of a astng class"""
+ module = self.module
+ klass = module['YO']
+ self.assertEquals(klass.name, 'YO')
+ self.assertEquals(klass.doc, 'hehe')
+ self.assertEquals(klass.source_line(), 27)
+ self.assert_(klass.parent)
+ self.assertEquals(klass.frame(), klass)
+ self.assertEquals(klass.parent.frame(), module)
+ self.assertEquals(klass.root(), module)
+ self.assertEquals(klass.basenames, [])
+ self.assertEquals(klass.newstyle, False)
+ self.failUnless(isinstance(klass['__doc__'], nodes.Const))
+
+ def test_class_locals(self):
+ """test the 'locals' dictionary of a astng class"""
+ module = self.module
+ klass1 = module['YO']
+ klass2 = module['YOUPI']
+ locals1 = klass1.locals
+ locals2 = klass2.locals
+ keys = locals1.keys()
+ keys.sort()
+ self.assertEquals(keys, ['__dict__', '__doc__', '__init__', '__module__', '__name__',
+ 'a'])
+ keys = locals2.keys()
+ keys.sort()
+ self.assertEquals(keys, ['__dict__', '__doc__', '__init__', '__module__', '__name__',
+ 'class_attr',
+ 'class_method', 'method', 'static_method'])
+
+ def test_class_instance_attrs(self):
+ module = self.module
+ klass1 = module['YO']
+ klass2 = module['YOUPI']
+ self.assertEquals(klass1.instance_attrs.keys(), ['yo'])
+ self.assertEquals(klass2.instance_attrs.keys(), ['member'])
+
+ def test_class_basenames(self):
+ module = self.module
+ klass1 = module['YO']
+ klass2 = module['YOUPI']
+ self.assertEquals(klass1.basenames, [])
+ self.assertEquals(klass2.basenames, ['YO'])
+
+ def test_method_base_props(self):
+ """test base properties and method of a astng method"""
+ klass2 = self.module['YOUPI']
+ # "normal" method
+ method = klass2['method']
+ self.assertEquals(method.name, 'method')
+ self.assertEquals(method.argnames, ['self'])
+ self.assertEquals(method.doc, 'method test')
+ self.assertEquals(method.source_line(), 47)
+ self.assertEquals(method.type, 'method')
+ # class method
+ method = klass2['class_method']
+ self.assertEquals(method.argnames, ['cls'])
+ self.assertEquals(method.type, 'classmethod')
+ # static method
+ method = klass2['static_method']
+ self.assertEquals(method.argnames, [])
+ self.assertEquals(method.type, 'staticmethod')
+
+ def test_method_locals(self):
+ """test the 'locals' dictionary of a astng method"""
+ klass2 = self.module['YOUPI']
+ method = klass2['method']
+ _locals = method.locals
+ self.assertEquals(len(_locals), 5)
+ keys = _locals.keys()
+ keys.sort()
+ self.assertEquals(keys, ['a', 'autre', 'b', 'local', 'self'])
+
+
+class ModuleBuildTC(FileBuildTC):
+
+ def setUp(self):
+ abuilder = builder.ASTNGBuilder()
+ self.module = abuilder.module_build(test_module)
+
+
+__all__ = ('BuilderModuleBuildTC', 'BuilderFileBuildTC', 'BuilderTC')
+
+if __name__ == '__main__':
+ # unittest.main()
+ unittest_main()
diff --git a/test/unittest_inference.py b/test/unittest_inference.py
new file mode 100644
index 00000000..39ef21cf
--- /dev/null
+++ b/test/unittest_inference.py
@@ -0,0 +1,557 @@
+# 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""tests for the astng inference capabilities
+
+Copyright (c) 2005-2006 LOGILAB S.A. (Paris, FRANCE).
+http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+__revision__ = "$Id: unittest_inference.py,v 1.20 2006-04-19 15:17:41 syt Exp $"
+
+from os.path import join, abspath
+
+from logilab.astng import builder, nodes, inference, utils, YES, Instance
+from logilab.common.testlib import TestCase, unittest_main
+
+def get_name_node(start_from, name, index=0):
+ return [n for n in start_from.nodes_of_class(nodes.Name) if n.name == name][index]
+
+def get_node_of_class(start_from, klass):
+ return start_from.nodes_of_class(klass).next()
+
+builder = builder.ASTNGBuilder()
+
+class InferenceUtilsTC(TestCase):
+
+ def test_path_wrapper(self):
+ infer_default = inference.path_wrapper(inference.infer_default)
+ infer_end = inference.path_wrapper(inference.infer_end)
+ self.failUnlessRaises(inference.InferenceError,
+ infer_default(1).next)
+ self.failUnlessEqual(infer_end(1).next(), 1)
+
+class InferenceTC(TestCase):
+
+ DATA = '''
+import exceptions
+
+class C(object):
+ "new style"
+ attr = 4
+
+ def meth1(self, arg1, optarg=0):
+ var = object()
+ print "yo", arg1, optarg
+ self.iattr = "hop"
+ return var
+
+ def meth2(self):
+ self.meth1(*self.meth3)
+
+ def meth3(self, d=attr):
+ b = self.attr
+ c = self.iattr
+ return b, c
+
+ex = exceptions.Exception("msg")
+v = C().meth1(1)
+a, b, c = ex, 1, "bonjour"
+[d, e, f] = [ex, 1.0, ("bonjour", v)]
+g, h = f
+i, (j, k) = u"glup", f
+
+a, b= b, a # Gasp !
+'''
+
+ def setUp(self):
+ self.astng = builder.string_build(self.DATA, __name__, __file__)
+
+ def test_module_inference(self):
+ infered = self.astng.infer()
+ obj = infered.next()
+ self.failUnlessEqual(obj.name, __name__)
+ self.failUnlessEqual(obj.root().name, __name__)
+ self.failUnlessRaises(StopIteration, infered.next)
+
+ def test_class_inference(self):
+ infered = self.astng['C'].infer()
+ obj = infered.next()
+ self.failUnlessEqual(obj.name, 'C')
+ self.failUnlessEqual(obj.root().name, __name__)
+ self.failUnlessRaises(StopIteration, infered.next)
+
+ def test_function_inference(self):
+ infered = self.astng['C']['meth1'].infer()
+ obj = infered.next()
+ self.failUnlessEqual(obj.name, 'meth1')
+ self.failUnlessEqual(obj.root().name, __name__)
+ self.failUnlessRaises(StopIteration, infered.next)
+
+ def test_name_inference(self):
+ infered = self.astng['C']['meth1']['var'].infer()
+ var = infered.next()
+ self.failUnlessEqual(var.name, 'object')
+ self.failUnlessEqual(var.root().name, '__builtin__')
+ self.failUnlessRaises(StopIteration, infered.next)
+
+ def test_tupleassign_name_inference(self):
+ infered = self.astng['a'].infer()
+ exc = infered.next()
+ self.failUnless(isinstance(exc, inference.Instance))
+ self.failUnlessEqual(exc.name, 'Exception')
+ self.failUnlessEqual(exc.root().name, 'exceptions')
+ self.failUnlessRaises(StopIteration, infered.next)
+ infered = self.astng['b'].infer()
+ const = infered.next()
+ self.failUnless(isinstance(const, nodes.Const))
+ self.failUnlessEqual(const.value, 1)
+ self.failUnlessRaises(StopIteration, infered.next)
+ infered = self.astng['c'].infer()
+ const = infered.next()
+ self.failUnless(isinstance(const, nodes.Const))
+ self.failUnlessEqual(const.value, "bonjour")
+ self.failUnlessRaises(StopIteration, infered.next)
+
+ def test_listassign_name_inference(self):
+ infered = self.astng['d'].infer()
+ exc = infered.next()
+ self.failUnless(isinstance(exc, inference.Instance))
+ self.failUnlessEqual(exc.name, 'Exception')
+ self.failUnlessEqual(exc.root().name, 'exceptions')
+ self.failUnlessRaises(StopIteration, infered.next)
+ infered = self.astng['e'].infer()
+ const = infered.next()
+ self.failUnless(isinstance(const, nodes.Const))
+ self.failUnlessEqual(const.value, 1.0)
+ self.failUnlessRaises(StopIteration, infered.next)
+ infered = self.astng['f'].infer()
+ const = infered.next()
+ self.failUnless(isinstance(const, nodes.Tuple))
+ self.failUnlessRaises(StopIteration, infered.next)
+
+ def test_advanced_tupleassign_name_inference1(self):
+ infered = self.astng['g'].infer()
+ const = infered.next()
+ self.failUnless(isinstance(const, nodes.Const))
+ self.failUnlessEqual(const.value, "bonjour")
+ self.failUnlessRaises(StopIteration, infered.next)
+ infered = self.astng['h'].infer()
+ var = infered.next()
+ self.failUnlessEqual(var.name, 'object')
+ self.failUnlessEqual(var.root().name, '__builtin__')
+ self.failUnlessRaises(StopIteration, infered.next)
+
+ def test_advanced_tupleassign_name_inference2(self):
+ infered = self.astng['i'].infer()
+ const = infered.next()
+ self.failUnless(isinstance(const, nodes.Const))
+ self.failUnlessEqual(const.value, u"glup")
+ self.failUnlessRaises(StopIteration, infered.next)
+ infered = self.astng['j'].infer()
+ const = infered.next()
+ self.failUnless(isinstance(const, nodes.Const))
+ self.failUnlessEqual(const.value, "bonjour")
+ self.failUnlessRaises(StopIteration, infered.next)
+ infered = self.astng['k'].infer()
+ var = infered.next()
+ self.failUnlessEqual(var.name, 'object')
+ self.failUnlessEqual(var.root().name, '__builtin__')
+ self.failUnlessRaises(StopIteration, infered.next)
+
+ def test_swap_assign_inference(self):
+ infered = self.astng.locals['a'][1].infer()
+ const = infered.next()
+ self.failUnless(isinstance(const, nodes.Const))
+ self.failUnlessEqual(const.value, 1)
+ self.failUnlessRaises(StopIteration, infered.next)
+ infered = self.astng.locals['b'][1].infer()
+ exc = infered.next()
+ self.failUnless(isinstance(exc, inference.Instance))
+ self.failUnlessEqual(exc.name, 'Exception')
+ self.failUnlessEqual(exc.root().name, 'exceptions')
+ self.failUnlessRaises(StopIteration, infered.next)
+
+ def test_getattr_inference1(self):
+ infered = self.astng['ex'].infer(path=[])
+ exc = infered.next()
+ self.failUnless(isinstance(exc, inference.Instance))
+ self.failUnlessEqual(exc.name, 'Exception')
+ self.failUnlessEqual(exc.root().name, 'exceptions')
+ self.failUnlessRaises(StopIteration, infered.next)
+
+ def test_getattr_inference2(self):
+ infered = get_node_of_class(self.astng['C']['meth2'], nodes.Getattr).infer()
+ meth1 = infered.next()
+ self.failUnlessEqual(meth1.name, 'meth1')
+ self.failUnlessEqual(meth1.root().name, __name__)
+ self.failUnlessRaises(StopIteration, infered.next)
+
+ def test_getattr_inference3(self):
+ infered = self.astng['C']['meth3']['b'].infer()
+ const = infered.next()
+ self.failUnless(isinstance(const, nodes.Const))
+ self.failUnlessEqual(const.value, 4)
+ self.failUnlessRaises(StopIteration, infered.next)
+
+ def test_getattr_inference4(self):
+ infered = self.astng['C']['meth3']['c'].infer()
+ const = infered.next()
+ self.failUnless(isinstance(const, nodes.Const))
+ self.failUnlessEqual(const.value, "hop")
+ self.failUnlessRaises(StopIteration, infered.next)
+
+ def test_callfunc_inference(self):
+ infered = self.astng['v'].infer()
+ meth1 = infered.next()
+ self.failUnless(isinstance(meth1, inference.Instance))
+ self.failUnlessEqual(meth1.name, 'object')
+ self.failUnlessEqual(meth1.root().name, '__builtin__')
+ self.failUnlessRaises(StopIteration, infered.next)
+
+ def test_args_default_inference1(self):
+ optarg = get_name_node(self.astng['C']['meth1'], 'optarg')
+ infered = optarg.infer()
+ obj1 = infered.next()
+ self.failUnless(isinstance(obj1, nodes.Const))
+ self.failUnlessEqual(obj1.value, 0)
+ obj1 = infered.next()
+ self.failUnless(obj1 is YES)
+ self.failUnlessRaises(StopIteration, infered.next)
+
+ def test_args_default_inference2(self):
+ infered = self.astng['C']['meth3']['d'].infer(name='d')
+ obj1 = infered.next()
+ self.failUnless(isinstance(obj1, nodes.Const))
+ self.failUnlessEqual(obj1.value, 4)
+ obj1 = infered.next()
+ self.failUnless(obj1 is YES)
+ self.failUnlessRaises(StopIteration, infered.next)
+
+ def test_inference_restrictions(self):
+ infered = get_name_node(self.astng['C']['meth1'], 'arg1').infer()
+ obj1 = infered.next()
+ self.failUnless(obj1 is YES)
+ self.failUnlessRaises(StopIteration, infered.next)
+
+ def test_del(self):
+ data = '''
+del undefined_attr
+ '''
+ astng = builder.string_build(data, __name__, __file__)
+ self.failUnlessRaises(inference.InferenceError,
+ astng.node.getChildNodes()[0].infer().next)
+
+ def test_ancestors_inference(self):
+ data = '''
+class A:
+ pass
+
+class A(A):
+ pass
+ '''
+ astng = builder.string_build(data, __name__, __file__)
+ a1 = astng.locals['A'][0]
+ a2 = astng.locals['A'][1]
+ a2_ancestors = list(a2.ancestors())
+ self.failUnlessEqual(len(a2_ancestors), 1)
+ self.failUnless(a2_ancestors[0] is a1)
+
+ def test_ancestors_inference2(self):
+ data = '''
+class A:
+ pass
+
+class B(A): pass
+
+class A(B):
+ pass
+ '''
+ astng = builder.string_build(data, __name__, __file__)
+ a1 = astng.locals['A'][0]
+ a2 = astng.locals['A'][1]
+ a2_ancestors = list(a2.ancestors())
+ self.failUnlessEqual(len(a2_ancestors), 2)
+ self.failUnless(a2_ancestors[0] is astng.locals['B'][0])
+ self.failUnless(a2_ancestors[1] is a1, a2_ancestors[1])
+
+
+ def test_f_arg_f(self):
+ data = '''
+def f(f=1):
+ return f
+
+a = f()
+ '''
+ astng = builder.string_build(data, __name__, __file__)
+ a = astng['a']
+ a_infer = a.infer()
+ self.failUnlessEqual(a_infer.next().value, 1)
+ self.failUnlessEqual(a_infer.next(), YES)
+ self.failUnlessRaises(StopIteration, a_infer.next)
+
+ def test_exc_ancestors(self):
+ data = '''
+def f():
+ raise NotImplementedError
+ '''
+ astng = builder.string_build(data, __name__, __file__)
+ names = astng.nodes_of_class(nodes.Name)
+ nie = names.next().infer().next()
+ self.failUnless(isinstance(nie, nodes.Class))
+ nie_ancestors = [c.name for c in nie.ancestors()]
+ self.failUnlessEqual(nie_ancestors, ['RuntimeError', 'StandardError', 'Exception'])
+
+ def test_except_inference(self):
+ data = '''
+try:
+ print hop
+except NameError, ex:
+ ex1 = ex
+except Exception, ex:
+ ex2 = ex
+ raise
+ '''
+ astng = builder.string_build(data, __name__, __file__)
+ ex1 = astng['ex1']
+ ex1_infer = ex1.infer()
+ infered = list(ex1.infer())
+ #print 'EX1:', ex1
+ #print 'INFEREND', infered
+ ex1 = ex1_infer.next()
+ self.failUnless(isinstance(ex1, inference.Instance))
+ self.failUnlessEqual(ex1.name, 'NameError')
+ self.failUnlessRaises(StopIteration, ex1_infer.next)
+ ex2 = astng['ex2']
+ ex2_infer = ex2.infer()
+ ex2 = ex2_infer.next()
+ self.failUnless(isinstance(ex2, inference.Instance))
+ self.failUnlessEqual(ex2.name, 'Exception')
+ self.failUnlessRaises(StopIteration, ex2_infer.next)
+
+ def test_del(self):
+ data = '''
+a = 1
+b = a
+del a
+c = a
+a = 2
+d = a
+ '''
+ astng = builder.string_build(data, __name__, __file__)
+ n = astng['b']
+ n_infer = n.infer()
+ infered = n_infer.next()
+ self.failUnless(isinstance(infered, nodes.Const))
+ self.failUnlessEqual(infered.value, 1)
+ self.failUnlessRaises(StopIteration, n_infer.next)
+ n = astng['c']
+ n_infer = n.infer()
+ self.failUnlessRaises(inference.InferenceError, n_infer.next)
+ n = astng['d']
+ n_infer = n.infer()
+ infered = n_infer.next()
+ self.failUnless(isinstance(infered, nodes.Const))
+ self.failUnlessEqual(infered.value, 2)
+ self.failUnlessRaises(StopIteration, n_infer.next)
+
+ def test_builtin_types(self):
+ data = '''
+l = [1]
+t = (2,)
+d = {}
+s = ''
+u = u''
+ '''
+ astng = builder.string_build(data, __name__, __file__)
+ n = astng['l']
+ infered = n.infer().next()
+ self.failUnless(isinstance(infered, nodes.List))
+ self.failUnless(isinstance(infered, inference.Instance))
+ self.failUnlessEqual(infered.getitem(0).value, 1)
+ self.failUnless(isinstance(infered._proxied, nodes.Class))
+ self.failUnlessEqual(infered._proxied.name, 'list')
+ self.failUnless('append' in infered._proxied.locals)
+ n = astng['t']
+ infered = n.infer().next()
+ self.failUnless(isinstance(infered, nodes.Tuple))
+ self.failUnless(isinstance(infered, inference.Instance))
+ self.failUnlessEqual(infered.getitem(0).value, 2)
+ self.failUnless(isinstance(infered._proxied, nodes.Class))
+ self.failUnlessEqual(infered._proxied.name, 'tuple')
+ n = astng['d']
+ infered = n.infer().next()
+ self.failUnless(isinstance(infered, nodes.Dict))
+ self.failUnless(isinstance(infered, inference.Instance))
+ self.failUnless(isinstance(infered._proxied, nodes.Class))
+ self.failUnlessEqual(infered._proxied.name, 'dict')
+ self.failUnless('get' in infered._proxied.locals)
+ n = astng['s']
+ infered = n.infer().next()
+ self.failUnless(isinstance(infered, nodes.Const))
+ self.failUnless(isinstance(infered, inference.Instance))
+ self.failUnlessEqual(infered.name, 'str')
+ self.failUnless('lower' in infered._proxied.locals)
+ n = astng['u']
+ infered = n.infer().next()
+ self.failUnless(isinstance(infered, nodes.Const))
+ self.failUnless(isinstance(infered, inference.Instance))
+ self.failUnlessEqual(infered.name, 'unicode')
+ self.failUnless('lower' in infered._proxied.locals)
+
+ def test_descriptor_are_callable(self):
+ data = '''
+class A:
+ statm = staticmethod(open)
+ clsm = classmethod('whatever')
+ '''
+ astng = builder.string_build(data, __name__, __file__)
+ statm = astng['A'].igetattr('statm').next()
+ self.failUnless(statm.callable())
+ clsm = astng['A'].igetattr('clsm').next()
+ self.failUnless(clsm.callable())
+
+ def test_bt_ancestor_crash(self):
+ data = '''
+class Warning(Warning):
+ pass
+ '''
+ astng = builder.string_build(data, __name__, __file__)
+ w = astng['Warning']
+ ancestors = w.ancestors()
+ ancestor = ancestors.next()
+ self.failUnlessEqual(ancestor.name, 'Warning')
+ self.failUnlessEqual(ancestor.root().name, 'exceptions')
+ ancestor = ancestors.next()
+ self.failUnlessEqual(ancestor.name, 'Exception')
+ self.failUnlessEqual(ancestor.root().name, 'exceptions')
+ self.failUnlessRaises(StopIteration, ancestors.next)
+
+ def test_qqch(self):
+ data = '''
+from logilab.common.modutils import load_module_from_name
+xxx = load_module_from_name('__pkginfo__')
+ '''
+ astng = builder.string_build(data, __name__, __file__)
+ xxx = astng['xxx']
+ infered = list(xxx.infer())
+ self.failUnlessEqual([n.__class__ for n in infered],
+ [nodes.Const, YES.__class__, YES.__class__])
+
+ def test_method_argument(self):
+ data = '''
+class ErudiEntitySchema:
+ """a entity has a type, a set of subject and or object relations"""
+ def __init__(self, e_type, **kwargs):
+ kwargs['e_type'] = e_type.capitalize().encode()
+
+ def meth(self, e_type, *args, **kwargs):
+ kwargs['e_type'] = e_type.capitalize().encode()
+ print args
+ '''
+ astng = builder.string_build(data, __name__, __file__)
+ arg = get_name_node(astng['ErudiEntitySchema']['__init__'], 'e_type')
+ self.failUnlessEqual([n.__class__ for n in arg.infer()],
+ [YES.__class__])
+ arg = get_name_node(astng['ErudiEntitySchema']['__init__'], 'kwargs')
+ self.failUnlessEqual([n.__class__ for n in arg.infer()],
+ [nodes.Dict])
+ arg = get_name_node(astng['ErudiEntitySchema']['meth'], 'e_type')
+ self.failUnlessEqual([n.__class__ for n in arg.infer()],
+ [YES.__class__])
+ arg = get_name_node(astng['ErudiEntitySchema']['meth'], 'args')
+ self.failUnlessEqual([n.__class__ for n in arg.infer()],
+ [nodes.Tuple])
+ arg = get_name_node(astng['ErudiEntitySchema']['meth'], 'kwargs')
+ self.failUnlessEqual([n.__class__ for n in arg.infer()],
+ [nodes.Dict])
+
+
+ def test_tuple_then_list(self):
+ data = '''
+def test_view(rql, vid, tags=()):
+ tags = list(tags)
+ tags.append(vid)
+ '''
+ astng = builder.string_build(data, __name__, __file__)
+ name = get_name_node(astng['test_view'], 'tags', -1)
+ it = name.infer()
+ tags = it.next()
+ self.failUnlessEqual(tags.__class__, Instance)
+ self.failUnlessEqual(tags._proxied.name, 'list')
+ self.failUnlessRaises(StopIteration, it.next)
+
+
+
+ def test_mulassign_inference(self):
+ data = '''
+
+def first_word(line):
+ """Return the first word of a line"""
+
+ return line.split()[0]
+
+def last_word(line):
+ """Return last word of a line"""
+
+ return line.split()[-1]
+
+def process_line(word_pos):
+ """Silly function: returns (ok, callable) based on argument.
+
+ For test purpose only.
+ """
+
+ if word_pos > 0:
+ return (True, first_word)
+ elif word_pos < 0:
+ return (True, last_word)
+ else:
+ return (False, None)
+
+if __name__ == '__main__':
+
+ line_number = 0
+ for a_line in file('test_callable.py'):
+ tupletest = process_line(line_number)
+ (ok, fct) = process_line(line_number)
+ if ok:
+ fct(a_line)
+'''
+ astng = builder.string_build(data, __name__, __file__)
+ self.failUnlessEqual(len(list(astng['process_line'].infer_call_result(None))),
+ 3)
+ self.failUnlessEqual(len(list(astng['tupletest'].infer())),
+ 3)
+ self.failUnlessEqual([str(infered)
+ for infered in astng['fct'].infer()],
+ ['Function(first_word)', 'Function(last_word)', 'Const(None)'])
+
+ def test_float_complex_ambiguity(self):
+ data = '''
+def no_conjugate_member(magic_flag):
+ """should not raise E1101 on something.conjugate"""
+ if magic_flag:
+ something = 1.0
+ else:
+ something = 1.0j
+ if isinstance(something, float):
+ return something
+ return something.conjugate()
+ '''
+ astng = builder.string_build(data, __name__, __file__)
+ self.failUnlessEqual([i.value for i in astng['no_conjugate_member'].ilookup('something')],
+ [1.0, 1.0j])
+
+if __name__ == '__main__':
+ from logilab.common.testlib import unittest_main
+ unittest_main()
diff --git a/test/unittest_inspector.py b/test/unittest_inspector.py
new file mode 100644
index 00000000..7556a082
--- /dev/null
+++ b/test/unittest_inspector.py
@@ -0,0 +1,88 @@
+# Copyright (c) 2000-2002 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""
+unittest for the visitors.diadefs module
+"""
+
+__revision__ = "$Id: unittest_inspector.py,v 1.4 2006-01-26 00:12:59 syt Exp $"
+
+import unittest
+import sys
+
+from logilab import astng
+from logilab.astng import ASTNGManager, nodes, inspector
+
+def astng_wrapper(func, modname):
+ return func(modname)
+
+
+class LinkerTC(unittest.TestCase):
+
+ def setUp(self):
+ self.project = ASTNGManager().project_from_files(['data2'], astng_wrapper)
+ self.linker = inspector.Linker(self.project)
+ self.linker.visit(self.project)
+
+ def test_class_implements(self):
+ klass = self.project.get_module('data2.clientmodule_test')['Ancestor']
+ self.assert_(hasattr(klass, 'implements'))
+ self.assertEqual(len(klass.implements), 1)
+ self.assert_(isinstance(klass.implements[0], nodes.Class))
+ self.assertEqual(klass.implements[0].name, "Interface")
+ klass = self.project.get_module('data2.clientmodule_test')['Specialization']
+ self.assert_(hasattr(klass, 'implements'))
+ self.assertEqual(len(klass.implements), 0)
+
+ def test_locals_assignment_resolution(self):
+ klass = self.project.get_module('data2.clientmodule_test')['Specialization']
+ self.assert_(hasattr(klass, 'locals_type'))
+ type_dict = klass.locals_type
+ self.assertEqual(len(type_dict), 2)
+ keys = type_dict.keys()
+ keys.sort()
+ self.assertEqual(keys, ['TYPE', 'top'])
+ self.assertEqual(len(type_dict['TYPE']), 1)
+ self.assertEqual(type_dict['TYPE'][0].value, 'final class')
+ self.assertEqual(len(type_dict['top']), 1)
+ self.assertEqual(type_dict['top'][0].value, 'class')
+
+ def test_instance_attrs_resolution(self):
+ klass = self.project.get_module('data2.clientmodule_test')['Specialization']
+ self.assert_(hasattr(klass, 'instance_attrs_type'))
+ type_dict = klass.instance_attrs_type
+ self.assertEqual(len(type_dict), 3)
+ keys = type_dict.keys()
+ keys.sort()
+ self.assertEqual(keys, ['_id', 'relation', 'toto'])
+ self.assert_(isinstance(type_dict['relation'][0], astng.Instance), type_dict['relation'])
+ self.assertEqual(type_dict['relation'][0].name, 'DoNothing')
+ self.assert_(isinstance(type_dict['toto'][0], astng.Instance), type_dict['toto'])
+ self.assertEqual(type_dict['toto'][0].name, 'Toto')
+ self.assert_(type_dict['_id'][0] is astng.YES, type_dict['_id'])
+
+
+class LinkerTC2(LinkerTC):
+
+ def setUp(self):
+ self.project = ASTNGManager().from_directory('data2')
+ self.linker = inspector.Linker(self.project)
+ self.linker.visit(self.project)
+
+__all__ = ('LinkerTC', 'LinkerTC2')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/unittest_lookup.py b/test/unittest_lookup.py
new file mode 100644
index 00000000..62cbd22f
--- /dev/null
+++ b/test/unittest_lookup.py
@@ -0,0 +1,122 @@
+# 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""tests for the astng variable lookup capabilities
+
+Copyright (c) 2005-2006 LOGILAB S.A. (Paris, FRANCE).
+http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+__revision__ = "$Id: unittest_lookup.py,v 1.2 2006-03-03 09:29:50 syt Exp $"
+
+from os.path import join, abspath
+
+from logilab.astng import builder, nodes, scoped_nodes, \
+ InferenceError, NotFoundError
+#from logilab.astng import builder, nodes, inference, utils, YES
+from logilab.common.testlib import TestCase, unittest_main
+
+builder = builder.ASTNGBuilder()
+MODULE = builder.file_build('data/module.py', 'data.module')
+MODULE2 = builder.file_build('data/module2.py', 'data.module2')
+NONREGR = builder.file_build('data/nonregr.py', 'data.nonregr')
+
+class LookupTC(TestCase):
+
+ def test_limit(self):
+ data = '''
+l = [a
+ for a,b in list]
+
+a = 1
+b = a
+a = None
+
+def func():
+ c = 1
+ '''
+ astng = builder.string_build(data, __name__, __file__)
+ names = astng.nodes_of_class(nodes.Name)
+ a = names.next()
+ stmts = a.lookup('a')[1]
+ self.failUnlessEqual(len(stmts), 1)
+ b = astng.locals['b'][1]
+ #self.failUnlessEqual(len(b.lookup('b')[1]), 1)
+ self.failUnlessEqual(len(astng.lookup('b')[1]), 2)
+ b_infer = b.infer()
+ b_value = b_infer.next()
+ self.failUnlessEqual(b_value.value, 1)
+ self.failUnlessRaises(StopIteration, b_infer.next)
+ func = astng.locals['func'][0]
+ self.failUnlessEqual(len(func.lookup('c')[1]), 1)
+
+ def test_module(self):
+ astng = builder.string_build('pass', __name__, __file__)
+ # built-in objects
+ none = astng.ilookup('None').next()
+ self.assertEquals(none.value, None)
+ obj = astng.ilookup('object').next()
+ self.assert_(isinstance(obj, nodes.Class))
+ self.assertEquals(obj.name, 'object')
+ self.assertRaises(InferenceError, astng.ilookup('YOAA').next)
+
+ # XXX
+ self.assertEquals(len(list(NONREGR.ilookup('enumerate'))), 2)
+
+ def test_class_ancestor_name(self):
+ data = '''
+class A:
+ pass
+
+class A(A):
+ pass
+ '''
+ astng = builder.string_build(data, __name__, __file__)
+ cls1 = astng.locals['A'][0]
+ cls2 = astng.locals['A'][1]
+ name = cls2.nodes_of_class(nodes.Name).next()
+ self.assertEquals(name.infer().next(), cls1)
+
+ ### backport those test to inline code
+ def test_method(self):
+ method = MODULE['YOUPI']['method']
+ my_dict = method.ilookup('MY_DICT').next()
+ self.assert_(isinstance(my_dict, nodes.Dict), my_dict)
+ none = method.ilookup('None').next()
+ self.assertEquals(none.value, None)
+ self.assertRaises(InferenceError, method.ilookup('YOAA').next)
+
+ def test_function_argument_with_default(self):
+ make_class = MODULE2['make_class']
+ base = make_class.ilookup('base').next()
+ self.assert_(isinstance(base, nodes.Class), base.__class__)
+ self.assertEquals(base.name, 'YO')
+ self.assertEquals(base.root().name, 'data.module')
+
+ def test_class(self):
+ klass = MODULE['YOUPI']
+ #print klass.getattr('MY_DICT')
+ my_dict = klass.ilookup('MY_DICT').next()
+ self.assert_(isinstance(my_dict, nodes.Dict))
+ none = klass.ilookup('None').next()
+ self.assertEquals(none.value, None)
+ obj = klass.ilookup('object').next()
+ self.assert_(isinstance(obj, nodes.Class))
+ self.assertEquals(obj.name, 'object')
+ self.assertRaises(InferenceError, klass.ilookup('YOAA').next)
+
+ def test_inner_classes(self):
+ ccc = NONREGR['Ccc']
+ self.assertEquals(ccc.ilookup('Ddd').next().name, 'Ddd')
+
+if __name__ == '__main__':
+ unittest_main()
diff --git a/test/unittest_manager.py b/test/unittest_manager.py
new file mode 100644
index 00000000..fd01036f
--- /dev/null
+++ b/test/unittest_manager.py
@@ -0,0 +1,70 @@
+import unittest
+import os
+from os.path import join
+from logilab.astng.manager import ASTNGManager
+
+
+class ASTNGManagerTC(unittest.TestCase):
+ def setUp(self):
+ self.manager = ASTNGManager()
+
+ def test_astng_from_module(self):
+ astng = self.manager.astng_from_module(unittest)
+ self.assertEquals(astng.pure_python, True)
+ import time
+ astng = self.manager.astng_from_module(time)
+ self.assertEquals(astng.pure_python, False)
+
+ def test_astng_from_class(self):
+ astng = self.manager.astng_from_class(file)
+ self.assertEquals(astng.name, 'file')
+ self.assertEquals(astng.parent.frame().name, '__builtin__')
+
+ astng = self.manager.astng_from_class(object)
+ self.assertEquals(astng.name, 'object')
+ self.assertEquals(astng.parent.frame().name, '__builtin__')
+ self.failUnless('__setattr__' in astng)
+
+
+
+ def test_from_directory(self):
+ obj = self.manager.from_directory('data')
+ self.assertEquals(obj.name, 'data')
+ self.assertEquals(obj.path, join(os.getcwd(), 'data'))
+
+ def test_package_node(self):
+ obj = self.manager.from_directory('data')
+ expected_short = ['SSL1', '__init__', 'all', 'appl', 'module', 'module2',
+ 'noendingnewline', 'nonregr', 'notall']
+ expected_long = ['SSL1', 'data', 'data.all', 'appl', 'data.module',
+ 'data.module2', 'data.noendingnewline', 'data.nonregr',
+ 'data.notall']
+ self.assertEquals(obj.keys(), expected_short)
+ self.assertEquals([m.name for m in obj.values()], expected_long)
+ self.assertEquals([m for m in list(obj)], expected_short)
+ self.assertEquals([(name, m.name) for name, m in obj.items()],
+ zip(expected_short, expected_long))
+ self.assertEquals([(name, m.name) for name, m in obj.items()],
+ zip(expected_short, expected_long))
+
+ self.assertEquals('module' in obj, True)
+ self.assertEquals(obj.has_key('module'), True)
+ self.assertEquals(obj.get('module').name, 'data.module')
+ self.assertEquals(obj['module'].name, 'data.module')
+ self.assertEquals(obj.get('whatever'), None)
+
+ #self.assertEquals(obj.has_key, [])
+ #self.assertEquals(obj.get, [])
+ #for key in obj:
+ # print key,
+ # print obj[key]
+
+ self.assertEquals(obj.fullname(), 'data')
+ # FIXME: test fullname on a subpackage
+
+__all__ = ('ASTNGManagerTC',)
+
+if __name__ == '__main__':
+ unittest.main()
+
+
diff --git a/test/unittest_nodes.py b/test/unittest_nodes.py
new file mode 100644
index 00000000..5d65cf0a
--- /dev/null
+++ b/test/unittest_nodes.py
@@ -0,0 +1,77 @@
+# 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""tests for specific behaviour of astng nodes
+
+Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+__revision__ = "$Id: unittest_nodes.py,v 1.6 2005-12-28 14:56:21 syt Exp $"
+
+import unittest
+
+from logilab.astng import builder, nodes, NotFoundError
+
+from data import module as test_module
+
+abuilder = builder.ASTNGBuilder()
+MODULE = abuilder.module_build(test_module)
+MODULE2 = abuilder.file_build('data/module2.py', 'data.module2')
+
+
+class ImportNodeTC(unittest.TestCase):
+
+ def test_import_self_resolve(self):
+ import_ = MODULE2['myos']
+ myos = import_.infer('myos').next()
+ self.failUnless(isinstance(myos, nodes.Module), myos)
+ self.failUnlessEqual(myos.name, 'os')
+
+ def test_from_self_resolve(self):
+ from_ = MODULE['modutils']
+ spawn = from_.infer('spawn').next()
+ self.failUnless(isinstance(spawn, nodes.Class), spawn)
+ self.failUnlessEqual(spawn.root().name, 'logilab.common')
+ from_ = MODULE2['abspath']
+ abspath = from_.infer('abspath').next()
+ self.failUnless(isinstance(abspath, nodes.Function), abspath)
+ self.failUnlessEqual(abspath.root().name, 'os.path')
+
+ def test_real_name(self):
+ from_ = MODULE['modutils']
+ self.assertEquals(from_.real_name('spawn'), 'Execute')
+ imp_ = MODULE['os']
+ self.assertEquals(imp_.real_name('os'), 'os')
+ self.assertRaises(NotFoundError, imp_.real_name, 'os.path')
+ imp_ = MODULE['spawn']
+ self.assertEquals(imp_.real_name('spawn'), 'Execute')
+ self.assertRaises(NotFoundError, imp_.real_name, 'Execute')
+ imp_ = MODULE2['YO']
+ self.assertEquals(imp_.real_name('YO'), 'YO')
+ self.assertRaises(NotFoundError, imp_.real_name, 'data')
+
+ def test_as_string(self):
+ ast = MODULE['modutils']
+ self.assertEquals(ast.as_string(), "from logilab.common import modutils, Execute as spawn")
+ ast = MODULE['os']
+ self.assertEquals(ast.as_string(), "import os.path")
+
+class CmpNodeTC(unittest.TestCase):
+ def test_as_string(self):
+ ast = abuilder.string_build("a == 2")
+ self.assertEquals(ast.as_string(), "a == 2")
+
+__all__ = ('ImportNodeTC',)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/unittest_scoped_nodes.py b/test/unittest_scoped_nodes.py
new file mode 100644
index 00000000..41bc03e0
--- /dev/null
+++ b/test/unittest_scoped_nodes.py
@@ -0,0 +1,272 @@
+# 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""tests for specific behaviour of astng scoped nodes (ie module, class and
+function)
+
+Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE).
+http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+__revision__ = "$Id: unittest_scoped_nodes.py,v 1.12 2006-03-05 14:44:15 syt Exp $"
+
+import unittest
+import sys
+
+from logilab.common.compat import sorted
+
+from logilab.astng import builder, nodes, scoped_nodes, \
+ InferenceError, NotFoundError
+
+abuilder = builder.ASTNGBuilder()
+MODULE = abuilder.file_build('data/module.py', 'data.module')
+MODULE2 = abuilder.file_build('data/module2.py', 'data.module2')
+NONREGR = abuilder.file_build('data/nonregr.py', 'data.nonregr')
+
+def _test_dict_interface(self, node, test_attr):
+ self.assert_(node[test_attr] is node[test_attr])
+ self.assert_(test_attr in node)
+ node.keys()
+ node.values()
+ node.items()
+ iter(node)
+
+
+class ModuleNodeTC(unittest.TestCase):
+
+ def test_dict_interface(self):
+ _test_dict_interface(self, MODULE, 'YO')
+
+ def test_getattr(self):
+ yo = MODULE.getattr('YO')[0]
+ self.assert_(isinstance(yo, nodes.Class))
+ self.assertEquals(yo.name, 'YO')
+ red = MODULE.igetattr('redirect').next()
+ self.assert_(isinstance(red, nodes.Function))
+ self.assertEquals(red.name, 'nested_args')
+ spawn = MODULE.igetattr('spawn').next()
+ self.assert_(isinstance(spawn, nodes.Class))
+ self.assertEquals(spawn.name, 'Execute')
+ # resolve packageredirection
+ sys.path.insert(1, 'data')
+ try:
+ m = abuilder.file_build('data/appl/myConnection.py', 'appl.myConnection')
+ cnx = m.igetattr('SSL1').next().igetattr('Connection').next()
+ self.assertEquals(cnx.__class__, nodes.Class)
+ self.assertEquals(cnx.name, 'Connection')
+ self.assertEquals(cnx.root().name, 'SSL1.Connection1')
+ finally:
+ del sys.path[1]
+ self.assertEquals(len(NONREGR.getattr('enumerate')), 2)
+ # raise ResolveError
+ self.assertRaises(InferenceError, MODULE.igetattr, 'YOAA')
+
+ def test_wildard_import_names(self):
+ m = abuilder.file_build('data/all.py', 'all')
+ self.assertEquals(m.wildcard_import_names(), ['Aaa', '_bla', 'name'])
+ m = abuilder.file_build('data/notall.py', 'notall')
+ res = m.wildcard_import_names()
+ res.sort()
+ self.assertEquals(res, ['Aaa', 'func', 'name', 'other'])
+
+ def test_as_string(self):
+ """just check as_string on a whole module doesn't raise an exception
+ """
+ self.assert_(MODULE.as_string())
+ self.assert_(MODULE2.as_string())
+
+
+class FunctionNodeTC(unittest.TestCase):
+
+ def test_dict_interface(self):
+ _test_dict_interface(self, MODULE['global_access'], 'local')
+
+ def test_default_value(self):
+ func = MODULE2['make_class']
+ self.assert_(isinstance(func.default_value('base'), nodes.Getattr))
+ self.assertRaises(scoped_nodes.NoDefault, func.default_value, 'args')
+ self.assertRaises(scoped_nodes.NoDefault, func.default_value, 'kwargs')
+ self.assertRaises(scoped_nodes.NoDefault, func.default_value, 'any')
+ self.assert_(isinstance(func.mularg_class('args'), nodes.Tuple))
+ self.assert_(isinstance(func.mularg_class('kwargs'), nodes.Dict))
+ self.assertEquals(func.mularg_class('base'), None)
+
+ def test_navigation(self):
+ function = MODULE['global_access']
+ self.assertEquals(function.statement(), function)
+ l_sibling = function.previous_sibling()
+ self.assert_(isinstance(l_sibling, nodes.Assign))
+ self.assert_(l_sibling is function.getChildNodes()[0].previous_sibling())
+ r_sibling = function.next_sibling()
+ self.assert_(isinstance(r_sibling, nodes.Class))
+ self.assertEquals(r_sibling.name, 'YO')
+ self.assert_(r_sibling is function.getChildNodes()[0].next_sibling())
+ last = r_sibling.next_sibling().next_sibling().next_sibling()
+ self.assert_(isinstance(last, nodes.Assign))
+ self.assertEquals(last.next_sibling(), None)
+ first = l_sibling.previous_sibling().previous_sibling().previous_sibling().previous_sibling()
+ self.assertEquals(first.previous_sibling(), None)
+
+ def test_nested_args(self):
+ func = MODULE['nested_args']
+ self.assertEquals(func.argnames, ['a', ('b', 'c', 'd')])
+ local = func.keys()
+ local.sort()
+ self.assertEquals(local, ['a', 'b', 'c', 'd'])
+ self.assertEquals(func.type, 'function')
+
+ def test_format_args(self):
+ func = MODULE2['make_class']
+ self.assertEquals(func.format_args(), 'any, base=data.module.YO, *args, **kwargs')
+ func = MODULE['nested_args']
+ self.assertEquals(func.format_args(), 'a, (b,c,d)')
+
+ def test_is_abstract(self):
+ method = MODULE2['AbstractClass']['to_override']
+ self.assert_(method.is_abstract(pass_is_abstract=False))
+ method = MODULE2['AbstractClass']['return_something']
+ self.assert_(not method.is_abstract(pass_is_abstract=False))
+ # non regression : test raise "string" doesn't cause an exception in is_abstract
+ func = MODULE2['raise_string']
+ self.assert_(not func.is_abstract(pass_is_abstract=False))
+
+## def test_raises(self):
+## method = MODULE2['AbstractClass']['to_override']
+## self.assertEquals([str(term) for term in method.raises()],
+## ["CallFunc(Name('NotImplementedError'), [], None, None)"] )
+
+## def test_returns(self):
+## method = MODULE2['AbstractClass']['return_something']
+## # use string comp since Node doesn't handle __cmp__
+## self.assertEquals([str(term) for term in method.returns()],
+## ["Const('toto')", "Const(None)"])
+
+
+class ClassNodeTC(unittest.TestCase):
+
+ def test_dict_interface(self):
+ _test_dict_interface(self, MODULE['YOUPI'], 'method')
+
+ def test_navigation(self):
+ klass = MODULE['YO']
+ self.assertEquals(klass.statement(), klass)
+ l_sibling = klass.previous_sibling()
+ self.assert_(isinstance(l_sibling, nodes.Function), l_sibling)
+ self.assertEquals(l_sibling.name, 'global_access')
+ r_sibling = klass.next_sibling()
+ self.assert_(isinstance(r_sibling, nodes.Class))
+ self.assertEquals(r_sibling.name, 'YOUPI')
+
+ def test_local_attr_ancestors(self):
+ klass2 = MODULE['YOUPI']
+ it = klass2.local_attr_ancestors('__init__')
+ anc_klass = it.next()
+ self.assert_(isinstance(anc_klass, nodes.Class))
+ self.assertEquals(anc_klass.name, 'YO')
+ self.assertRaises(StopIteration, it.next)
+ it = klass2.local_attr_ancestors('method')
+ self.assertRaises(StopIteration, it.next)
+
+ def test_instance_attr_ancestors(self):
+ klass2 = MODULE['YOUPI']
+ it = klass2.instance_attr_ancestors('yo')
+ anc_klass = it.next()
+ self.assert_(isinstance(anc_klass, nodes.Class))
+ self.assertEquals(anc_klass.name, 'YO')
+ self.assertRaises(StopIteration, it.next)
+ klass2 = MODULE['YOUPI']
+ it = klass2.instance_attr_ancestors('member')
+ self.assertRaises(StopIteration, it.next)
+
+ def test_methods(self):
+ klass2 = MODULE['YOUPI']
+ methods = [m.name for m in klass2.methods()]
+ methods.sort()
+ self.assertEquals(methods, ['__init__', 'class_method',
+ 'method', 'static_method'])
+ methods = [m.name for m in klass2.mymethods()]
+ methods.sort()
+ self.assertEquals(methods, ['__init__', 'class_method',
+ 'method', 'static_method'])
+ klass2 = MODULE2['Specialization']
+ methods = [m.name for m in klass2.mymethods()]
+ methods.sort()
+ self.assertEquals(methods, [])
+ self.assertEquals(klass2.local_attr('method').name, 'method')
+ self.assertRaises(NotFoundError, klass2.local_attr, 'nonexistant')
+ methods = [m.name for m in klass2.methods()]
+ methods.sort()
+ self.assertEquals(methods, ['__init__', 'class_method',
+ 'method', 'static_method'])
+
+ #def test_rhs(self):
+ # my_dict = MODULE['MY_DICT']
+ # self.assert_(isinstance(my_dict.rhs(), nodes.Dict))
+ # a = MODULE['YO']['a']
+ # value = a.rhs()
+ # self.assert_(isinstance(value, nodes.Const))
+ # self.assertEquals(value.value, 1)
+
+ def test_ancestors(self):
+ klass = MODULE['YOUPI']
+ ancs = [a.name for a in klass.ancestors()]
+ self.assertEquals(ancs, ['YO'])
+ klass = MODULE2['Specialization']
+ ancs = [a.name for a in klass.ancestors()]
+ self.assertEquals(ancs, ['YOUPI', 'YO', 'YO'])
+
+ def test_type(self):
+ klass = MODULE['YOUPI']
+ self.assertEquals(klass.type, 'class')
+ klass = MODULE2['Metaclass']
+ self.assertEquals(klass.type, 'metaclass')
+ klass = MODULE2['MyException']
+ self.assertEquals(klass.type, 'exception')
+ klass = MODULE2['MyIFace']
+ self.assertEquals(klass.type, 'interface')
+ klass = MODULE2['MyError']
+ self.assertEquals(klass.type, 'exception')
+
+ def test_interfaces(self):
+ for klass, interfaces in (('Concrete0', ['MyIFace']),
+ ('Concrete1', ['MyIFace', 'AnotherIFace']),
+ ('Concrete2', ['MyIFace', 'AnotherIFace']),
+ ('Concrete23', ['MyIFace', 'AnotherIFace'])):
+ klass = MODULE2[klass]
+ self.assertEquals([i.name for i in klass.interfaces()],
+ interfaces)
+
+ def test_inner_classes(self):
+ eee = NONREGR['Ccc']['Eee']
+ self.assertEquals([n.name for n in eee.ancestors()], ['Ddd', 'Aaa', 'object'])
+
+
+ def test_classmethod_attributes(self):
+ data = '''
+class WebAppObject(object):
+ def registered(cls, application):
+ cls.appli = application
+ cls.schema = application.schema
+ cls.config = application.config
+ return cls
+ registered = classmethod(registered)
+ '''
+ astng = abuilder.string_build(data, __name__, __file__)
+ cls = astng['WebAppObject']
+ self.assertEquals(sorted(cls.locals.keys()),
+ ['__dict__', '__doc__', '__module__', '__name__',
+ 'appli', 'config', 'registered', 'schema'])
+
+__all__ = ('ModuleNodeTC', 'ImportNodeTC', 'FunctionNodeTC', 'ClassNodeTC')
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/utils.py b/utils.py
new file mode 100644
index 00000000..24158c1d
--- /dev/null
+++ b/utils.py
@@ -0,0 +1,175 @@
+# 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.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""this module contains some utilities to navigate in the tree or to
+extract information from it
+
+:version: $Revision: 1.18 $
+:author: Sylvain Thenault
+:copyright: 2003-2005 LOGILAB S.A. (Paris, FRANCE)
+:contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org
+:copyright: 2003-2005 Sylvain Thenault
+:contact: mailto:thenault@gmail.com
+"""
+
+__revision__ = "$Id: utils.py,v 1.18 2006-01-03 15:51:41 syt Exp $"
+__doctype__ = "restructuredtext en"
+
+from logilab.common.compat import enumerate
+from logilab.astng._exceptions import IgnoreChild
+
+def extend_class(original, addons):
+ """add methods and attribute defined in the addons class to the original
+ class
+ """
+ brain = addons.__dict__.copy()
+ for special_key in ('__doc__', '__module__'):
+ if special_key in addons.__dict__:
+ del brain[special_key]
+ original.__dict__.update(brain)
+
+class ASTWalker:
+ """a walker visiting a tree in preorder, calling on the handler:
+
+ * visit_<class name> on entering a node, where class name is the class of
+ the node in lower case
+
+ * leave_<class name> on leaving a node, where class name is the class of
+ the node in lower case
+ """
+ def __init__(self, handler):
+ self.handler = handler
+ self._cache = {}
+
+ def walk(self, node):
+ """walk on the tree from <node>, getting callbacks from handler
+ """
+ try:
+ self.visit(node)
+ except IgnoreChild:
+ pass
+ else:
+ for child_node in node.getChildNodes():
+ self.walk(child_node)
+ self.leave(node)
+
+ def get_callbacks(self, node):
+ """get callbacks from handler for the visited node
+ """
+ klass = node.__class__
+ methods = self._cache.get(klass)
+ if methods is None:
+ handler = self.handler
+ kid = klass.__name__.lower()
+ e_method = getattr(handler, 'visit_%s' % kid,
+ getattr(handler, 'visit_default', None))
+ l_method = getattr(handler, 'leave_%s' % kid,
+ getattr(handler, 'leave_default', None))
+ self._cache[klass] = (e_method, l_method)
+ else:
+ e_method, l_method = methods
+ return e_method, l_method
+
+ def visit(self, node):
+ """walk on the tree from <node>, getting callbacks from handler"""
+ method = self.get_callbacks(node)[0]
+ if method is not None:
+ method(node)
+
+ def leave(self, node):
+ """walk on the tree from <node>, getting callbacks from handler"""
+ method = self.get_callbacks(node)[1]
+ if method is not None:
+ method(node)
+
+
+class LocalsVisitor(ASTWalker):
+ """visit a project by traversing the locals dictionnary"""
+ def __init__(self):
+ ASTWalker.__init__(self, self)
+ self._visited = {}
+
+ def visit(self, node):
+ """launch the visit starting from the given node"""
+ if self._visited.has_key(node):
+ return
+ self._visited[node] = 1
+ methods = self.get_callbacks(node)
+ recurse = 1
+ if methods[0] is not None:
+ try:
+ methods[0](node)
+ except IgnoreChild:
+ recurse = 0
+ if recurse:
+ if hasattr(node, 'locals'):
+ for local_node in node.values():
+ self.visit(local_node)
+ if methods[1] is not None:
+ return methods[1](node)
+
+def are_exclusive(stmt1, stmt2):
+ """return true if the two given statement are mutually exclusive
+
+ algorithm :
+ 1) index stmt1's parents
+ 2) climb among stmt2's parents until we find a common parent
+ 3) if the common parent is a If or TryExcept statement, look if nodes are
+ in exclusive branchs
+ """
+ from logilab.astng.nodes import If, TryExcept
+ # index stmt1's parents
+ stmt1_parents = {}
+ children = {}
+ node = stmt1.parent
+ previous = stmt1
+ while node:
+ stmt1_parents[node] = 1
+ children[node] = previous
+ previous = node
+ node = node.parent
+ # climb among stmt2's parents until we find a common parent
+ node = stmt2.parent
+ previous = stmt2
+ while node:
+ if stmt1_parents.has_key(node):
+ # if the common parent is a If or TryExcept statement, look if
+ # nodes are in exclusive branchs
+ if isinstance(node, If):
+ if previous != children[node]:
+ return True
+ elif isinstance(node, TryExcept):
+ stmt1_previous = children[node]
+ if not previous is stmt1_previous:
+ stmt1_branch, stmt1_num = _try_except_from_branch(node, stmt1_previous)
+ stmt2_branch, stmt2_num = _try_except_from_branch(node, previous)
+ if stmt1_branch != stmt1_branch:
+ if not ((stmt2_branch == 'body' and stmt1_branch == 'else') or
+ (stmt1_branch == 'body' and stmt2_branch == 'else') or
+ (stmt2_branch == 'body' and stmt1_branch == 'except') or
+ (stmt1_branch == 'body' and stmt2_branch == 'except')):
+ return True
+ elif stmt1_num != stmt2_num:
+ return True
+ return False
+ previous = node
+ node = node.parent
+ return False
+
+def _try_except_from_branch(node, stmt):
+ if stmt is node.body:
+ return 'body', 1
+ if stmt is node.else_:
+ return 'else', 1
+ for i, block_nodes in enumerate(node.handlers):
+ if stmt in block_nodes:
+ return 'except', i