| Index: tools/telemetry/third_party/rope/docs/overview.rst
|
| diff --git a/tools/telemetry/third_party/rope/docs/overview.rst b/tools/telemetry/third_party/rope/docs/overview.rst
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..3863c9d5e05d9a9c2cbd9a6f9f24a79766d777f5
|
| --- /dev/null
|
| +++ b/tools/telemetry/third_party/rope/docs/overview.rst
|
| @@ -0,0 +1,1243 @@
|
| +===============
|
| + Rope Overview
|
| +===============
|
| +
|
| +
|
| +The purpose of this file is to give an overview of some of rope's
|
| +features. It is incomplete. And some of the features shown here are
|
| +old and do not show what rope can do in extremes. So if you really
|
| +want to feel the power of rope try its features and see its unit
|
| +tests.
|
| +
|
| +This file is more suitable for the users. Developers who plan to use
|
| +rope as a library might find library.rst_ more useful.
|
| +
|
| +.. contents:: Table of Contents
|
| +.. _library.rst: library.rst
|
| +
|
| +
|
| +``.ropeproject`` Folder
|
| +=======================
|
| +
|
| +Rope uses a folder inside projects for holding project configuration
|
| +and data. Its default name is ``.ropeproject``, but it can be
|
| +changed (you can even tell rope not to create this folder).
|
| +
|
| +Currently it is used for things such as:
|
| +
|
| +* There is a ``config.py`` file in this folder in which you can change
|
| + project configurations. Have look at the default ``config.py`` file
|
| + (is created when it does not exist) for more information.
|
| +* It can be used for saving project history, so that the next time you
|
| + open the project you can undo past changes.
|
| +* It can be used for saving object information to help rope object
|
| + inference.
|
| +* It can be used for saving global names cache which is used in
|
| + auto-import.
|
| +
|
| +You can change what to save and what not to in the ``config.py`` file.
|
| +
|
| +
|
| +Refactorings
|
| +============
|
| +
|
| +This section shows some random refactorings that you can perform using
|
| +rope.
|
| +
|
| +
|
| +Renaming Attributes
|
| +-------------------
|
| +
|
| +Consider we have:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class AClass(object):
|
| +
|
| + def __init__(self):
|
| + self.an_attr = 1
|
| +
|
| + def a_method(self, arg):
|
| + print self.an_attr, arg
|
| +
|
| + a_var = AClass()
|
| + a_var.a_method(a_var.an_attr)
|
| +
|
| +After renaming ``an_attr`` to ``new_attr`` and ``a_method`` to
|
| +``new_method`` we'll have:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class AClass(object):
|
| +
|
| + def __init__(self):
|
| + self.new_attr = 1
|
| +
|
| + def new_method(self, arg):
|
| + print self.new_attr, arg
|
| +
|
| + a_var = AClass()
|
| + a_var.new_method(a_var.new_attr)
|
| +
|
| +
|
| +Renaming Function Keyword Parameters
|
| +------------------------------------
|
| +
|
| +On:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def a_func(a_param):
|
| + print a_param
|
| +
|
| + a_func(a_param=10)
|
| + a_func(10)
|
| +
|
| +performing rename refactoring on any occurrence of ``a_param`` will
|
| +result in:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def a_func(new_param):
|
| + print new_param
|
| +
|
| + a_func(new_param=10)
|
| + a_func(10)
|
| +
|
| +
|
| +Renaming modules
|
| +----------------
|
| +
|
| +Consider the project tree is something like::
|
| +
|
| + root/
|
| + mod1.py
|
| + mod2.py
|
| +
|
| +``mod1.py`` contains:
|
| +
|
| +.. code-block:: python
|
| +
|
| + import mod2
|
| + from mod2 import AClass
|
| +
|
| + mod2.a_func()
|
| + a_var = AClass()
|
| +
|
| +After performing rename refactoring one of the ``mod2`` occurrences in
|
| +`mod1` we'll get:
|
| +
|
| +.. code-block:: python
|
| +
|
| + import newmod
|
| + from newmod import AClass
|
| +
|
| + newmod.a_func()
|
| + a_var = AClass()
|
| +
|
| +and the new project tree would be::
|
| +
|
| + root/
|
| + mod1.py
|
| + newmod.py
|
| +
|
| +
|
| +Renaming Occurrences In Strings And Comments
|
| +--------------------------------------------
|
| +
|
| +You can tell rope to rename all occurrences of a name in comments and
|
| +strings. This can be done by passing ``docs=True`` to
|
| +`Rename.get_changes()` method. Rope renames names in comments and
|
| +strings only where the name is visible. For example in:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def f():
|
| + a_var = 1
|
| + # INFO: I'm printing `a_var`
|
| + print 'a_var = %s' % a_var
|
| +
|
| + # f prints a_var
|
| +
|
| +after we rename the `a_var` local variable in `f()` to `new_var` we
|
| +would get:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def f():
|
| + new_var = 1
|
| + # INFO: I'm printing `new_var`
|
| + print 'new_var = %s' % new_var
|
| +
|
| + # f prints a_var
|
| +
|
| +This makes it safe to assume that this option does not perform wrong
|
| +renames most of the time.
|
| +
|
| +This also changes occurrences inside evaluated strings:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def func():
|
| + print 'func() called'
|
| +
|
| + eval('func()')
|
| +
|
| +After renaming ``func`` to ``newfunc`` we should have:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def newfunc():
|
| + print 'newfunc() called'
|
| +
|
| + eval('newfunc()')
|
| +
|
| +
|
| +Rename When Unsure
|
| +------------------
|
| +
|
| +This option tells rope to rename when it doesn't know whether it is an
|
| +exact match or not. For example after renaming `C.a_func` when the
|
| +'rename when unsure' option is set in:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class C(object):
|
| +
|
| + def a_func(self):
|
| + pass
|
| +
|
| + def a_func(arg):
|
| + arg.a_func()
|
| +
|
| + C().a_func()
|
| +
|
| +we would have:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class C(object):
|
| +
|
| + def new_func(self):
|
| + pass
|
| +
|
| + def a_func(arg):
|
| + arg.new_func()
|
| +
|
| + C().new_func()
|
| +
|
| +Note that the global ``a_func`` was not renamed because we are sure that
|
| +it is not a match. But when using this option there might be some
|
| +unexpected renames. So only use this option when the name is almost
|
| +unique and is not defined in other places.
|
| +
|
| +Move Method Refactoring
|
| +-----------------------
|
| +
|
| +It happens when you perform move refactoring on a method of a class.
|
| +In this refactoring, a method of a class is moved to the class of one
|
| +of its attributes. The old method will call the new method. If you
|
| +want to change all of the occurrences of the old method to use the new
|
| +method you can inline it afterwards.
|
| +
|
| +For instance if you perform move method on ``a_method`` in:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class A(object):
|
| + pass
|
| +
|
| + class B(object):
|
| +
|
| + def __init__(self):
|
| + self.attr = A()
|
| +
|
| + def a_method(self):
|
| + pass
|
| +
|
| + b = B()
|
| + b.a_method()
|
| +
|
| +You will be asked for the destination field and the name of the new
|
| +method. If you use ``attr`` and ``new_method`` in these fields
|
| +and press enter, you'll have:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class A(object):
|
| +
|
| + def new_method(self):
|
| + pass
|
| +
|
| + class B(object):
|
| +
|
| + def __init__(self):
|
| + self.attr = A()
|
| +
|
| + def a_method(self):
|
| + return self.attr.new_method()
|
| +
|
| +
|
| + b = B()
|
| + b.a_method()
|
| +
|
| +Now if you want to change the occurrences of ``B.a_method()`` to use
|
| +``A.new_method()``, you can inline ``B.a_method()``:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class A(object):
|
| +
|
| + def new_method(self):
|
| + pass
|
| +
|
| + class B(object):
|
| +
|
| + def __init__(self):
|
| + self.attr = A()
|
| +
|
| + b = B()
|
| + b.attr.new_method()
|
| +
|
| +
|
| +Moving Fields
|
| +-------------
|
| +
|
| +Rope does not have a separate refactoring for moving fields. Rope's
|
| +refactorings are very flexible, though. You can use the rename
|
| +refactoring to move fields. For instance:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class A(object):
|
| + pass
|
| +
|
| + class B(object):
|
| +
|
| + def __init__(self):
|
| + self.a = A()
|
| + self.attr = 1
|
| +
|
| + b = B()
|
| + print(b.attr)
|
| +
|
| +consider we want to move ``attr`` to ``A``. We can do that by renaming
|
| +``attr`` to ``a.attr``:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class A(object):
|
| + pass
|
| +
|
| + class B(object):
|
| +
|
| + def __init__(self):
|
| + self.a = A()
|
| + self.a.attr = 1
|
| +
|
| + b = B()
|
| + print(b.a.attr)
|
| +
|
| +You can move the definition of ``attr`` manually.
|
| +
|
| +
|
| +Extract Method
|
| +--------------
|
| +
|
| +In these examples ``${region_start}`` and ``${region_end}`` show the
|
| +selected region for extraction:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def a_func():
|
| + a = 1
|
| + b = 2 * a
|
| + c = ${region_start}a * 2 + b * 3${region_end}
|
| +
|
| +After performing extract method we'll have:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def a_func():
|
| + a = 1
|
| + b = 2 * a
|
| + c = new_func(a, b)
|
| +
|
| + def new_func(a, b):
|
| + return a * 2 + b * 3
|
| +
|
| +For multi-line extractions if we have:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def a_func():
|
| + a = 1
|
| + ${region_start}b = 2 * a
|
| + c = a * 2 + b * 3${region_end}
|
| + print b, c
|
| +
|
| +After performing extract method we'll have:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def a_func():
|
| + a = 1
|
| + b, c = new_func(a)
|
| + print b, c
|
| +
|
| + def new_func(a):
|
| + b = 2 * a
|
| + c = a * 2 + b * 3
|
| + return b, c
|
| +
|
| +
|
| +Extracting Similar Expressions/Statements
|
| +-----------------------------------------
|
| +
|
| +When performing extract method or local variable refactorings you can
|
| +tell rope to extract similar expressions/statements. For instance
|
| +in:
|
| +
|
| +.. code-block:: python
|
| +
|
| + if True:
|
| + x = 2 * 3
|
| + else:
|
| + x = 2 * 3 + 1
|
| +
|
| +Extracting ``2 * 3`` will result in:
|
| +
|
| +.. code-block:: python
|
| +
|
| + six = 2 * 3
|
| + if True:
|
| + x = six
|
| + else:
|
| + x = six + 1
|
| +
|
| +
|
| +Extract Method In staticmethods/classmethods
|
| +--------------------------------------------
|
| +
|
| +The extract method refactoring has been enhanced to handle static and
|
| +class methods better. For instance in:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class A(object):
|
| +
|
| + @staticmethod
|
| + def f(a):
|
| + b = a * 2
|
| +
|
| +if you extract ``a * 2`` as a method you'll get:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class A(object):
|
| +
|
| + @staticmethod
|
| + def f(a):
|
| + b = A.twice(a)
|
| +
|
| + @staticmethod
|
| + def twice(a):
|
| + return a * 2
|
| +
|
| +
|
| +Inline Method Refactoring
|
| +-------------------------
|
| +
|
| +Inline method refactoring can add imports when necessary. For
|
| +instance consider ``mod1.py`` is:
|
| +
|
| +.. code-block:: python
|
| +
|
| + import sys
|
| +
|
| +
|
| + class C(object):
|
| + pass
|
| +
|
| + def do_something():
|
| + print sys.version
|
| + return C()
|
| +
|
| +and ``mod2.py`` is:
|
| +
|
| +.. code-block:: python
|
| +
|
| + import mod1
|
| +
|
| +
|
| + c = mod1.do_something()
|
| +
|
| +After inlining ``do_something``, ``mod2.py`` would be:
|
| +
|
| +.. code-block:: python
|
| +
|
| + import mod1
|
| + import sys
|
| +
|
| +
|
| + print sys.version
|
| + c = mod1.C()
|
| +
|
| +Rope can inline methods, too:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class C(object):
|
| +
|
| + var = 1
|
| +
|
| + def f(self, p):
|
| + result = self.var + pn
|
| + return result
|
| +
|
| +
|
| + c = C()
|
| + x = c.f(1)
|
| +
|
| +After inlining ``C.f()``, we'll have:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class C(object):
|
| +
|
| + var = 1
|
| +
|
| + c = C()
|
| + result = c.var + pn
|
| + x = result
|
| +
|
| +As another example we will inline a ``classmethod``:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class C(object):
|
| + @classmethod
|
| + def say_hello(cls, name):
|
| + return 'Saying hello to %s from %s' % (name, cls.__name__)
|
| + hello = C.say_hello('Rope')
|
| +
|
| +Inlining ``say_hello`` will result in:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class C(object):
|
| + pass
|
| + hello = 'Saying hello to %s from %s' % ('Rope', C.__name__)
|
| +
|
| +
|
| +Inlining Parameters
|
| +-------------------
|
| +
|
| +``rope.refactor.inline.create_inline()`` creates an ``InlineParameter``
|
| +object when performed on a parameter. It passes the default value of
|
| +the parameter wherever its function is called without passing it. For
|
| +instance in:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def f(p1=1, p2=1):
|
| + pass
|
| +
|
| + f(3)
|
| + f()
|
| + f(3, 4)
|
| +
|
| +after inlining p2 parameter will have:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def f(p1=1, p2=1):
|
| + pass
|
| +
|
| + f(3, 2)
|
| + f(p2=2)
|
| + f(3, 4)
|
| +
|
| +
|
| +Use Function Refactoring
|
| +------------------------
|
| +
|
| +It tries to find the places in which a function can be used and
|
| +changes the code to call it instead. For instance if mod1 is:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def square(p):
|
| + return p ** 2
|
| +
|
| + my_var = 3 ** 2
|
| +
|
| +
|
| +and mod2 is:
|
| +
|
| +.. code-block:: python
|
| +
|
| + another_var = 4 ** 2
|
| +
|
| +if we perform "use function" on square function, mod1 will be:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def square(p):
|
| + return p ** 2
|
| +
|
| + my_var = square(3)
|
| +
|
| +and mod2 will be:
|
| +
|
| +.. code-block:: python
|
| +
|
| + import mod1
|
| + another_var = mod1.square(4)
|
| +
|
| +
|
| +Automatic Default Insertion In Change Signature
|
| +-----------------------------------------------
|
| +
|
| +The ``rope.refactor.change_signature.ArgumentReorderer`` signature
|
| +changer takes a parameter called ``autodef``. If not ``None``, its
|
| +value is used whenever rope needs to insert a default for a parameter
|
| +(that happens when an argument without default is moved after another
|
| +that has a default value). For instance in:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def f(p1, p2=2):
|
| + pass
|
| +
|
| +if we reorder using:
|
| +
|
| +.. code-block:: python
|
| +
|
| + changers = [ArgumentReorderer([1, 0], autodef='1')]
|
| +
|
| +will result in:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def f(p2=2, p1=1):
|
| + pass
|
| +
|
| +
|
| +Sorting Imports
|
| +---------------
|
| +
|
| +Organize imports sorts imports, too. It does that according to
|
| +:PEP:`8`::
|
| +
|
| + [__future__ imports]
|
| +
|
| + [standard imports]
|
| +
|
| + [third-party imports]
|
| +
|
| + [project imports]
|
| +
|
| +
|
| + [the rest of module]
|
| +
|
| +
|
| +Handling Long Imports
|
| +---------------------
|
| +
|
| +``Handle long imports`` command trys to make long imports look better by
|
| +transforming ``import pkg1.pkg2.pkg3.pkg4.mod1`` to ``from
|
| +pkg1.pkg2.pkg3.pkg4 import mod1``. Long imports can be identified
|
| +either by having lots of dots or being very long. The default
|
| +configuration considers imported modules with more than 2 dots or with
|
| +more than 27 characters to be long.
|
| +
|
| +
|
| +Stoppable Refactorings
|
| +----------------------
|
| +
|
| +Some refactorings might take a long time to finish (based on the size of
|
| +your project). The ``get_changes()`` method of these refactorings take
|
| +a parameter called ``task_handle``. If you want to monitor or stop
|
| +these refactoring you can pass a ``rope.refactor.taskhandle.TaskHandle``
|
| +to this method. See ``rope.refactor.taskhandle`` module for more
|
| +information.
|
| +
|
| +
|
| +Basic Implicit Interfaces
|
| +-------------------------
|
| +
|
| +Implicit interfaces are the interfaces that you don't explicitly
|
| +define; But you expect a group of classes to have some common
|
| +attributes. These interfaces are very common in dynamic languages;
|
| +Since we only have implementation inheritance and not interface
|
| +inheritance. For instance:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class A(object):
|
| +
|
| + def count(self):
|
| + pass
|
| +
|
| + class B(object):
|
| +
|
| + def count(self):
|
| + pass
|
| +
|
| + def count_for(arg):
|
| + return arg.count()
|
| +
|
| + count_for(A())
|
| + count_for(B())
|
| +
|
| +Here we know that there is an implicit interface defined by the function
|
| +``count_for`` that provides ``count()``. Here when we rename
|
| +``A.count()`` we expect ``B.count()`` to be renamed, too. Currently
|
| +rope supports a basic form of implicit interfaces. When you try to
|
| +rename an attribute of a parameter, rope renames that attribute for all
|
| +objects that have been passed to that function in different call sites.
|
| +That is renaming the occurrence of ``count`` in ``count_for`` function
|
| +to ``newcount`` will result in:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class A(object):
|
| +
|
| + def newcount(self):
|
| + pass
|
| +
|
| + class B(object):
|
| +
|
| + def newcount(self):
|
| + pass
|
| +
|
| + def count_for(arg):
|
| + return arg.newcount()
|
| +
|
| + count_for(A())
|
| + count_for(B())
|
| +
|
| +This also works for change method signature. Note that this feature
|
| +relies on rope's object analysis mechanisms to find out the parameters
|
| +that are passed to a function.
|
| +
|
| +
|
| +Restructurings
|
| +--------------
|
| +
|
| +``rope.refactor.restructure`` can be used for performing restructurings.
|
| +A restructuring is a program transformation; not as well defined as
|
| +other refactorings like rename. In this section, we'll see some
|
| +examples. After this example you might like to have a look at:
|
| +
|
| +* ``rope.refactor.restructure`` for more examples and features not
|
| + described here like adding imports to changed modules.
|
| +* ``rope.refactor.wildcards`` for an overview of the arguments the
|
| + default wildcard supports.
|
| +
|
| +Finally, restructurings can be improved in many ways (for instance
|
| +adding new wildcards). You might like to discuss your ideas in the
|
| +mailing list.
|
| +
|
| +
|
| +Example 1
|
| +'''''''''
|
| +
|
| +In its basic form we have a pattern and a goal. Consider we were not
|
| +aware of the ``**`` operator and wrote our own:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def pow(x, y):
|
| + result = 1
|
| + for i in range(y):
|
| + result *= x
|
| + return result
|
| +
|
| + print pow(2, 3)
|
| +
|
| +Now that we know ``**`` exists we want to use it wherever ``pow`` is
|
| +used (there might be hundreds of them!). We can use a pattern like::
|
| +
|
| + pattern: pow(${param1}, ${param2})
|
| +
|
| +Goal can be something like::
|
| +
|
| + goal: ${param1} ** ${param2}
|
| +
|
| +Note that ``${...}`` can be used to match expressions. By default
|
| +every expression at that point will match.
|
| +
|
| +You can use the matched names in goal and they will be replaced with
|
| +the string that was matched in each occurrence. So the outcome of our
|
| +restructuring will be:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def pow(x, y):
|
| + result = 1
|
| + for i in range(y):
|
| + result *= x
|
| + return result
|
| +
|
| + print 2 ** 3
|
| +
|
| +It seems to be working but what if ``pow`` is imported in some module or
|
| +we have some other function defined in some other module that uses the
|
| +same name and we don't want to change it. Wildcard arguments come to
|
| +rescue. Wildcard arguments is a mapping; Its keys are wildcard names
|
| +that appear in the pattern (the names inside ``${...}``).
|
| +
|
| +The values are the parameters that are passed to wildcard matchers.
|
| +The arguments a wildcard takes is based on its type.
|
| +
|
| +For checking the type of a wildcard, we can pass ``type=value`` as an
|
| +argument; ``value`` should be resolved to a python variable (or
|
| +reference). For instance for specifying ``pow`` in this example we can
|
| +use ``mod.pow``. As you see, this string should start from module name.
|
| +For referencing python builtin types and functions you can use
|
| +``__builtin__`` module (for instance ``__builtin__.int``).
|
| +
|
| +For solving the mentioned problem, we change our ``pattern``. But
|
| +``goal`` remains the same::
|
| +
|
| + pattern: ${pow_func}(${param1}, ${param2})
|
| + goal: ${param1} ** ${param2}
|
| +
|
| +Consider the name of the module containing our ``pow`` function is
|
| +``mod``. ``args`` can be::
|
| +
|
| + pow_func: name=mod.pow
|
| +
|
| +If we need to pass more arguments to a wildcard matcher we can use
|
| +``,`` to separate them. Such as ``name: type=mod.MyClass,exact``.
|
| +
|
| +This restructuring handles aliases like in:
|
| +
|
| +.. code-block:: python
|
| +
|
| + mypow = pow
|
| + result = mypow(2, 3)
|
| +
|
| +Transforms into:
|
| +
|
| +.. code-block:: python
|
| +
|
| + mypow = pow
|
| + result = 2 ** 3
|
| +
|
| +If we want to ignore aliases we can pass ``exact`` as another wildcard
|
| +argument::
|
| +
|
| + pattern: ${pow}(${param1}, ${param2})
|
| + goal: ${param1} ** ${param2}
|
| + args: pow: name=mod.pow, exact
|
| +
|
| +``${name}``, by default, matches every expression at that point; if
|
| +``exact`` argument is passed to a wildcard only the specified name
|
| +will match (for instance, if ``exact`` is specified , ``${name}``
|
| +matches ``name`` and ``x.name`` but not ``var`` nor ``(1 + 2)`` while
|
| +a normal ``${name}`` can match all of them).
|
| +
|
| +For performing this refactoring using rope library see `library.rst`_.
|
| +
|
| +
|
| +Example 2
|
| +'''''''''
|
| +
|
| +As another example consider:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class A(object):
|
| +
|
| + def f(self, p1, p2):
|
| + print p1
|
| + print p2
|
| +
|
| +
|
| + a = A()
|
| + a.f(1, 2)
|
| +
|
| +Later we decide that ``A.f()`` is doing too much and we want to divide
|
| +it to ``A.f1()`` and ``A.f2()``:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class A(object):
|
| +
|
| + def f(self, p1, p2):
|
| + print p1
|
| + print p2
|
| +
|
| + def f1(self, p):
|
| + print p
|
| +
|
| + def f2(self, p):
|
| + print p
|
| +
|
| +
|
| + a = A()
|
| + a.f(1, 2)
|
| +
|
| +But who's going to fix all those nasty occurrences (actually this
|
| +situation can be handled using inline method refactoring but this is
|
| +just an example; consider inline refactoring is not implemented yet!).
|
| +Restructurings come to rescue::
|
| +
|
| + pattern: ${inst}.f(${p1}, ${p2})
|
| + goal:
|
| + ${inst}.f1(${p1})
|
| + ${inst}.f2(${p2})
|
| +
|
| + args:
|
| + inst: type=mod.A
|
| +
|
| +After performing we will have:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class A(object):
|
| +
|
| + def f(self, p1, p2):
|
| + print p1
|
| + print p2
|
| +
|
| + def f1(self, p):
|
| + print p
|
| +
|
| + def f2(self, p):
|
| + print p
|
| +
|
| +
|
| + a = A()
|
| + a.f1(1)
|
| + a.f2(2)
|
| +
|
| +
|
| +Example 3
|
| +'''''''''
|
| +
|
| +If you like to replace every occurrences of ``x.set(y)`` with ``x =
|
| +y`` when x is an instance of ``mod.A`` in:
|
| +
|
| +.. code-block:: python
|
| +
|
| + from mod import A
|
| +
|
| + a = A()
|
| + b = A()
|
| + a.set(b)
|
| +
|
| +We can perform a restructuring with these information::
|
| +
|
| + pattern: ${x}.set(${y})
|
| + goal: ${x} = ${y}
|
| +
|
| + args: x: type=mod.A
|
| +
|
| +After performing the above restructuring we'll have:
|
| +
|
| +.. code-block:: python
|
| +
|
| + from mod import A
|
| +
|
| + a = A()
|
| + b = A()
|
| + a = b
|
| +
|
| +Note that ``mod.py`` contains something like:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class A(object):
|
| +
|
| + def set(self, arg):
|
| + pass
|
| +
|
| +Issues
|
| +''''''
|
| +
|
| +Pattern names can appear only at the start of an expression. For
|
| +instance ``var.${name}`` is invalid. These situations can usually be
|
| +fixed by specifying good checks, for example on the type of `var` and
|
| +using a ``${var}.name``.
|
| +
|
| +
|
| +Object Inference
|
| +================
|
| +
|
| +This section is a bit out of date. Static object inference can do
|
| +more than described here (see unittests). Hope to update this
|
| +someday!
|
| +
|
| +
|
| +Static Object Inference
|
| +-----------------------
|
| +
|
| +.. code-block:: python
|
| +
|
| + class AClass(object):
|
| +
|
| + def __init__(self):
|
| + self.an_attr = 1
|
| +
|
| + def call_a_func(self):
|
| + return a_func()
|
| +
|
| + def a_func():
|
| + return AClass()
|
| +
|
| + a_var = a_func()
|
| + #a_var.${codeassist}
|
| +
|
| + another_var = a_var
|
| + #another_var.${codeassist}
|
| + #another_var.call_a_func().${codeassist}
|
| +
|
| +
|
| +Basic support for builtin types:
|
| +
|
| +.. code-block:: python
|
| +
|
| + a_list = [AClass(), AClass()]
|
| + for x in a_list:
|
| + pass
|
| + #x.${codeassist}
|
| + #a_list.pop().${codeassist}
|
| +
|
| + a_dict = ['text': AClass()]
|
| + for key, value in a_dict.items():
|
| + pass
|
| + #key.${codeassist}
|
| + #value.${codeassist}
|
| +
|
| +Enhanced static returned object inference:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class C(object):
|
| +
|
| + def c_func(self):
|
| + return ['']
|
| +
|
| + def a_func(arg):
|
| + return arg.c_func()
|
| +
|
| + a_var = a_func(C())
|
| +
|
| +Here rope knows that the type of a_var is a ``list`` that holds
|
| +``str``\s.
|
| +
|
| +Supporting generator functions:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class C(object):
|
| + pass
|
| +
|
| + def a_generator():
|
| + yield C()
|
| +
|
| +
|
| + for c in a_generator():
|
| + a_var = c
|
| +
|
| +Here the objects ``a_var`` and ``c`` hold are known.
|
| +
|
| +Rope collects different types of data during SOA, like per name data
|
| +for builtin container types:
|
| +
|
| +.. code-block:: python
|
| +
|
| + l1 = [C()]
|
| + var1 = l1.pop()
|
| +
|
| + l2 = []
|
| + l2.append(C())
|
| + var2 = l2.pop()
|
| +
|
| +Here rope can easily infer the type of ``var1``. But for knowing the
|
| +type of ``var2``, it needs to analyze the items inserted into ``l2``
|
| +which might happen in other modules. Rope can do that by running SOA on
|
| +that module.
|
| +
|
| +You might be wondering is there any reason for using DOA instead of
|
| +SOA. The answer is that DOA might be more accurate and handles
|
| +complex and dynamic situations. For example in:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def f(arg):
|
| + return eval(arg)
|
| +
|
| + a_var = f('C')
|
| +
|
| +SOA can no way conclude the object ``a_var`` holds but it is really
|
| +trivial for DOA. What's more SOA only analyzes calls in one module
|
| +while DOA analyzes any call that happens when running a module. That
|
| +is, for achieving the same result as DOA you might need to run SOA on
|
| +more than one module and more than once (not considering dynamic
|
| +situations.) One advantage of SOA is that it is much faster than DOA.
|
| +
|
| +
|
| +Dynamic Object Analysis
|
| +-----------------------
|
| +
|
| +``PyCore.run_module()`` runs a module and collects object information if
|
| +``perform_doa`` project config is set. Since as the program runs rope
|
| +gathers type information, the program runs much slower. After the
|
| +program is run, you can get better code assists and some of the
|
| +refactorings perform much better.
|
| +
|
| +``mod1.py``:
|
| +
|
| +.. code-block:: python
|
| +
|
| + def f1(param):
|
| + pass
|
| + #param.${codeassist}
|
| + #f2(param).${codeassist}
|
| +
|
| + def f2(param):
|
| + #param.${codeassist}
|
| + return param
|
| +
|
| +Using code assist in specified places does not give any information and
|
| +there is actually no information about the return type of ``f2`` or
|
| +``param`` parameter of ``f1``.
|
| +
|
| +``mod2.py``:
|
| +
|
| +.. code-block:: python
|
| +
|
| + import mod1
|
| +
|
| + class A(object):
|
| +
|
| + def a_method(self):
|
| + pass
|
| +
|
| + a_var = A()
|
| + mod1.f1(a_var)
|
| +
|
| +Retry those code assists after performing DOA on ``mod2`` module.
|
| +
|
| +
|
| +Builtin Container Types
|
| +'''''''''''''''''''''''
|
| +
|
| +Builtin types can be handled in a limited way, too:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class A(object):
|
| +
|
| + def a_method(self):
|
| + pass
|
| +
|
| + def f1():
|
| + result = []
|
| + result.append(A())
|
| + return result
|
| +
|
| + returned = f()
|
| + #returned[0].${codeassist}
|
| +
|
| +Test the the proposed completions after running this module.
|
| +
|
| +
|
| +Guessing Function Returned Value Based On Parameters
|
| +----------------------------------------------------
|
| +
|
| +``mod1.py``:
|
| +
|
| +.. code-block:: python
|
| +
|
| + class C1(object):
|
| +
|
| + def c1_func(self):
|
| + pass
|
| +
|
| + class C2(object):
|
| +
|
| + def c2_func(self):
|
| + pass
|
| +
|
| +
|
| + def func(arg):
|
| + if isinstance(arg, C1):
|
| + return C2()
|
| + else:
|
| + return C1()
|
| +
|
| + func(C1())
|
| + func(C2())
|
| +
|
| +After running ``mod1`` either SOA or DOA on this module you can test:
|
| +
|
| +``mod2.py``:
|
| +
|
| +.. code-block:: python
|
| +
|
| + import mod1
|
| +
|
| + arg = mod1.C1()
|
| + a_var = mod1.func(arg)
|
| + a_var.${codeassist}
|
| + mod1.func(mod1.C2()).${codeassist}
|
| +
|
| +
|
| +Automatic SOA
|
| +-------------
|
| +
|
| +When turned on, it analyzes the changed scopes of a file when saving
|
| +for obtaining object information; So this might make saving files a
|
| +bit more time consuming. By default, this feature is turned on, but
|
| +you can turn it off by editing your project ``config.py`` file, though
|
| +that is not recommended.
|
| +
|
| +
|
| +Validating Object DB
|
| +--------------------
|
| +
|
| +Since files on disk change over time project objectdb might hold
|
| +invalid information. Currently there is a basic incremental objectdb
|
| +validation that can be used to remove or fix out of date information.
|
| +Rope uses this feature by default but you can disable it by editing
|
| +``config.py``.
|
| +
|
| +
|
| +Custom Source Folders
|
| +=====================
|
| +
|
| +By default rope searches the project for finding source folders
|
| +(folders that should be searched for finding modules). You can add
|
| +paths to that list using ``source_folders`` project config. Note that
|
| +rope guesses project source folders correctly most of the time. You
|
| +can also extend python path using ``python_path`` config.
|
| +
|
| +
|
| +Version Control Systems Support
|
| +===============================
|
| +
|
| +When performing refactorings some files might need to be moved (when
|
| +renaming a module) or new files might be created. When using a VCS,
|
| +rope detects and uses it to perform file system actions.
|
| +
|
| +Currently Mercurial_, GIT_, Darcs_ and SVN (using pysvn_ library) are
|
| +supported. They are selected based on dot files in project root
|
| +directory. For instance, Mercurial will be used if `mercurial` module
|
| +is available and there is a ``.hg`` folder in project root. Rope
|
| +assumes either all files are under version control in a project or
|
| +there is no version control at all. Also don't forget to commit your
|
| +changes yourself, rope doesn't do that.
|
| +
|
| +Adding support for other VCSs is easy; have a look at
|
| +`library.rst`_.
|
| +
|
| +.. _pysvn: http://pysvn.tigris.org
|
| +.. _Mercurial: http://selenic.com/mercurial
|
| +.. _GIT: http://git.or.cz
|
| +.. _darcs: http://darcs.net
|
|
|