././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1637593692.3753905
rope-0.22.0/ 0000775 0001750 0001750 00000000000 00000000000 012433 5 ustar 00lieryan lieryan ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1637593508.0
rope-0.22.0/CHANGELOG.md 0000664 0001750 0001750 00000005646 00000000000 014257 0 ustar 00lieryan lieryan # **Upcoming release**
## Syntax support
- #443 Implement `yield from` syntax support to patchedast.py
## Bug fixes
- #445, #446 Improve empty tuple and handling of parentheses around tuple
- #270, #432 Fix rename import statement with dots and as keyword (@climbus)
# Release 0.21.1
Date: 2021-11-11
## Bug fixes
- #441. Start publishing wheel packages to allow offline installs
# Release 0.21.0
Date: 2021-10-18
## Syntax support
- #392, #316 Handle `global` keyword when extracting method (@climbus)
- context manager:
- #387, #433 Implement extract refactoring for code containing `async with` (@lieryan)
- #398, #104 Fix parsing of nested `with` statement/context manager (@climbus)
- list/set/dict/generator comprehension scope issues:
- #422 Added scopes for comprehension expressions as part of #293 (@climbus)
- #426, #429 Added support for checking scopes by offset as part of #293 (@climbus)
- #293, #430 Fix renaming global var affects list comprehension (@climbus)
- #395, #315 Reuse of variable in comprehensions confuses method extraction (@climbus)
- #436 Fix error `TypeError: 'PyDefinedObject' object is not subscriptable` (@lieryan)
- f-string:
- #303, #420 Fix inlining into f-string containing quote characters (@lieryan)
- inline assignment/walrus operator:
- #423 Fix `AttributeError: '_ExpressionVisitor' object has no attribute 'defineds'` (@lieryan)
## Bug fixes
- #391, #376 Fix improper replacement when extracting attribute access expression with `similar=True` (@climbus)
- #396 Fix improper replacement when extracting index access expression with `similar=True` (@lieryan)
## New feature
- #434 Move read() to FileSystemCommands (@lieryan)
## Misc
- #410 Setup all-contributors bot (@lieryan)
- #404 Blacken source code, rope now follows black code style (@climbus)
- #399 Add Github Actions to enforce black code style (@lieryan)
- #403 Remove plain 'unittest' only runner (@lieryan)
# Release 0.20.1
Date: 2021-09-18
## Bug fixes
- Fix caller of `_namedexpr_last()` throwing exception due to returning unexpected list
instead of boolean
# Release 0.20.0
Date: 2021-09-18
## New feature
- #377 Added the ability to extract method to @staticmethod/@classmethod (@climbus)
- #374 Changed Organize import to keep variables listed in `__all__`
- Change default .ropeproject/config.py to ignore code in folders named
.venv and venv (@0x1e02)
## Syntax support
- #372 Add extract method refactoring of code containing `exec` (@ceridwen)
- #389 Add extract method refactoring of code containing `async def`, `async for`, and `await`
- #365, #386 Support extract method of expressions containing inline assignment (walrus operator)
## Bug fixes
- #380 Fix list of variables that are returned and/or turned into argument when extracting method in a loop
# Previous releases
[Changelog from pre-0.10.0](https://github.com/python-rope/rope/blob/595af418e7e7e844dcce600778e1c650c2fc0ba1/docs/done.rst).
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1631106611.0
rope-0.22.0/COPYING 0000664 0001750 0001750 00000016744 00000000000 013502 0 ustar 00lieryan lieryan GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1632642863.0
rope-0.22.0/MANIFEST.in 0000664 0001750 0001750 00000000226 00000000000 014171 0 ustar 00lieryan lieryan include README.rst COPYING setup.py MANIFEST.in CHANGELOG.md
recursive-include rope *.py
recursive-include docs *.rst
recursive-include ropetest *.py
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1637593692.3753905
rope-0.22.0/PKG-INFO 0000664 0001750 0001750 00000003531 00000000000 013532 0 ustar 00lieryan lieryan Metadata-Version: 2.1
Name: rope
Version: 0.22.0
Summary: a python refactoring library...
Home-page: https://github.com/python-rope/rope
Author: Ali Gholami Rudi
Author-email: aligrudi@users.sourceforge.net
License: LGPL-3.0-or-later
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Operating System :: OS Independent
Classifier: Environment :: X11 Applications
Classifier: Environment :: Win32 (MS Windows)
Classifier: Environment :: MacOS X
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
Classifier: Natural Language :: English
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Topic :: Software Development
Description-Content-Type: text/x-rst
Provides-Extra: dev
License-File: COPYING
.. _GitHub python-rope / rope: https://github.com/python-rope/rope
=========================================================================
rope -- the world's most advanced open source Python refactoring library
=========================================================================
Overview
========
`Rope`_ is the world's most advanced open source Python refactoring library
(yes, I totally stole that tagline from Postgres).
.. _`rope`: https://github.com/python-rope/rope
Most Python syntax from Python 2.7 up to Python 3.9 is supported. Please file bugs and contribute
patches if you encounter gaps.
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1633427443.0
rope-0.22.0/README.rst 0000664 0001750 0001750 00000006167 00000000000 014134 0 ustar 00lieryan lieryan
.. _GitHub python-rope / rope: https://github.com/python-rope/rope
=========================================================================
rope -- the world's most advanced open source Python refactoring library
=========================================================================
Overview
========
`Rope`_ is the world's most advanced open source Python refactoring library
(yes, I totally stole that tagline from Postgres).
.. _`rope`: https://github.com/python-rope/rope
Most Python syntax from Python 2.7 up to Python 3.9 is supported. Please file bugs and contribute
patches if you encounter gaps.
Getting Started
===============
* `How to use Rope in my IDE or Text editor? `_
* List of features: ``_
* Overview of some of rope's features: ``_
* Using as a library: ``_
* Contributing: ``_
To change your project preferences edit
``$PROJECT_ROOT/.ropeproject/config.py`` where ``$PROJECT_ROOT`` is
the root folder of your project (this file is created the first time
you open a project).
Why use Rope?
=============
- Rope aims to provide powerful and safe refactoring
- Rope is light on dependency, Rope only depends on Python itself
- Unlike PyRight or PyLance, Rope does not depend on Node.js
- Unlike PyLance or PyCharm, Rope is open source.
- Unlike PyRight and PyLance, Rope is written in Python itself, so if you experience problems, you would be able to debug and hack it yourself in a language that you are already familiar with
- In comparison to Jedi, Rope is focused on refactoring. While Jedi provides some basic refactoring capabilities, Rope supports many more advanced refactoring operations and options that Jedi does not.
Bug Reports
===========
Send your bug reports and feature requests at `python-rope's issue tracker`_ in Github.
.. _`python-rope's issue tracker`: https://github.com/python-rope/rope/issues
Maintainers
===========
Current active maintainers of Rope are Matej Cepl (`@mcepl`_) and Lie Ryan (`@lieryan`_).
Special Thanks
==============
Many thanks the following people:
- Ali Gholami Rudi (`@aligrudi`_) for initially creating the initial Rope project and most of Rope's code
- Nick Smith (`@soupytwist`_) as former Rope maintainer
- `all of our current and former contributors`_
- authors of editor integrations
.. _`@aligrudi`: https://github.com/aligrudi
.. _`@soupytwist`: https://github.com/soupytwist
.. _`@lieryan`: https://github.com/lieryan
.. _`@mcepl`: https://github.com/mcepl
.. _`all of our current and former contributors`: https://github.com/python-rope/rope/blob/master/CONTRIBUTORS.md
License
=======
This program is under the terms of LGPL v3+ (GNU Lesser General Public License).
Have a look at `COPYING`_ for more information.
.. _`docs/rope.rst`: docs/rope.html
.. _`docs/overview.rst`: docs/overview.html
.. _`docs/contributing.rst`: docs/contributing.html
.. _`docs/library.rst`: docs/library.html
.. _`COPYING`: COPYING
.. image:: https://secure.travis-ci.org/python-rope/rope.svg
:alt: Build Status
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1637593692.3473897
rope-0.22.0/docs/ 0000775 0001750 0001750 00000000000 00000000000 013363 5 ustar 00lieryan lieryan ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1632875930.0
rope-0.22.0/docs/contributing.rst 0000664 0001750 0001750 00000007161 00000000000 016631 0 ustar 00lieryan lieryan ======================
Contributing to Rope
======================
Getting Involved!
=================
Rope's main goal is being a good refactoring tool for python. It also
provides some IDE helpers. If you would like to contribute, you're
welcome to!
How to Help Rope?
=================
Rope's development happens in `python-rope's Github`_.
Use `python-rope's Github Issue Tracker`_ to discuss development-related issues:
* Send bug reports and request features
* Submit patches for bugs or new features
Use `python-rope's Github Discussion`_ for other discussions, such as:
* Help using rope
* Help integrating rope with text editors/tools
* Discuss your ideas
* Engage with the rope community
.. _`python-rope's Github`: https://github.com/python-rope/rope
.. _`python-rope's Github Issue Tracker`: https://github.com/python-rope/rope/issues
.. _`python-rope's Github Discussion`: https://github.com/python-rope/rope/discussions
Wish List
=========
You are welcome to make pull requests in `python-rope's Github Issue Tracker`_.
Here is a list of suggestions.
Issues
------
If this is your first time contributing in rope and you don't know where to start,
tickets labeled `good first issue`_ is a good place start.
The `unresolved issues list`_ in Github is the latest todo list.
There is also a rather outdated list in `dev/issues.rst`_. There
is a section called "unresolved issues"; it contains almost every kind
of task. This file will need some cleanup, thoughts, and discussions.
Pickup whichever you are most interested in. If you have ideas or questions
about them, don't hesitate to create a Github ticket for it.
.. _`good first issue`: https://github.com/python-rope/rope/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22
.. _`unresolved issues list`: https://github.com/python-rope/rope/issues
.. _`dev/issues.rst`: dev/issues.rst
Write Plugins For Other IDEs
----------------------------
See ropemacs_, ropevim_, eric4_ and ropeide_.
.. _ropemacs: http://rope.sf.net/ropemacs.rst
.. _ropevim: http://rope.sf.net/ropevim.rst
.. _ropeide: http://rope.sf.net/ropeide.rst
.. _eric4: http://www.die-offenbachs.de/eric/index.rst
Rope Structure
==============
Rope package structure:
* `rope.base`: the base part of rope
* `rope.refactor`: refactorings and tools used in them
* `rope.contrib`: IDE helpers
Have a look at ``__init__.py`` of these packages or `library.rst`_ for
more information.
.. _`library.rst`: library.rst
Source Repository
=================
Rope uses GitHub_. The repository exists at
`https://github.com/python-rope/rope`_.
Submitting pull requests
========================
Pull requests are welcome.
Follow the instructions on GitHub_ on how to setup Git and fork the
`python-rope/rope`_ repository. Once your changes are ready, send a
`pull request`_ for review.
Programming Style
-----------------
* Follow `black codestyle`_
* Follow :PEP:`8`.
* Use four spaces for indentation.
* Include good unit-tests when appropriate.
* Rope test suite should pass after patching
.. _`black codestyle`: https://github.com/psf/black
Testing
-------
Rope uses `pytest`_ as a test runner per default (although the
tests are strictly unittest-based), so running::
pytest -v
runs all tests. Make sure to have complete test suite passing and
add new tests for the changes you are providing with each new
submission.
All required packages for development could be installed with::
pip install -e .[dev]
.. _GitHub: http://github.com/
.. _`python-rope/rope`: https://github.com/python-rope/rope
.. _`pull request`: https://help.github.com/articles/using-pull-requests
.. _`pytest`: https://pytest.org/
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1637593692.3473897
rope-0.22.0/docs/dev/ 0000775 0001750 0001750 00000000000 00000000000 014141 5 ustar 00lieryan lieryan ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1632657719.0
rope-0.22.0/docs/dev/issues.rst 0000664 0001750 0001750 00000007504 00000000000 016214 0 ustar 00lieryan lieryan =============
Rope Issues
=============
NOTE: this file is severely outdated and is no longer actively used by rope to keep track of issues. It is kept only for historical purpose. Use the `Issue Tracker`_ instead
.. _`Issue Tracker`: https://github.com/python-rope/rope/issues
Unresolved Issues
=================
* purging out less accurate callinfos when better ones appear?
* using properties without calling its get?
* global variable inlines
* transform and extension modules
* merging extract and usefunction
* caching instances of PyObject
* moving a group of elements together
* temps might be read after body in usefunction or extract
* usefunction and function returns
* usefunction on methods
* extracted functions should be inserted before using class bodies
* adding "referenced later" wildcard argument to restructurings?
* adding "change references" wildcard argument to restructurings?
* ideas for more custom wildcards
* custom wildcards and recursive patterns
* custom restructuring wildcard patterns and replacements
* not reimporting back imports after moving
* importing compressed objectdb/history data?
* not applying all commenting mechanisms always in codeassist
* fixing try blocks before current line in code_assist
* better tests for patchedast
* import actions with more that one phase and filtering problems
* handle long imports should work on filtered imports unconditionally?
* extracting subexpressions; look at `extracttest` for more info
* unignored files that are not under version control
* inline fails when there is an arg mismatch
* evaluate function parameter defaults in staticoi?
* saving diffs instead of old contents in ChangeContents?
* handling tuple parameters
* extract class
* analyzing function decorators
* generate ... and implicit interfaces
* generate method and class hierarchies
* lambdas as functions; consider their parameters
* renaming similarly named variables
* handling the return type of ``yield`` keyword
* not writing unchanged objectdb and history?
To Be Reviewed
==============
* review patchedast; make it faster
* lots of estimations in codeanalyze in WordRangeFinder
* review objectdb modules
* how concluded data are held for star imports
Insert Before In Restructurings
===============================
Consider a restructuring like this::
pattern: ${a} if ${b} else ${c}
goal: replacement
before: if ${b}:\n replacement = ${a}\nelse:\n replacement = ${c}
Memory Management
=================
These are the places in which rope spends most of the memory it
consumes:
* PyCore: for storing PyModules
* ObjectInfo: for storing object information
* History: for storing changes
We should measure the amount of memory each of them use to make
decisions.
Custom Restructuring Wildcards
==============================
There is a need to add more custom wildcards in restructuring
patterns. But adding all such needs to `similarfinder` module makes
it really complex. So I think adding the ability to extend them is
useful.
Sometimes wildcards can be customized. For instance one might want to
match the function calls only if ``p1`` is passed in the arguments.
They can be specified in wildcard arguments.
Since matched wildcards can appear in the goal pattern, each wildcard
should have a corresponding replacement wildcard. Each replacement
might be customized in each place it appears; for instance
``${mycall:-p1}`` might mean to remove ``p1`` argument.
Wildcard Format
---------------
All wildcards should appear as ``${name}``. The type of wildcards and
their parameters can be specified using the ``args`` argument of
``Restructuring()``.
Ideas:
* Maybe we can put checks inside args, too::
pattern: ${project:type=rope.base.project.Project}.pycore
But what should be done when a variable appears twice::
pattern: ${a:type=__builtin__.int} + ${a}
Examples
--------
.. ...
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1636636936.0
rope-0.22.0/docs/library.rst 0000664 0001750 0001750 00000064651 00000000000 015575 0 ustar 00lieryan lieryan =========================
Using Rope As A Library
=========================
If you need other features, send a feature request. Have a look at
`contributing.rst`_.
.. contents:: Table of Contents
Quick Start
===========
This section will help you get started as soon as possible.
Making A Project
----------------
The first thing you should do is make a project:
.. code-block:: python
import rope.base.project
myproject = rope.base.project.Project('/path/to/myproject')
It's good to know that:
* A project is a folder in the file-system.
* It can contain anything.
* Rope searches for python modules and packages inside a project when
needed.
* Refactorings only change files and folders inside the project that
has been passed to them.
* Out of project modules that are imported from a module inside a
project are handled but never changed by refactorings.
* Rope makes a rope folder inside projects. By default the name of this
folder is ``.ropeproject``, but that can be changed using the
constructor's ``ropefolder`` parameter. Passing ``None`` prevents rope
from making this folder.
* Rope uses the ``.ropeproject`` folder for things like saving object
information and loading project configurations.
* Project preferences can be configured by passing options to the
constructor or in ``.ropeproject/config.py``. See the default
``config.py``, ``rope.base.default_config`` module, for more
information.
* All configurations that are available in the ``config.py`` file can
be specified as keyword parameters to the ``Project`` constructor.
These parameters override the ones in the ``config.py`` file.
* Each project has a set of ignored resource patterns. You can use it
to tell rope to ignore files and folders matching certain patterns.
* The ``.ropeproject`` folder can be safely copied in other clones of
a project if you don't want to lose your objectdb and history.
Library Utilities
-----------------
The `rope.base.libutils`_ module provides tools that make using rope as a
library easier. We'll talk more about this module later.
What Are These `Resource`\s?
----------------------------
In rope, files and folders in a project are accessed through
``rope.base.resources.Resource`` objects. It has two subclasses
``File`` and ``Folder``. What we care about is that refactorings and
``Change``\s (we'll talk about them later) use resources.
There are two options for creating a ``Resource`` for a path in a project.
The first approach uses the `Project.get_resource()`_ method.
.. code-block:: python
from rope.base import project
myresource = myproject.get_resource('/path/to/resource')
However, it's preferable to use the ``libutils.path_to_resource()``
function, because it's more flexible and offers a unified way to create
resources. It takes a ``project`` and ``path`` as parameters with an
optional ``type``. The ``type`` parameter, with values ``file`` or
``folder``, can create a resource for an object that doesn't exist yet.
.. code-block:: python
from rope.base import libutils
myresource = libutils.path_to_resource(myproject, '/path/to/resource')
Consider we have a resource. How can we know anything about it? The
answer is to use its ``path`` and ``real_path`` attributes.
``Resource.real_path`` is the absolute path of the resource in the
file-system. The ``Resource.path`` attribute contains the address of a
resource relative to the project's root.
Performing Refactorings
-----------------------
As a short example of performing refactorings, we'll show how to extract
a variable from a file. First we need the ``Resource`` object that
points to a file in a project:
.. code-block:: python
resource = libutils.path_to_resource(myproject, '/path/to/my/module.py')
Now we can make our Refactoring class:
.. code-block:: python
from rope.refactor.extract import ExtractVariable
extractor = ExtractVariable(myproject, resource, start, end)
Where ``start`` and ``end`` are the offsets of the region to extract in
``resource``. Be careful when calculating the offsets. DOS
line-endings and multi-byte characters are considered to be one
character. This is actually easier for IDEs, since most GUI libraries
handle those cases for you.
Next, the IDE ask the user to configure refactoring options, like
specifying the name of the extracted variable.
After that, we can calculate the changes:
.. code-block:: python
changes = extractor.get_changes('extracted_variable')
Each refactoring returns a ``rope.base.change.Change`` object that holds
the changes it made. Calculating those changes can be time consuming.
See the `rope.base.taskhandle.TaskHandle`_ section for measuring its
progress or interrupting it.
Previewing And Performing Changes
---------------------------------
As mentioned in the last section each refactoring returns a
``rope.base.change.Change`` object. Now how can we know what it
contains and how to perform it?
*Previewing*:
You can use ``changes.get_description()`` to get a preview. It is useful
when you don't care much about the format. Otherwise you can use the
``changes`` object directly. See the documentation in
``rope.base.change`` module.
*Performing*:
The easiest way for performing the refactoring is to use the
`Project.do()`_ method:
.. code-block:: python
myproject.do(changes)
If you want to perform the changes yourself, you have two options.
Note that the main reason for performing the changes manually is
handling version control systems that are not supported by rope.
1. The first approach is to use `rope.base.fscommands`_ (see `Writing A
FileSystemCommands`_). The changes can be performed as before using
`Project.do()`_.
2. The second approach is to perform the changes manually based on the
returned ``changes`` object (again see the documentation in
``rope.base.change`` module). If this approach is used you cannot undo
the refactoring using ``project.history.undo()``.
*Updating Open Buffers In IDEs*:
Usually editors need to reload the files changed by rope. You can use
``Change.get_changed_resources()`` to get the list of resources that
need to be reloaded.
Validating The Project
----------------------
When using rope as a library, you probably change the files in it in
parallel (for example in IDEs). To force rope to invalidate cached
information about resources that have been removed or changed outside
rope, you should call the `Project.validate()`_ method. You can pass a
resource to this method. For example:
.. code-block:: python
myproject.validate(resource)
This validates all files and directories in resource. Call this
function every time you want use rope (i.e., before performing
refactorings).
Performing Static Object Analysis
---------------------------------
One of the greatest strengths of rope is its Static Object Analysis
(SOA). It analyzes function calls and assignments to collect the types
of objects passed to the function. Rope uses the collected data to infer
the type of function parameters, return values, and the objects stored
in built-in containers. The function
``rope.base.libutils.analyze_modules()`` performs SOA on all modules in
the project. It is recommended that you call this function occasionally,
and especially before performing large refactorings. Note that analyzing
all modules of a project may take a long time.
If you have ``automatic_soa`` set, which instructs rop to analyze the
changed scopes of modules, then you should report the changes by calling
``rope.base.libutils.report_change()`` when saving files, as follows:
.. code-block:: python
# Save the new contents.
old_contents = resource.read()
resource.write(new_contents)
# Inform rope about the change.
libutils.report_change(myproject, path, old_contents)
Note, however, that the use of ``automatic_soa`` is discouraged, because it may
slow down saving considerably.
Closing The Project
-------------------
`Project.close()`_ closes a project's open resources. Always call this
function when you don't need a project anymore:
.. code-block:: python
myproject.close()
``rope.base.libutils``
======================
The ``rope.base.libutils`` module contains functions that make life
easier for building refactoring tools. In some cases, the functions
offer a unified way to access or create objects. You're encouraged to
use ``rope.base.libutils`` functions whenever possible, because the APIs
here may not be as volatile as class methods.
``libutils.analyze_module()``
------------------------------
Perform static object analysis on a Python file in the project. Note,
this may be a very time consuming task.
.. code-block:: python
libutils.analyze_module(myproject, resource)
``libutils.analyze_modules()``
------------------------------
Perform static object analysis on all Python files in the project. Note
that it might take a long time to finish.
.. code-block:: python
libutils.analyze_modules(myproject)
``libutils.get_string_module()``
--------------------------------
Returns a ``rope.base.pyobjects.PyModule`` object for the code string.
An optional ``resource`` argument can be specified for the resource this
code is associated with. If ``force_errors` is ``True``, then
``rope.base.exceptions.ModuleSyntaxError`` is raised when the code has
syntax errors. Otherwise, syntax errors are silently ignored. Note that
``force_errors`` overrides the ``ignore_syntax_errors`` project
configuration flag.
.. code-block:: python
pymodule = libutils.get_string_module(myproject, source)
``libutils.get_string_scope()``
-------------------------------
Get the ``rope.base.pyscopes.GlobalScope`` object for the code string.
This is the outermost scope of the code encompassing the whole module.
.. code-block:: python
scope = libutils.get_string_scope(myproject, source)
``libutils.is_python_file()``
-----------------------------
Returns ``True`` if the resource is a Python file.
.. code-block:: python
libutils.is_python_file(myproject, resource)
``libutils.modname()``
----------------------
Retrieves the dotted path string to the module that contains that given
resource.
.. code-block:: python
# If resource is 'path/to/resource.py' relative to the project's root
# directory, this returns the string: 'path.to.resource'.
module_name = libutils.modname(resource)
``libutils.path_relative_to_project_root()``
--------------------------------------------
Retrieve the path relative to the project's root directory.
.. code-block:: python
# Get the path relative to the project's root directory.
relpath = libutils.relative(myproject.address, path)
``libutils.path_to_resource()``
-------------------------------
Get the resource --- a file or folder --- at the given path. An optional
``type`` argument can be used if the resource doesn't yet exist. The
values for ``type`` are the strings ``'file'`` or ``'folder'``.
.. code-block:: python
# Resource for an existing file.
myfile = libutils.path_to_resource(myproject, '/path/to/file.py')
# Resource for a non-existing folder.
new_folder = libutils.path_to_resource(myproject, '/path/to/folder', type='folder')
``rope.base.project.Project``
=============================
You can create a project by:
.. code-block:: python
project = Project(root_address)
Where the ``root_address`` is the root folder of your project.
A project has some useful attributes. ``Project.address`` is the
address of the root folder of a project. ``Project.root`` is a
``Folder`` object that points to that folder.
`Project.do()`
--------------
Used to commit changes returned by refactorings:
.. code-block:: python
project.do(changes)
`Project.history`
-----------------
A ``rope.base.history.History`` object. You can use its ``undo`` and
``redo`` methods for undoing or redoing changes. Note that you can use
this only if you have committed your changes using rope.
`Project.validate()`
--------------------
When using rope as a library, you will probably change the files in that
project in parallel (for example in IDEs). To force rope to validate
cached information about resources that have been removed or changed
outside rope, you should call ``Project.validate()``. You should pass a
resource to this method. For example:
.. code-block:: python
project.validate(project.root)
This validates all files and directories in the project and clears the
cache of all recorded changes.
`Project.close()`
-----------------
Closes a project's open resources. Always call this function when you
don't need a project anymore. Currently it closes the files used for
storing object information and project history. Because some parts of
these files are in memory for efficiency, not closing a project might
put them in an inconsistent state.
`rope.base.fscommands`
======================
The ``rope.base.fscommands`` module implements basic file system
operations that rope needs to perform. The main reason for the
existence of this module is supporting version control systems. Have a
look at ``FileSystemCommands`` and ``SubversionCommands`` in the same
module. If you need other version control systems you can write a new
class that provides this interface. ``rope.base.project.Project``
accepts an ``fscommands`` argument. You can use this argument to force
rope to use your new class.
``.ropeproject`` Folder
=======================
Since version ``0.5``, rope makes a ``.ropeproject`` folder in the
project by default for saving project configurations and data. The name
of this folder is passed to the constructor if you want to change that.
You can force rope not to make such a folder by passing ``None``.
If such a folder exists, rope loads the ``config.py`` file in that
folder. It might also use it for storing object information and
history.
`rope.base.pycore.PyCore`
=========================
Provides useful methods for managing python modules and packages. Each
project has a ``PyCore`` that can be accessed using the
``Project.pycore`` attribute.
``PyCore.run_module()`` runs a resource. When running, it collects type
information to do dynamic object inference. For this reason modules
run much slower.
Also ``Pycore.analyze_module()`` collects object information for a
module. The collected information can be used to enhance rope's
static object inference.
`rope.base.taskhandle.TaskHandle`
=================================
A TaskHandle can be used for stopping and monitoring the progress of
time consuming tasks, like some refactorings. The ``Project.do()`` and
``Refactoring.get_changes()`` methods for most refactorings take a
keyword parameter called ``task_handle``. You can pass a ``TaskHandle``
object to them. A ``TaskHandle`` can be used for interrupting or
observing a task.
Always pass ``task_handle`` as keyword argument. It will always be the
last argument, and new arguments of the refactoring are added before it.
A task might consist of a few ``JobSet``\s. Each ``JobSet`` performs a
few jobs. For instance calculating the changes for renaming a method in
a class hierarchy has two job sets: one to find the classes for
constructing the class hierarchy and another to change the occurrences.
The ``TaskHandle.current_jobset()`` returns the most recent ``JobSet``
or ``None`` if none has been started. You can use the methods of
``JobSet`` for obtaining information about the current job. So you
might want to do something like:
.. code-block:: python
import rope.base.taskhandle
handle = rope.base.taskhandle.TaskHandle("Test Task")
def update_progress():
jobset = handle.current_jobsets()
if jobset:
text = ''
# getting current job set name
if jobset.get_name() is not None:
text += jobset.get_name()
# getting active job name
if jobset.get_active_job_name() is not None:
text += ' : ' + jobset.get_active_job_name()
# adding done percent
percent = jobset.get_percent_done()
if percent is not None:
text += ' ... %s percent done' % percent
print text
handle.add_observer(update_progress)
changes = renamer.get_changes('new_name', task_handle=handle)
Also you can use something like this for stopping the task:
.. code-block:: python
def stop():
handle.stop()
After calling ``stop()``, the thread that is executing the task will
be interrupted by a ``rope.base.exceptions.InterruptedTaskError``
exception.
Refactorings
============
Have a look at ``rope.refactor`` package and its sub-modules. For
example for performing a move refactoring you can create a ``Move``
object like this:
.. code-block:: python
mover = Move(project, resource, offset)
Where ``resource`` and ``offset`` is the location to perform the
refactoring.
Then you can commit the changes by it using the ``get_changes()``
method:
.. code-block:: python
project.do(mover.get_changes(destination))
Where the ``destination`` module/package is the destination resource for
move refactoring. Other refactorings classes have a similar interface.
List Of Refactorings
--------------------
Here is the list of refactorings rope provides. (Note that this list
might be out of date.) For more information about these refactoring see
pydocs in their modules and the unit-tests in the ``ropetest/refactor/``
folder.
* ``rope.refactor.rename``:
Rename something in the project. See the example below.
* ``rope.refactor.move``:
Move a python element in the project.
* ``rope.refactor.restructure``:
Restructure code. See the example below.
* ``rope.refactor.extract``:
Extract methods/variables.
* ``rope.refactor.inline``:
Inline occurrences of a method/variable/parameter.
* ``rope.refactor.usefunction``:
Try to use a function wherever possible.
* ``rope.refactor.method_object``:
Transform a function or a method to a method object.
* ``rope.refactor.change_signature``:
Change the signature of a function/method.
* ``rope.refactor.introduce_factory``:
Introduce a factory for a class and changes all constructors to use
it.
* ``rope.refactor.introduce_parameter``:
Introduce a parameter in a function.
* ``rope.refactor.encapsulate_field``:
Generate a getter/setter for a field and changes its occurrences to
use them.
* ``rope.refactor.localtofield``:
Change a local variable to field.
* ``rope.refactor.topackage``:
Transform a module to a package with the same name.
* ``rope.refactor.importutils``:
Perform actions like organize imports.
Refactoring Resources Parameter
-------------------------------
Some refactorings, restructure and find occurrences accept an argument
called ``resources``. If it is a list of `File`\s, all other
resources in the project are ignored and the refactoring only analyzes
them. If it is ``None`` all python modules in the project will be
analyzed. Using this parameter, IDEs can let the user limit the files
on which a refactoring should be applied.
Examples
========
Rename
------
Using rename refactoring:
.. code-block:: python
# Creating a project
>>> from rope.base.project import Project
>>> project = Project('.')
# Working with files to create a module
>>> mod1 = project.root.create_file('mod1.py')
>>> mod1.write('a_var = 10\n')
# Alternatively you can use `generate` module.
# Creating modules and packages using `generate` module
>>> from rope.contrib import generate
>>> pkg = generate.create_package(project, 'pkg')
>>> mod2 = generate.create_module(project, 'mod2', pkg)
>>> mod2.write('import mod1\nprint mod1.a_var\n')
# We can use `Project.find_module` for finding modules, too
>>> assert mod2 == project.find_module('pkg.mod2')
# Performing rename refactoring on `mod1.a_var`
>>> from rope.refactor.rename import Rename
>>> changes = Rename(project, mod1, 1).get_changes('new_var')
>>> project.do(changes)
>>> mod1.read()
u'new_var = 10\n'
>>> mod2.read()
u'import mod1\nprint mod1.new_var\n'
# Undoing rename refactoring
>>> project.history.undo()
...
>>> mod1.read()
u'a_var = 10\n'
>>> mod2.read()
u'import mod1\nprint mod1.a_var\n'
# Cleaning up
>>> pkg.remove()
>>> mod1.remove()
>>> project.close()
Restructuring
-------------
The example for replacing occurrences of our ``pow`` function to ``**``
operator (see the restructuring section of `overview.rst`_):
.. code-block:: python
# Setting up the project
>>> from rope.base.project import Project
>>> project = Project('.')
>>> mod1 = project.root.create_file('mod1.py')
>>> mod1.write('def pow(x, y):\n result = 1\n'
... ' for i in range(y):\n result *= x\n'
... ' return result\n')
>>> mod2 = project.root.create_file('mod2.py')
>>> mod2.write('import mod1\nprint(mod1.pow(2, 3))\n')
>>> from rope.refactor import restructure
>>> pattern = '${pow_func}(${param1}, ${param2})'
>>> goal = '${param1} ** ${param2}'
>>> args = {'pow_func': 'name=mod1.pow'}
>>> restructuring = restructure.Restructure(project, pattern, goal, args)
>>> project.do(restructuring.get_changes())
>>> mod2.read()
u'import mod1\nprint(2 ** 3)\n'
# Cleaning up
>>> mod1.remove()
>>> mod2.remove()
>>> project.close()
See code documentation and test suites for more information.
.. _overview.rst: overview.rst
.. _contributing.rst: contributing.rst
Other Topics
============
Writing A `FileSystemCommands`
------------------------------
The ``get_changes()`` method of refactoring classes return a
``rope.base.change.Change`` object. You perform these changes by
calling ``Project.do()``. But as explained above some IDEs need to
perform the changes themselves.
Every change to the file-system in rope is commited using an object that
provides a ``rope.base.fscommands.FileSystemCommands`` interface. As
explained above in `rope.base.fscommands`_ section, rope uses this
interface to handle different VCSs.
You can implement your own fscommands object:
.. code-block:: python
class MyFileSystemCommands(object):
def create_file(self, path):
"""Create a new file"""
# ...
def create_folder(self, path):
"""Create a new folder"""
# ...
def move(self, path, new_location):
"""Move resource at `path` to `new_location`"""
# ...
def remove(self, path):
"""Remove resource"""
# ...
def write(self, path, data):
"""Write `data` to file at `path`"""
# ...
def read(self, path):
"""Read `data` from file at `path`"""
# ...
And you can create a project like this:
.. code-block:: python
my_fscommands = MyFileSystemCommands()
project = rope.base.project.Project('~/myproject',
fscommands=my_fscommands)
`rope.contrib.codeassist`
-------------------------
The ``rope.contrib`` package contains modules that use rope base parts
and provide useful features. ``rope.contrib.codeassist`` module can be
used in IDEs:
.. code-block:: python
from rope.ide import codeassist
# Get the proposals; you might want to pass a Resource
proposals = codeassist.code_assist(project, source_code, offset)
# Sorting proposals; for changing the order see pydoc
proposals = codeassist.sorted_proposals(proposals)
# Where to insert the completions
starting_offset = codeassist.starting_offset(source_code, offset)
# Applying a proposal
proposal = proposals[x]
replacement = proposal.name
new_source_code = (source_code[:starting_offset] +
replacement + source_code[offset:])
``maxfixes`` parameter of ``code_assist`` decides how many syntax errors
to fix. The default value is one. For instance:
.. code-block:: python
def f():
g(my^
myvariable = None
def g(p):
invalid syntax ...
will report ``myvariable``, only if ``maxfixes`` is greater than 1.
``later_locals``, if ``True``, forces rope to propose names that are
defined later in current scope. It is ``True`` by default. For
instance:
.. code-block:: python
def f():
my^
myvariable = None
will not report ``myvariable``, if ``later_locals`` is ``False``.
See pydocs and source code for more information (other functions in
this module might be interesting, too; like ``get_doc``,
``get_definition_location``).
`rope.contrib.findit`
---------------------
``findit`` module provides ``find_occurrences()`` for finding
occurrences of a name. Also the ``find_implementations()`` function
finds the places in which a method is overridden.
`rope.contrib.autoimport`
-------------------------
This module can be used to find the modules that provide a name. IDEs
can use this module to auto-import names. ``AutoImport.get_modules()``
returns the list of modules with the given global name.
``AutoImport.import_assist()`` tries to find the modules that have a
global name that starts with the given prefix.
Cross-Project Refactorings
--------------------------
``rope.refactor.multiproject`` can be used to perform a refactoring
across multiple projects.
Usually refactorings have a main project. That is the project that
contains the definition of the changing python name. Other projects
depend on the main one, and the uses of the changed name in them should
be updated.
Each refactoring changes only one project (the project passed to its
constructor). But we can use the ``MultiProjectRefactoring`` proxy to
perform a refactoring on other projects, too.
First we need to create a multi-project refactoring constructor. As
an example consider that we want to perform a rename refactoring:
.. code-block:: python
from rope.refactor import multiproject, rename
CrossRename = multiproject.MultiProjectRefactoring(rename.Rename,
projects)
Here ``projects`` is the list of dependant projects. It does not
include the main project. The first argument is the refactoring class
(such as ``Rename``) or factory function (like ``create_move``).
Next we can construct the refactoring:
.. code-block:: python
renamer = CrossRename(project, resource, offset)
We create the rename refactoring as we do for normal refactoings. Note
that ``project`` is the main project.
As mentioned above, other projects use the main project. Rope
automatically adds the main project to the python path of other
projects.
Finally we can calculate the changes. But instead of calling
``get_changes()`` (which returns main project changes only), we can
call ``get_all_changes()`` with the same arguments. It returns a list
of ``(project, changes)`` tuples. You can perform them manually by
calling ``project.do(changes)`` for each tuple, or use
``multiproject.perform()``:
.. code-block:: python
project_and_changes = renamer.get_all_changes('newname')
multiproject.perform(project_and_changes)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1633427443.0
rope-0.22.0/docs/overview.rst 0000664 0001750 0001750 00000072676 00000000000 016005 0 ustar 00lieryan lieryan ===============
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.
Key bindings
============
Rope is a library that is used in many IDE and Text Editors to perform
refactoring on Python coe. This page documents the details of the refactoring
operations but you would need consult the documentation for your IDE/Text
Editor client integration for the specific key bindings that are used by
those IDE/Text Editors.
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 Regular Method into staticmethod/classmethod
----------------------------------------------------
If you prefix the extracted method name with `@` or `$`, the generated
method will be created as a `classmethod` and `staticmethod` respectively.
For instance in:
.. code-block:: python
class A(object):
def f(self, a):
b = a * 2
if you select ``a * 2`` for method extraction and name the method
``@new_method``, you'll get:
.. code-block:: python
class A(object):
def f(self, a):
b = A.twice(a)
@classmethod
def new_method(cls, a):
return a * 2
Similarly, you can prefix the name with `$` to create a staticmethod instead.
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=2):
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
`Github Discussion`_.
.. _`Github Discussion`: https://github.com/python-rope/rope/discussions
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``.
Type Hinting
------------
Currently supported type hinting for:
- function parameter type, using function doctring (:type or @type)
- function return type, using function doctring (:rtype or @rtype)
- class attribute type, using class docstring (:type or @type). Attribute should by set to None or NotImplemented in class.
- any assignment, using type comments of PEP 0484 (in limited form).
If rope cannot detect the type of a function argument correctly (due to the
dynamic nature of Python), you can help it by hinting the type using
one of the following docstring syntax styles.
**Sphinx style**
http://sphinx-doc.org/domains.html#info-field-lists
::
def myfunction(node, foo):
"""Do something with a ``node``.
:type node: ProgramNode
:param str foo: foo parameter description
"""
node.| # complete here
**Epydoc**
http://epydoc.sourceforge.net/manual-fields.html
::
def myfunction(node):
"""Do something with a ``node``.
@type node: ProgramNode
"""
node.| # complete here
**Numpydoc**
https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt
In order to support the numpydoc format, you need to install the `numpydoc
`__ package.
::
def foo(var1, var2, long_var_name='hi'):
r"""A one-line summary that does not use variable names or the
function name.
...
Parameters
----------
var1 : array_like
Array_like means all those objects -- lists, nested lists,
etc. -- that can be converted to an array. We can also
refer to variables like `var1`.
var2 : int
The type above can either refer to an actual Python type
(e.g. ``int``), or describe the type of the variable in more
detail, e.g. ``(N,) ndarray`` or ``array_like``.
long_variable_name : {'hi', 'ho'}, optional
Choices in brackets, default first when optional.
...
"""
var2.| # complete here
**PEP 0484**
https://www.python.org/dev/peps/pep-0484/#type-comments
::
class Sample(object):
def __init__(self):
self.x = None # type: random.Random
self.x.| # complete here
Supported syntax of type hinting
''''''''''''''''''''''''''''''''
Currently rope supports the following syntax of type-hinting.
Parametrized objects:
- Foo
- foo.bar.Baz
- list[Foo] or list[foo.bar.Baz] etc.
- set[Foo]
- tuple[Foo]
- dict[Foo, Bar]
- collections.Iterable[Foo]
- collections.Iterator[Foo]
Nested expressions also allowed:
- collections.Iterable[list[Foo]]
TODO:
Callable objects:
- (Foo, Bar) -> Baz
Multiple interfaces implementation:
- Foo | Bar
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
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1636636936.0
rope-0.22.0/docs/release-process.rst 0000664 0001750 0001750 00000000574 00000000000 017217 0 ustar 00lieryan lieryan 1. Ensure tickets assigned to Milestones are up to date
2. Update CHANGELOG.md
3. Increment version number in ``rope/__init__.py``
4. Tag the release with the tag annotation containing the release information, e.g. ``git tag -s 0.21.0; git push origin 0.21.0``
5. ``python3 -m build``
6. ``twine upload -s dist/rope-$VERSION.{tar.gz,whl}``
7. Publish to Discussions Announcement
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1599360855.0
rope-0.22.0/docs/rope.rst 0000664 0001750 0001750 00000002704 00000000000 015065 0 ustar 00lieryan lieryan Features
========
Features implemented so far:
* Refactorings
* Rename everything!
* Extract method/local variable
* Move class/function/module/package/method
* Inline method/local variable/parameter
* Restructuring (like converting ``${a}.f(${b})`` to
``${b}.g(${a})`` where ``a: type=mymod.A``)
* Introduce factory
* Change method signature
* Transform module to package
* Encapsulate field
* Replace method with method object
* And a few others
* Refactoring Features
* Extracting similar statements in extract refactorings
* Fixing imports when needed
* Previewing refactorings
* Undo/redo refactorings
* Stopping refactorings
* Cross-project refactorings
* Basic implicit interfaces handling in rename and change signature
* Mercurial_, GIT_, Darcs_ and SVN (pysvn_ library) support in
refactorings
* IDE helpers
* Auto-completion
* Definition location
* Get pydoc
* Find occurrences
* Organize imports (remove unused and duplicate imports and sort them)
* Generating python elements
* Object Inference
* Static and dynamic object analysis
* Handling built-in container types
* Saving object information on disk and validating them
* Type hints using docstring or type comments PEP 0484
For more information see `overview.rst`_.
.. _overview.rst: overview.rst
.. _pysvn: http://pysvn.tigris.org
.. _Mercurial: http://selenic.com/mercurial
.. _GIT: http://git.or.cz
.. _darcs: http://darcs.net
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1637501450.0
rope-0.22.0/pyproject.toml 0000664 0001750 0001750 00000000226 00000000000 015347 0 ustar 00lieryan lieryan [tool.black]
target-version = ['py27', 'py33', 'py34', 'py35', 'py36', 'py37', 'py38', 'py39']
include = 'rope/.*\.pyi?$'
force-exclude = 'ropetest'
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1637593692.3513896
rope-0.22.0/rope/ 0000775 0001750 0001750 00000000000 00000000000 013400 5 ustar 00lieryan lieryan ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1637593692.3513896
rope-0.22.0/rope/.ropeproject/ 0000775 0001750 0001750 00000000000 00000000000 016012 5 ustar 00lieryan lieryan ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1636638665.0
rope-0.22.0/rope/.ropeproject/config.py 0000664 0001750 0001750 00000011403 00000000000 017630 0 ustar 00lieryan lieryan # The default ``config.py``
# flake8: noqa
def set_prefs(prefs):
"""This function is called before opening the project"""
# Specify which files and folders to ignore in the project.
# Changes to ignored resources are not added to the history and
# VCSs. Also they are not returned in `Project.get_files()`.
# Note that ``?`` and ``*`` match all characters but slashes.
# '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
# 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
# '.svn': matches 'pkg/.svn' and all of its children
# 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
# 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
prefs["ignored_resources"] = [
"*.pyc",
"*~",
".ropeproject",
".hg",
".svn",
"_svn",
".git",
".tox",
".venv",
"venv",
]
# Specifies which files should be considered python files. It is
# useful when you have scripts inside your project. Only files
# ending with ``.py`` are considered to be python files by
# default.
# prefs['python_files'] = ['*.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. Note
# that rope guesses project source folders correctly most of the
# time; use this if you have any problems.
# The folders should be relative to project root and use '/' for
# separating folders regardless of the platform rope is running on.
# 'src/my_source_folder' for instance.
# prefs.add('source_folders', 'src')
# You can extend python path for looking up modules
# prefs.add('python_path', '~/python/')
# Should rope save object information or not.
prefs["save_objectdb"] = True
prefs["compress_objectdb"] = False
# If `True`, rope analyzes each module when it is being saved.
prefs["automatic_soa"] = True
# The depth of calls to follow in static object analysis
prefs["soa_followed_calls"] = 0
# If `False` when running modules or unit tests "dynamic object
# analysis" is turned off. This makes them much faster.
prefs["perform_doa"] = True
# Rope can check the validity of its object DB when running.
prefs["validate_objectdb"] = True
# How many undos to hold?
prefs["max_history_items"] = 32
# Shows whether to save history across sessions.
prefs["save_history"] = True
prefs["compress_history"] = False
# Set the number spaces used for indenting. According to
# :PEP:`8`, it is best to use 4 spaces. Since most of rope's
# unit-tests use 4 spaces it is more reliable, too.
prefs["indent_size"] = 4
# Builtin and c-extension modules that are allowed to be imported
# and inspected by rope.
prefs["extension_modules"] = []
# Add all standard c-extensions to extension_modules list.
prefs["import_dynload_stdmods"] = True
# If `True` modules with syntax errors are considered to be empty.
# The default value is `False`; When `False` syntax errors raise
# `rope.base.exceptions.ModuleSyntaxError` exception.
prefs["ignore_syntax_errors"] = False
# If `True`, rope ignores unresolvable imports. Otherwise, they
# appear in the importing namespace.
prefs["ignore_bad_imports"] = False
# If `True`, rope will insert new module imports as
# `from import ` by default.
prefs["prefer_module_from_imports"] = False
# If `True`, rope will transform a comma list of imports into
# multiple separate import statements when organizing
# imports.
prefs["split_imports"] = False
# If `True`, rope will remove all top-level import statements and
# reinsert them at the top of the module when making changes.
prefs["pull_imports_to_top"] = True
# If `True`, rope will sort imports alphabetically by module name instead
# of alphabetically by import statement, with from imports after normal
# imports.
prefs["sort_imports_alphabetically"] = False
# Location of implementation of
# rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general
# case, you don't have to change this value, unless you're an rope expert.
# Change this value to inject you own implementations of interfaces
# listed in module rope.base.oi.type_hinting.providers.interfaces
# For example, you can add you own providers for Django Models, or disable
# the search type-hinting in a class hierarchy, etc.
prefs[
"type_hinting_factory"
] = "rope.base.oi.type_hinting.factory.default_type_hinting_factory"
def project_opened(project):
"""This function is called after opening the project"""
# Do whatever you like here!
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1637593538.0
rope-0.22.0/rope/__init__.py 0000664 0001750 0001750 00000001670 00000000000 015515 0 ustar 00lieryan lieryan """rope, a python refactoring library"""
INFO = __doc__
VERSION = "0.22.0"
COPYRIGHT = """\
Copyright (C) 2021-2021 Lie Ryan
Copyright (C) 2019-2021 Matej Cepl
Copyright (C) 2015-2018 Nicholas Smith
Copyright (C) 2014-2015 Matej Cepl
Copyright (C) 2006-2012 Ali Gholami Rudi
Copyright (C) 2009-2012 Anton Gritsay
This program is free software: you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, either
version 3 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this program. If not, see
."""
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1637593692.3553898
rope-0.22.0/rope/base/ 0000775 0001750 0001750 00000000000 00000000000 014312 5 ustar 00lieryan lieryan ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1632648447.0
rope-0.22.0/rope/base/__init__.py 0000664 0001750 0001750 00000000241 00000000000 016420 0 ustar 00lieryan lieryan """Base rope package
This package contains rope core modules that are used by other modules
and packages.
"""
__all__ = ["project", "libutils", "exceptions"]
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1632648447.0
rope-0.22.0/rope/base/arguments.py 0000664 0001750 0001750 00000006352 00000000000 016677 0 ustar 00lieryan lieryan import rope.base.evaluate
from rope.base import ast
class Arguments(object):
"""A class for evaluating parameters passed to a function
You can use the `create_arguments` factory. It handles implicit
first arguments.
"""
def __init__(self, args, scope):
self.args = args
self.scope = scope
self.instance = None
def get_arguments(self, parameters):
result = []
for pyname in self.get_pynames(parameters):
if pyname is None:
result.append(None)
else:
result.append(pyname.get_object())
return result
def get_pynames(self, parameters):
result = [None] * max(len(parameters), len(self.args))
for index, arg in enumerate(self.args):
if isinstance(arg, ast.keyword) and arg.arg in parameters:
result[parameters.index(arg.arg)] = self._evaluate(arg.value)
else:
result[index] = self._evaluate(arg)
return result
def get_instance_pyname(self):
if self.args:
return self._evaluate(self.args[0])
def _evaluate(self, ast_node):
return rope.base.evaluate.eval_node(self.scope, ast_node)
def create_arguments(primary, pyfunction, call_node, scope):
"""A factory for creating `Arguments`"""
args = list(call_node.args)
args.extend(call_node.keywords)
called = call_node.func
# XXX: Handle constructors
if _is_method_call(primary, pyfunction) and isinstance(called, ast.Attribute):
args.insert(0, called.value)
return Arguments(args, scope)
class ObjectArguments(object):
def __init__(self, pynames):
self.pynames = pynames
def get_arguments(self, parameters):
result = []
for pyname in self.pynames:
if pyname is None:
result.append(None)
else:
result.append(pyname.get_object())
return result
def get_pynames(self, parameters):
return self.pynames
def get_instance_pyname(self):
return self.pynames[0]
class MixedArguments(object):
def __init__(self, pyname, arguments, scope):
"""`argumens` is an instance of `Arguments`"""
self.pyname = pyname
self.args = arguments
def get_pynames(self, parameters):
return [self.pyname] + self.args.get_pynames(parameters[1:])
def get_arguments(self, parameters):
result = []
for pyname in self.get_pynames(parameters):
if pyname is None:
result.append(None)
else:
result.append(pyname.get_object())
return result
def get_instance_pyname(self):
return self.pyname
def _is_method_call(primary, pyfunction):
if primary is None:
return False
pyobject = primary.get_object()
if (
isinstance(pyobject.get_type(), rope.base.pyobjects.PyClass)
and isinstance(pyfunction, rope.base.pyobjects.PyFunction)
and isinstance(pyfunction.parent, rope.base.pyobjects.PyClass)
):
return True
if isinstance(
pyobject.get_type(), rope.base.pyobjects.AbstractClass
) and isinstance(pyfunction, rope.base.builtins.BuiltinFunction):
return True
return False
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1632648447.0
rope-0.22.0/rope/base/ast.py 0000664 0001750 0001750 00000004377 00000000000 015466 0 ustar 00lieryan lieryan from __future__ import absolute_import
import ast
from ast import *
from rope.base import fscommands
try:
unicode
except NameError:
unicode = str
def parse(source, filename=""):
# NOTE: the raw string should be given to `compile` function
if isinstance(source, unicode):
source = fscommands.unicode_to_file_data(source)
if b"\r" in source:
source = source.replace(b"\r\n", b"\n").replace(b"\r", b"\n")
if not source.endswith(b"\n"):
source += b"\n"
try:
return ast.parse(source, filename="")
except (TypeError, ValueError) as e:
error = SyntaxError()
error.lineno = 1
error.filename = filename
error.msg = str(e)
raise error
def walk(node, walker):
"""Walk the syntax tree"""
method_name = "_" + node.__class__.__name__
method = getattr(walker, method_name, None)
if method is not None:
if isinstance(node, ast.ImportFrom) and node.module is None:
# In python < 2.7 ``node.module == ''`` for relative imports
# but for python 2.7 it is None. Generalizing it to ''.
node.module = ""
return method(node)
for child in get_child_nodes(node):
walk(child, walker)
def get_child_nodes(node):
if isinstance(node, ast.Module):
return node.body
result = []
if node._fields is not None:
for name in node._fields:
child = getattr(node, name)
if isinstance(child, list):
for entry in child:
if isinstance(entry, ast.AST):
result.append(entry)
if isinstance(child, ast.AST):
result.append(child)
return result
def call_for_nodes(node, callback, recursive=False):
"""If callback returns `True` the child nodes are skipped"""
result = callback(node)
if recursive and not result:
for child in get_child_nodes(node):
call_for_nodes(child, callback, recursive)
def get_children(node):
result = []
if node._fields is not None:
for name in node._fields:
if name in ["lineno", "col_offset"]:
continue
child = getattr(node, name)
result.append(child)
return result
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1632648447.0
rope-0.22.0/rope/base/astutils.py 0000664 0001750 0001750 00000003162 00000000000 016536 0 ustar 00lieryan lieryan from rope.base import ast
def get_name_levels(node):
"""Return a list of ``(name, level)`` tuples for assigned names
The `level` is `None` for simple assignments and is a list of
numbers for tuple assignments for example in::
a, (b, c) = x
The levels for for `a` is ``[0]``, for `b` is ``[1, 0]`` and for
`c` is ``[1, 1]``.
"""
visitor = _NodeNameCollector()
ast.walk(node, visitor)
return visitor.names
class _NodeNameCollector(object):
def __init__(self, levels=None):
self.names = []
self.levels = levels
self.index = 0
def _add_node(self, node):
new_levels = []
if self.levels is not None:
new_levels = list(self.levels)
new_levels.append(self.index)
self.index += 1
self._added(node, new_levels)
def _added(self, node, levels):
if hasattr(node, "id"):
self.names.append((node.id, levels))
def _Name(self, node):
self._add_node(node)
def _ExceptHandler(self, node):
self.names.append((node.name, []))
def _Tuple(self, node):
new_levels = []
if self.levels is not None:
new_levels = list(self.levels)
new_levels.append(self.index)
self.index += 1
visitor = _NodeNameCollector(new_levels)
for child in ast.get_child_nodes(node):
ast.walk(child, visitor)
self.names.extend(visitor.names)
def _Subscript(self, node):
self._add_node(node)
def _Attribute(self, node):
self._add_node(node)
def _Slice(self, node):
self._add_node(node)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1632913225.0
rope-0.22.0/rope/base/builtins.py 0000664 0001750 0001750 00000064301 00000000000 016521 0 ustar 00lieryan lieryan """This module trys to support builtin types and functions."""
import inspect
import io
try:
raw_input
except NameError:
raw_input = input
import rope.base.evaluate
from rope.base.utils import pycompat
from rope.base import pynames, pyobjects, arguments, utils
class BuiltinModule(pyobjects.AbstractModule):
def __init__(self, name, pycore=None, initial={}):
super(BuiltinModule, self).__init__()
self.name = name
self.pycore = pycore
self.initial = initial
parent = None
def get_attributes(self):
return self.attributes
def get_doc(self):
if self.module:
return self.module.__doc__
def get_name(self):
return self.name.split(".")[-1]
@property
@utils.saveit
def attributes(self):
result = _object_attributes(self.module, self)
result.update(self.initial)
if self.pycore is not None:
submodules = self.pycore._builtin_submodules(self.name)
for name, module in submodules.items():
result[name] = rope.base.builtins.BuiltinName(module)
return result
@property
@utils.saveit
def module(self):
try:
result = __import__(self.name)
for token in self.name.split(".")[1:]:
result = getattr(result, token, None)
return result
except ImportError:
return
class _BuiltinElement(object):
def __init__(self, builtin, parent=None):
self.builtin = builtin
self._parent = parent
def get_doc(self):
if self.builtin:
return getattr(self.builtin, "__doc__", None)
def get_name(self):
if self.builtin:
return getattr(self.builtin, "__name__", None)
@property
def parent(self):
if self._parent is None:
return builtins
return self._parent
class BuiltinClass(_BuiltinElement, pyobjects.AbstractClass):
def __init__(self, builtin, attributes, parent=None):
_BuiltinElement.__init__(self, builtin, parent)
pyobjects.AbstractClass.__init__(self)
self.initial = attributes
@utils.saveit
def get_attributes(self):
result = _object_attributes(self.builtin, self)
result.update(self.initial)
return result
def get_module(self):
return builtins
class BuiltinFunction(_BuiltinElement, pyobjects.AbstractFunction):
def __init__(
self, returned=None, function=None, builtin=None, argnames=[], parent=None
):
_BuiltinElement.__init__(self, builtin, parent)
pyobjects.AbstractFunction.__init__(self)
self.argnames = argnames
self.returned = returned
self.function = function
def get_returned_object(self, args):
if self.function is not None:
return self.function(_CallContext(self.argnames, args))
else:
return self.returned
def get_param_names(self, special_args=True):
return self.argnames
class BuiltinUnknown(_BuiltinElement, pyobjects.PyObject):
def __init__(self, builtin):
super(BuiltinUnknown, self).__init__(pyobjects.get_unknown())
self.builtin = builtin
self.type = pyobjects.get_unknown()
def get_name(self):
return getattr(type(self.builtin), "__name__", None)
@utils.saveit
def get_attributes(self):
return _object_attributes(self.builtin, self)
def _object_attributes(obj, parent):
attributes = {}
for name in dir(obj):
if name == "None":
continue
try:
child = getattr(obj, name)
except AttributeError:
# descriptors are allowed to raise AttributeError
# even if they are in dir()
continue
pyobject = None
if inspect.isclass(child):
pyobject = BuiltinClass(child, {}, parent=parent)
elif inspect.isroutine(child):
pyobject = BuiltinFunction(builtin=child, parent=parent)
else:
pyobject = BuiltinUnknown(builtin=child)
attributes[name] = BuiltinName(pyobject)
return attributes
def _create_builtin_type_getter(cls):
def _get_builtin(*args):
if not hasattr(cls, "_generated"):
cls._generated = {}
if args not in cls._generated:
cls._generated[args] = cls(*args)
return cls._generated[args]
return _get_builtin
def _create_builtin_getter(cls):
type_getter = _create_builtin_type_getter(cls)
def _get_builtin(*args):
return pyobjects.PyObject(type_getter(*args))
return _get_builtin
class _CallContext(object):
def __init__(self, argnames, args):
self.argnames = argnames
self.args = args
def _get_scope_and_pyname(self, pyname):
if pyname is not None and isinstance(pyname, pynames.AssignedName):
pymodule, lineno = pyname.get_definition_location()
if pymodule is None:
return None, None
if lineno is None:
lineno = 1
scope = pymodule.get_scope().get_inner_scope_for_line(lineno)
name = None
while name is None and scope is not None:
for current in scope.get_names():
if scope[current] is pyname:
name = current
break
else:
scope = scope.parent
return scope, name
return None, None
def get_argument(self, name):
if self.args:
args = self.args.get_arguments(self.argnames)
return args[self.argnames.index(name)]
def get_pyname(self, name):
if self.args:
args = self.args.get_pynames(self.argnames)
if name in self.argnames:
return args[self.argnames.index(name)]
def get_arguments(self, argnames):
if self.args:
return self.args.get_arguments(argnames)
def get_pynames(self, argnames):
if self.args:
return self.args.get_pynames(argnames)
def get_per_name(self):
if self.args is None:
return None
pyname = self.args.get_instance_pyname()
scope, name = self._get_scope_and_pyname(pyname)
if name is not None:
pymodule = pyname.get_definition_location()[0]
return pymodule.pycore.object_info.get_per_name(scope, name)
return None
def save_per_name(self, value):
if self.args is None:
return None
pyname = self.args.get_instance_pyname()
scope, name = self._get_scope_and_pyname(pyname)
if name is not None:
pymodule = pyname.get_definition_location()[0]
pymodule.pycore.object_info.save_per_name(scope, name, value)
class _AttributeCollector(object):
def __init__(self, type):
self.attributes = {}
self.type = type
def __call__(
self,
name,
returned=None,
function=None,
argnames=["self"],
check_existence=True,
parent=None,
):
try:
builtin = getattr(self.type, name)
except AttributeError:
if check_existence:
raise
builtin = None
self.attributes[name] = BuiltinName(
BuiltinFunction(
returned=returned,
function=function,
argnames=argnames,
builtin=builtin,
parent=parent,
)
)
def __setitem__(self, name, value):
self.attributes[name] = value
class List(BuiltinClass):
def __init__(self, holding=None):
self.holding = holding
collector = _AttributeCollector(list)
collector("__iter__", function=self._iterator_get, parent=self)
collector("__new__", function=self._new_list, parent=self)
# Adding methods
collector(
"append", function=self._list_add, argnames=["self", "value"], parent=self
)
collector(
"__setitem__",
function=self._list_add,
argnames=["self", "index", "value"],
parent=self,
)
collector(
"insert",
function=self._list_add,
argnames=["self", "index", "value"],
parent=self,
)
collector(
"extend",
function=self._self_set,
argnames=["self", "iterable"],
parent=self,
)
# Getting methods
collector("__getitem__", function=self._list_get, parent=self)
collector("pop", function=self._list_get, parent=self)
try:
collector("__getslice__", function=self._list_get)
except AttributeError:
pass
super(List, self).__init__(list, collector.attributes)
def _new_list(self, args):
return _create_builtin(args, get_list)
def _list_add(self, context):
if self.holding is not None:
return
holding = context.get_argument("value")
if holding is not None and holding != pyobjects.get_unknown():
context.save_per_name(holding)
def _self_set(self, context):
if self.holding is not None:
return
iterable = context.get_pyname("iterable")
holding = _infer_sequence_for_pyname(iterable)
if holding is not None and holding != pyobjects.get_unknown():
context.save_per_name(holding)
def _list_get(self, context):
if self.holding is not None:
args = context.get_arguments(["self", "key"])
if (
len(args) > 1
and args[1] is not None
and args[1].get_type() == builtins["slice"].get_object()
):
return get_list(self.holding)
return self.holding
return context.get_per_name()
def _iterator_get(self, context):
return get_iterator(self._list_get(context))
def _self_get(self, context):
return get_list(self._list_get(context))
get_list = _create_builtin_getter(List)
get_list_type = _create_builtin_type_getter(List)
class Dict(BuiltinClass):
def __init__(self, keys=None, values=None):
self.keys = keys
self.values = values
collector = _AttributeCollector(dict)
collector("__new__", function=self._new_dict, parent=self)
collector("__setitem__", function=self._dict_add, parent=self)
collector("popitem", function=self._item_get, parent=self)
collector("pop", function=self._value_get, parent=self)
collector("get", function=self._key_get, parent=self)
collector("keys", function=self._key_list, parent=self)
collector("values", function=self._value_list, parent=self)
collector("items", function=self._item_list, parent=self)
collector("copy", function=self._self_get, parent=self)
collector("__getitem__", function=self._value_get, parent=self)
collector("__iter__", function=self._key_iter, parent=self)
collector("update", function=self._self_set, parent=self)
super(Dict, self).__init__(dict, collector.attributes)
def _new_dict(self, args):
def do_create(holding=None):
if holding is None:
return get_dict()
type = holding.get_type()
if isinstance(type, Tuple) and len(type.get_holding_objects()) == 2:
return get_dict(*type.get_holding_objects())
return _create_builtin(args, do_create)
def _dict_add(self, context):
if self.keys is not None:
return
key, value = context.get_arguments(["self", "key", "value"])[1:]
if key is not None and key != pyobjects.get_unknown():
context.save_per_name(get_tuple(key, value))
def _item_get(self, context):
if self.keys is not None:
return get_tuple(self.keys, self.values)
item = context.get_per_name()
if item is None or not isinstance(item.get_type(), Tuple):
return get_tuple(self.keys, self.values)
return item
def _value_get(self, context):
item = self._item_get(context).get_type()
return item.get_holding_objects()[1]
def _key_get(self, context):
item = self._item_get(context).get_type()
return item.get_holding_objects()[0]
def _value_list(self, context):
return get_list(self._value_get(context))
def _key_list(self, context):
return get_list(self._key_get(context))
def _item_list(self, context):
return get_list(self._item_get(context))
def _value_iter(self, context):
return get_iterator(self._value_get(context))
def _key_iter(self, context):
return get_iterator(self._key_get(context))
def _item_iter(self, context):
return get_iterator(self._item_get(context))
def _self_get(self, context):
item = self._item_get(context).get_type()
key, value = item.get_holding_objects()[:2]
return get_dict(key, value)
def _self_set(self, context):
if self.keys is not None:
return
new_dict = context.get_pynames(["self", "d"])[1]
if new_dict and isinstance(new_dict.get_object().get_type(), Dict):
args = arguments.ObjectArguments([new_dict])
items = (
new_dict.get_object()["popitem"].get_object().get_returned_object(args)
)
context.save_per_name(items)
else:
holding = _infer_sequence_for_pyname(new_dict)
if holding is not None and isinstance(holding.get_type(), Tuple):
context.save_per_name(holding)
get_dict = _create_builtin_getter(Dict)
get_dict_type = _create_builtin_type_getter(Dict)
class Tuple(BuiltinClass):
def __init__(self, *objects):
self.objects = objects
first = None
if objects:
first = objects[0]
attributes = {
"__getitem__": BuiltinName(
BuiltinFunction(first)
), # TODO: add slice support
"__getslice__": BuiltinName(BuiltinFunction(pyobjects.PyObject(self))),
"__new__": BuiltinName(BuiltinFunction(function=self._new_tuple)),
"__iter__": BuiltinName(BuiltinFunction(get_iterator(first))),
}
super(Tuple, self).__init__(tuple, attributes)
def get_holding_objects(self):
return self.objects
def _new_tuple(self, args):
return _create_builtin(args, get_tuple)
get_tuple = _create_builtin_getter(Tuple)
get_tuple_type = _create_builtin_type_getter(Tuple)
class Set(BuiltinClass):
def __init__(self, holding=None):
self.holding = holding
collector = _AttributeCollector(set)
collector("__new__", function=self._new_set)
self_methods = [
"copy",
"difference",
"intersection",
"symmetric_difference",
"union",
]
for method in self_methods:
collector(method, function=self._self_get, parent=self)
collector("add", function=self._set_add, parent=self)
collector("update", function=self._self_set, parent=self)
collector("update", function=self._self_set, parent=self)
collector("symmetric_difference_update", function=self._self_set, parent=self)
collector("difference_update", function=self._self_set, parent=self)
collector("pop", function=self._set_get, parent=self)
collector("__iter__", function=self._iterator_get, parent=self)
super(Set, self).__init__(set, collector.attributes)
def _new_set(self, args):
return _create_builtin(args, get_set)
def _set_add(self, context):
if self.holding is not None:
return
holding = context.get_arguments(["self", "value"])[1]
if holding is not None and holding != pyobjects.get_unknown():
context.save_per_name(holding)
def _self_set(self, context):
if self.holding is not None:
return
iterable = context.get_pyname("iterable")
holding = _infer_sequence_for_pyname(iterable)
if holding is not None and holding != pyobjects.get_unknown():
context.save_per_name(holding)
def _set_get(self, context):
if self.holding is not None:
return self.holding
return context.get_per_name()
def _iterator_get(self, context):
return get_iterator(self._set_get(context))
def _self_get(self, context):
return get_list(self._set_get(context))
get_set = _create_builtin_getter(Set)
get_set_type = _create_builtin_type_getter(Set)
class Str(BuiltinClass):
def __init__(self):
self_object = pyobjects.PyObject(self)
collector = _AttributeCollector(str)
collector("__iter__", get_iterator(self_object), check_existence=False)
self_methods = [
"__getitem__",
"capitalize",
"center",
"encode",
"expandtabs",
"join",
"ljust",
"lower",
"lstrip",
"replace",
"rjust",
"rstrip",
"strip",
"swapcase",
"title",
"translate",
"upper",
"zfill",
]
for method in self_methods:
collector(method, self_object, parent=self)
py2_self_methods = ["__getslice__", "decode"]
for method in py2_self_methods:
try:
collector(method, self_object)
except AttributeError:
pass
for method in ["rsplit", "split", "splitlines"]:
collector(method, get_list(self_object), parent=self)
super(Str, self).__init__(str, collector.attributes)
def get_doc(self):
return str.__doc__
get_str = _create_builtin_getter(Str)
get_str_type = _create_builtin_type_getter(Str)
class BuiltinName(pynames.PyName):
def __init__(self, pyobject):
self.pyobject = pyobject
def get_object(self):
return self.pyobject
def get_definition_location(self):
return (None, None)
class Iterator(pyobjects.AbstractClass):
def __init__(self, holding=None):
super(Iterator, self).__init__()
self.holding = holding
self.attributes = {
"next": BuiltinName(BuiltinFunction(self.holding)),
"__iter__": BuiltinName(BuiltinFunction(self)),
}
def get_attributes(self):
return self.attributes
def get_returned_object(self, args):
return self.holding
get_iterator = _create_builtin_getter(Iterator)
class Generator(pyobjects.AbstractClass):
def __init__(self, holding=None):
super(Generator, self).__init__()
self.holding = holding
self.attributes = {
"next": BuiltinName(BuiltinFunction(self.holding)),
"__iter__": BuiltinName(BuiltinFunction(get_iterator(self.holding))),
"close": BuiltinName(BuiltinFunction()),
"send": BuiltinName(BuiltinFunction()),
"throw": BuiltinName(BuiltinFunction()),
}
def get_attributes(self):
return self.attributes
def get_returned_object(self, args):
return self.holding
get_generator = _create_builtin_getter(Generator)
class File(BuiltinClass):
def __init__(self, filename=None, mode="r", *args):
self.filename = filename
self.mode = mode
self.args = args
str_object = get_str()
str_list = get_list(get_str())
attributes = {}
def add(name, returned=None, function=None):
builtin = getattr(io.TextIOBase, name, None)
attributes[name] = BuiltinName(
BuiltinFunction(returned=returned, function=function, builtin=builtin)
)
add("__iter__", get_iterator(str_object))
add("__enter__", returned=pyobjects.PyObject(self))
for method in ["next", "read", "readline", "readlines"]:
add(method, str_list)
for method in [
"close",
"flush",
"lineno",
"isatty",
"seek",
"tell",
"truncate",
"write",
"writelines",
]:
add(method)
super(File, self).__init__(open, attributes)
get_file = _create_builtin_getter(File)
get_file_type = _create_builtin_type_getter(File)
class Property(BuiltinClass):
def __init__(self, fget=None, fset=None, fdel=None, fdoc=None):
self._fget = fget
self._fdoc = fdoc
attributes = {
"fget": BuiltinName(BuiltinFunction()),
"fset": BuiltinName(pynames.UnboundName()),
"fdel": BuiltinName(pynames.UnboundName()),
"__new__": BuiltinName(BuiltinFunction(function=_property_function)),
}
super(Property, self).__init__(property, attributes)
def get_property_object(self, args):
if isinstance(self._fget, pyobjects.AbstractFunction):
return self._fget.get_returned_object(args)
def _property_function(args):
parameters = args.get_arguments(["fget", "fset", "fdel", "fdoc"])
return pyobjects.PyObject(Property(parameters[0]))
class Lambda(pyobjects.AbstractFunction):
def __init__(self, node, scope):
super(Lambda, self).__init__()
self.node = node
self.arguments = node.args
self.scope = scope
def get_returned_object(self, args):
result = rope.base.evaluate.eval_node(self.scope, self.node.body)
if result is not None:
return result.get_object()
else:
return pyobjects.get_unknown()
def get_module(self):
return self.parent.get_module()
def get_scope(self):
return self.scope
def get_kind(self):
return "lambda"
def get_ast(self):
return self.node
def get_attributes(self):
return {}
def get_name(self):
return "lambda"
def get_param_names(self, special_args=True):
result = [
pycompat.get_ast_arg_arg(node)
for node in self.arguments.args
if isinstance(node, pycompat.ast_arg_type)
]
if self.arguments.vararg:
result.append("*" + pycompat.get_ast_arg_arg(self.arguments.vararg))
if self.arguments.kwarg:
result.append("**" + pycompat.get_ast_arg_arg(self.arguments.kwarg))
return result
@property
def parent(self):
return self.scope.pyobject
class BuiltinObject(BuiltinClass):
def __init__(self):
super(BuiltinObject, self).__init__(object, {})
class BuiltinType(BuiltinClass):
def __init__(self):
super(BuiltinType, self).__init__(type, {})
def _infer_sequence_for_pyname(pyname):
if pyname is None:
return None
seq = pyname.get_object()
args = arguments.ObjectArguments([pyname])
if "__iter__" in seq:
obj = seq["__iter__"].get_object()
if not isinstance(obj, pyobjects.AbstractFunction):
return None
iter = obj.get_returned_object(args)
if iter is not None and "next" in iter:
holding = iter["next"].get_object().get_returned_object(args)
return holding
def _create_builtin(args, creator):
passed = args.get_pynames(["sequence"])[0]
if passed is None:
holding = None
else:
holding = _infer_sequence_for_pyname(passed)
if holding is not None:
return creator(holding)
else:
return creator()
def _open_function(args):
return _create_builtin(args, get_file)
def _range_function(args):
return get_list()
def _reversed_function(args):
return _create_builtin(args, get_iterator)
def _sorted_function(args):
return _create_builtin(args, get_list)
def _super_function(args):
passed_class, passed_self = args.get_arguments(["type", "self"])
if passed_self is None:
return passed_class
else:
# pyclass = passed_self.get_type()
pyclass = passed_class
if isinstance(pyclass, pyobjects.AbstractClass):
supers = pyclass.get_superclasses()
if supers:
return pyobjects.PyObject(supers[0])
return passed_self
def _zip_function(args):
args = args.get_pynames(["sequence"])
objects = []
for seq in args:
if seq is None:
holding = None
else:
holding = _infer_sequence_for_pyname(seq)
objects.append(holding)
tuple = get_tuple(*objects)
return get_list(tuple)
def _enumerate_function(args):
passed = args.get_pynames(["sequence"])[0]
if passed is None:
holding = None
else:
holding = _infer_sequence_for_pyname(passed)
tuple = get_tuple(None, holding)
return get_iterator(tuple)
def _iter_function(args):
passed = args.get_pynames(["sequence"])[0]
if passed is None:
holding = None
else:
holding = _infer_sequence_for_pyname(passed)
return get_iterator(holding)
def _input_function(args):
return get_str()
_initial_builtins = {
"list": BuiltinName(get_list_type()),
"dict": BuiltinName(get_dict_type()),
"tuple": BuiltinName(get_tuple_type()),
"set": BuiltinName(get_set_type()),
"str": BuiltinName(get_str_type()),
"file": BuiltinName(get_file_type()),
"open": BuiltinName(BuiltinFunction(function=_open_function, builtin=open)),
"unicode": BuiltinName(get_str_type()),
"range": BuiltinName(BuiltinFunction(function=_range_function, builtin=range)),
"reversed": BuiltinName(
BuiltinFunction(function=_reversed_function, builtin=reversed)
),
"sorted": BuiltinName(BuiltinFunction(function=_sorted_function, builtin=sorted)),
"super": BuiltinName(BuiltinFunction(function=_super_function, builtin=super)),
"property": BuiltinName(
BuiltinFunction(function=_property_function, builtin=property)
),
"zip": BuiltinName(BuiltinFunction(function=_zip_function, builtin=zip)),
"enumerate": BuiltinName(
BuiltinFunction(function=_enumerate_function, builtin=enumerate)
),
"object": BuiltinName(BuiltinObject()),
"type": BuiltinName(BuiltinType()),
"iter": BuiltinName(BuiltinFunction(function=_iter_function, builtin=iter)),
"raw_input": BuiltinName(
BuiltinFunction(function=_input_function, builtin=raw_input)
),
}
builtins = BuiltinModule(pycompat.builtins.__name__, initial=_initial_builtins)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1632913225.0
rope-0.22.0/rope/base/change.py 0000664 0001750 0001750 00000032256 00000000000 016121 0 ustar 00lieryan lieryan import datetime
import difflib
import os
import time
import rope.base.fscommands
from rope.base import taskhandle, exceptions, utils
class Change(object):
"""The base class for changes
Rope refactorings return `Change` objects. They can be previewed,
committed or undone.
"""
def do(self, job_set=None):
"""Perform the change
.. note:: Do use this directly. Use `Project.do()` instead.
"""
def undo(self, job_set=None):
"""Perform the change
.. note:: Do use this directly. Use `History.undo()` instead.
"""
def get_description(self):
"""Return the description of this change
This can be used for previewing the changes.
"""
return str(self)
def get_changed_resources(self):
"""Return the list of resources that will be changed"""
return []
@property
@utils.saveit
def _operations(self):
return _ResourceOperations(self.resource.project)
class ChangeSet(Change):
"""A collection of `Change` objects
This class holds a collection of changes. This class provides
these fields:
* `changes`: the list of changes
* `description`: the goal of these changes
"""
def __init__(self, description, timestamp=None):
self.changes = []
self.description = description
self.time = timestamp
def do(self, job_set=taskhandle.NullJobSet()):
try:
done = []
for change in self.changes:
change.do(job_set)
done.append(change)
self.time = time.time()
except Exception:
for change in done:
change.undo()
raise
def undo(self, job_set=taskhandle.NullJobSet()):
try:
done = []
for change in reversed(self.changes):
change.undo(job_set)
done.append(change)
except Exception:
for change in done:
change.do()
raise
def add_change(self, change):
self.changes.append(change)
def get_description(self):
result = [str(self) + ":\n\n\n"]
for change in self.changes:
result.append(change.get_description())
result.append("\n")
return "".join(result)
def __str__(self):
if self.time is not None:
date = datetime.datetime.fromtimestamp(self.time)
if date.date() == datetime.date.today():
string_date = "today"
elif date.date() == (datetime.date.today() - datetime.timedelta(1)):
string_date = "yesterday"
elif date.year == datetime.date.today().year:
string_date = date.strftime("%b %d")
else:
string_date = date.strftime("%d %b, %Y")
string_time = date.strftime("%H:%M:%S")
string_time = "%s %s " % (string_date, string_time)
return self.description + " - " + string_time
return self.description
def get_changed_resources(self):
result = set()
for change in self.changes:
result.update(change.get_changed_resources())
return result
def _handle_job_set(function):
"""A decorator for handling `taskhandle.JobSet`
A decorator for handling `taskhandle.JobSet` for `do` and `undo`
methods of `Change`.
"""
def call(self, job_set=taskhandle.NullJobSet()):
job_set.started_job(str(self))
function(self)
job_set.finished_job()
return call
class ChangeContents(Change):
"""A class to change the contents of a file
Fields:
* `resource`: The `rope.base.resources.File` to change
* `new_contents`: What to write in the file
"""
def __init__(self, resource, new_contents, old_contents=None):
self.resource = resource
# IDEA: Only saving diffs; possible problems when undo/redoing
self.new_contents = new_contents
self.old_contents = old_contents
@_handle_job_set
def do(self):
if self.old_contents is None:
self.old_contents = self.resource.read()
self._operations.write_file(self.resource, self.new_contents)
@_handle_job_set
def undo(self):
if self.old_contents is None:
raise exceptions.HistoryError("Undoing a change that is not performed yet!")
self._operations.write_file(self.resource, self.old_contents)
def __str__(self):
return "Change <%s>" % self.resource.path
def get_description(self):
new = self.new_contents
old = self.old_contents
if old is None:
if self.resource.exists():
old = self.resource.read()
else:
old = ""
result = difflib.unified_diff(
old.splitlines(True),
new.splitlines(True),
"a/" + self.resource.path,
"b/" + self.resource.path,
)
return "".join(list(result))
def get_changed_resources(self):
return [self.resource]
class MoveResource(Change):
"""Move a resource to a new location
Fields:
* `resource`: The `rope.base.resources.Resource` to move
* `new_resource`: The destination for move; It is the moved
resource not the folder containing that resource.
"""
def __init__(self, resource, new_location, exact=False):
self.project = resource.project
self.resource = resource
if not exact:
new_location = _get_destination_for_move(resource, new_location)
if resource.is_folder():
self.new_resource = self.project.get_folder(new_location)
else:
self.new_resource = self.project.get_file(new_location)
@_handle_job_set
def do(self):
self._operations.move(self.resource, self.new_resource)
@_handle_job_set
def undo(self):
self._operations.move(self.new_resource, self.resource)
def __str__(self):
return "Move <%s>" % self.resource.path
def get_description(self):
return "rename from %s\nrename to %s" % (
self.resource.path,
self.new_resource.path,
)
def get_changed_resources(self):
return [self.resource, self.new_resource]
class CreateResource(Change):
"""A class to create a resource
Fields:
* `resource`: The resource to create
"""
def __init__(self, resource):
self.resource = resource
@_handle_job_set
def do(self):
self._operations.create(self.resource)
@_handle_job_set
def undo(self):
self._operations.remove(self.resource)
def __str__(self):
return "Create Resource <%s>" % (self.resource.path)
def get_description(self):
return "new file %s" % (self.resource.path)
def get_changed_resources(self):
return [self.resource]
def _get_child_path(self, parent, name):
if parent.path == "":
return name
else:
return parent.path + "/" + name
class CreateFolder(CreateResource):
"""A class to create a folder
See docs for `CreateResource`.
"""
def __init__(self, parent, name):
resource = parent.project.get_folder(self._get_child_path(parent, name))
super(CreateFolder, self).__init__(resource)
class CreateFile(CreateResource):
"""A class to create a file
See docs for `CreateResource`.
"""
def __init__(self, parent, name):
resource = parent.project.get_file(self._get_child_path(parent, name))
super(CreateFile, self).__init__(resource)
class RemoveResource(Change):
"""A class to remove a resource
Fields:
* `resource`: The resource to be removed
"""
def __init__(self, resource):
self.resource = resource
@_handle_job_set
def do(self):
self._operations.remove(self.resource)
# TODO: Undoing remove operations
@_handle_job_set
def undo(self):
raise NotImplementedError("Undoing `RemoveResource` is not implemented yet.")
def __str__(self):
return "Remove <%s>" % (self.resource.path)
def get_changed_resources(self):
return [self.resource]
def count_changes(change):
"""Counts the number of basic changes a `Change` will make"""
if isinstance(change, ChangeSet):
result = 0
for child in change.changes:
result += count_changes(child)
return result
return 1
def create_job_set(task_handle, change):
return task_handle.create_jobset(str(change), count_changes(change))
class _ResourceOperations(object):
def __init__(self, project):
self.project = project
self.fscommands = project.fscommands
self.direct_commands = rope.base.fscommands.FileSystemCommands()
def _get_fscommands(self, resource):
if self.project.is_ignored(resource):
return self.direct_commands
return self.fscommands
def write_file(self, resource, contents):
data = rope.base.fscommands.unicode_to_file_data(contents)
fscommands = self._get_fscommands(resource)
fscommands.write(resource.real_path, data)
for observer in list(self.project.observers):
observer.resource_changed(resource)
def move(self, resource, new_resource):
fscommands = self._get_fscommands(resource)
fscommands.move(resource.real_path, new_resource.real_path)
for observer in list(self.project.observers):
observer.resource_moved(resource, new_resource)
def create(self, resource):
if resource.is_folder():
self._create_resource(resource.path, kind="folder")
else:
self._create_resource(resource.path)
for observer in list(self.project.observers):
observer.resource_created(resource)
def remove(self, resource):
fscommands = self._get_fscommands(resource)
fscommands.remove(resource.real_path)
for observer in list(self.project.observers):
observer.resource_removed(resource)
def _create_resource(self, file_name, kind="file"):
resource_path = self.project._get_resource_path(file_name)
if os.path.exists(resource_path):
raise exceptions.RopeError("Resource <%s> already exists" % resource_path)
resource = self.project.get_file(file_name)
if not resource.parent.exists():
raise exceptions.ResourceNotFoundError(
"Parent folder of <%s> does not exist" % resource.path
)
fscommands = self._get_fscommands(resource)
try:
if kind == "file":
fscommands.create_file(resource_path)
else:
fscommands.create_folder(resource_path)
except IOError as e:
raise exceptions.RopeError(e)
def _get_destination_for_move(resource, destination):
dest_path = resource.project._get_resource_path(destination)
if os.path.isdir(dest_path):
if destination != "":
return destination + "/" + resource.name
else:
return resource.name
return destination
class ChangeToData(object):
def convertChangeSet(self, change):
description = change.description
changes = []
for child in change.changes:
changes.append(self(child))
return (description, changes, change.time)
def convertChangeContents(self, change):
return (change.resource.path, change.new_contents, change.old_contents)
def convertMoveResource(self, change):
return (change.resource.path, change.new_resource.path)
def convertCreateResource(self, change):
return (change.resource.path, change.resource.is_folder())
def convertRemoveResource(self, change):
return (change.resource.path, change.resource.is_folder())
def __call__(self, change):
change_type = type(change)
if change_type in (CreateFolder, CreateFile):
change_type = CreateResource
method = getattr(self, "convert" + change_type.__name__)
return (change_type.__name__, method(change))
class DataToChange(object):
def __init__(self, project):
self.project = project
def makeChangeSet(self, description, changes, time=None):
result = ChangeSet(description, time)
for child in changes:
result.add_change(self(child))
return result
def makeChangeContents(self, path, new_contents, old_contents):
resource = self.project.get_file(path)
return ChangeContents(resource, new_contents, old_contents)
def makeMoveResource(self, old_path, new_path):
resource = self.project.get_file(old_path)
return MoveResource(resource, new_path, exact=True)
def makeCreateResource(self, path, is_folder):
if is_folder:
resource = self.project.get_folder(path)
else:
resource = self.project.get_file(path)
return CreateResource(resource)
def makeRemoveResource(self, path, is_folder):
if is_folder:
resource = self.project.get_folder(path)
else:
resource = self.project.get_file(path)
return RemoveResource(resource)
def __call__(self, data):
method = getattr(self, "make" + data[0])
return method(*data[1])
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1633864767.0
rope-0.22.0/rope/base/codeanalyze.py 0000664 0001750 0001750 00000027066 00000000000 017175 0 ustar 00lieryan lieryan import bisect
import re
import token
import tokenize
class ChangeCollector(object):
def __init__(self, text):
self.text = text
self.changes = []
def add_change(self, start, end, new_text=None):
if new_text is None:
new_text = self.text[start:end]
self.changes.append((start, end, new_text))
def get_changed(self):
if not self.changes:
return None
self.changes.sort(key=lambda x: x[:2])
pieces = []
last_changed = 0
for change in self.changes:
start, end, text = change
pieces.append(self.text[last_changed:start] + text)
last_changed = end
if last_changed < len(self.text):
pieces.append(self.text[last_changed:])
result = "".join(pieces)
if result != self.text:
return result
class SourceLinesAdapter(object):
"""Adapts source to Lines interface
Note: The creation of this class is expensive.
"""
def __init__(self, source_code):
self.code = source_code
self.starts = None
self._initialize_line_starts()
def _initialize_line_starts(self):
self.starts = []
self.starts.append(0)
try:
i = 0
while True:
i = self.code.index("\n", i) + 1
self.starts.append(i)
except ValueError:
pass
self.starts.append(len(self.code) + 1)
def get_line(self, lineno):
return self.code[self.starts[lineno - 1] : self.starts[lineno] - 1]
def length(self):
return len(self.starts) - 1
def get_line_number(self, offset):
return bisect.bisect(self.starts, offset)
def get_line_start(self, lineno):
return self.starts[lineno - 1]
def get_line_end(self, lineno):
return self.starts[lineno] - 1
class ArrayLinesAdapter(object):
def __init__(self, lines):
self.lines = lines
def get_line(self, line_number):
return self.lines[line_number - 1]
def length(self):
return len(self.lines)
class LinesToReadline(object):
def __init__(self, lines, start):
self.lines = lines
self.current = start
def readline(self):
if self.current <= self.lines.length():
self.current += 1
return self.lines.get_line(self.current - 1) + "\n"
return ""
def __call__(self):
return self.readline()
class _CustomGenerator(object):
def __init__(self, lines):
self.lines = lines
self.in_string = ""
self.open_count = 0
self.continuation = False
def __call__(self):
size = self.lines.length()
result = []
i = 1
while i <= size:
while i <= size and not self.lines.get_line(i).strip():
i += 1
if i <= size:
start = i
while True:
line = self.lines.get_line(i)
self._analyze_line(line)
if (
not (self.continuation or self.open_count or self.in_string)
or i == size
):
break
i += 1
result.append((start, i))
i += 1
return result
# Matches all backslashes before the token, to detect escaped quotes
_main_tokens = re.compile(r'(\\*)((\'\'\'|"""|\'|")|#|\[|\]|\{|\}|\(|\))')
def _analyze_line(self, line):
token = None
for match in self._main_tokens.finditer(line):
prefix = match.group(1)
token = match.group(2)
# Skip any tokens which are escaped
if len(prefix) % 2 == 1:
continue
if token in ["'''", '"""', "'", '"']:
if not self.in_string:
self.in_string = token
elif self.in_string == token or (
self.in_string in ['"', "'"] and token == 3 * self.in_string
):
self.in_string = ""
if self.in_string:
continue
if token == "#":
break
if token in "([{":
self.open_count += 1
elif token in ")]}":
self.open_count -= 1
if line and token != "#" and line.endswith("\\"):
self.continuation = True
else:
self.continuation = False
def custom_generator(lines):
return _CustomGenerator(lines)()
class LogicalLineFinder(object):
def __init__(self, lines):
self.lines = lines
def logical_line_in(self, line_number):
indents = count_line_indents(self.lines.get_line(line_number))
tries = 0
while True:
block_start = get_block_start(self.lines, line_number, indents)
try:
return self._block_logical_line(block_start, line_number)
except IndentationError as e:
tries += 1
if tries == 5:
raise e
lineno = e.lineno + block_start - 1
indents = count_line_indents(self.lines.get_line(lineno))
def generate_starts(self, start_line=1, end_line=None):
for start, end in self.generate_regions(start_line, end_line):
yield start
def generate_regions(self, start_line=1, end_line=None):
# XXX: `block_start` should be at a better position!
block_start = 1
readline = LinesToReadline(self.lines, block_start)
try:
for start, end in self._logical_lines(readline):
real_start = start + block_start - 1
real_start = self._first_non_blank(real_start)
if end_line is not None and real_start >= end_line:
break
real_end = end + block_start - 1
if real_start >= start_line:
yield (real_start, real_end)
except tokenize.TokenError:
pass
def _block_logical_line(self, block_start, line_number):
readline = LinesToReadline(self.lines, block_start)
shifted = line_number - block_start + 1
region = self._calculate_logical(readline, shifted)
start = self._first_non_blank(region[0] + block_start - 1)
if region[1] is None:
end = self.lines.length()
else:
end = region[1] + block_start - 1
return start, end
def _calculate_logical(self, readline, line_number):
last_end = 1
try:
for start, end in self._logical_lines(readline):
if line_number <= end:
return (start, end)
last_end = end + 1
except tokenize.TokenError as e:
current = e.args[1][0]
return (last_end, max(last_end, current - 1))
return (last_end, None)
def _logical_lines(self, readline):
last_end = 1
for current_token in tokenize.generate_tokens(readline):
current = current_token[2][0]
if current_token[0] == token.NEWLINE:
yield (last_end, current)
last_end = current + 1
def _first_non_blank(self, line_number):
current = line_number
while current < self.lines.length():
line = self.lines.get_line(current).strip()
if line and not line.startswith("#"):
return current
current += 1
return current
def tokenizer_generator(lines):
return LogicalLineFinder(lines).generate_regions()
class CachingLogicalLineFinder(object):
def __init__(self, lines, generate=custom_generator):
self.lines = lines
self._generate = generate
_starts = None
@property
def starts(self):
if self._starts is None:
self._init_logicals()
return self._starts
_ends = None
@property
def ends(self):
if self._ends is None:
self._init_logicals()
return self._ends
def _init_logicals(self):
"""Should initialize _starts and _ends attributes"""
size = self.lines.length() + 1
self._starts = [None] * size
self._ends = [None] * size
for start, end in self._generate(self.lines):
self._starts[start] = True
self._ends[end] = True
def logical_line_in(self, line_number):
start = line_number
while start > 0 and not self.starts[start]:
start -= 1
if start == 0:
try:
start = self.starts.index(True, line_number)
except ValueError:
return (line_number, line_number)
return (start, self.ends.index(True, start))
def generate_starts(self, start_line=1, end_line=None):
if end_line is None:
end_line = self.lines.length()
for index in range(start_line, end_line):
if self.starts[index]:
yield index
def get_block_start(lines, lineno, maximum_indents=80):
"""Approximate block start"""
pattern = get_block_start_patterns()
for i in range(lineno, 0, -1):
match = pattern.search(lines.get_line(i))
if (
match is not None
and count_line_indents(lines.get_line(i)) <= maximum_indents
):
striped = match.string.lstrip()
# Maybe we're in a list comprehension or generator expression
if i > 1 and striped.startswith("if") or striped.startswith("for"):
bracs = 0
for j in range(i, min(i + 5, lines.length() + 1)):
for c in lines.get_line(j):
if c == "#":
break
if c in "[(":
bracs += 1
if c in ")]":
bracs -= 1
if bracs < 0:
break
if bracs < 0:
break
if bracs < 0:
continue
return i
return 1
_block_start_pattern = None
def get_block_start_patterns():
global _block_start_pattern
if not _block_start_pattern:
pattern = (
"^\\s*(((def|class|if|elif|except|for|while|with)\\s)|"
"((try|else|finally|except)\\s*:))"
)
_block_start_pattern = re.compile(pattern, re.M)
return _block_start_pattern
def count_line_indents(line):
indents = 0
for char in line:
if char == " ":
indents += 1
elif char == "\t":
indents += 8
else:
return indents
return 0
def get_string_pattern_with_prefix(prefix, prefix_group_name=None):
longstr = r'"""(\\.|"(?!"")|\\\n|[^"\\])*"""'
shortstr = r'"(\\.|\\\n|[^"\\\n])*"'
if prefix_group_name is not None:
pattern = "(?P<%s>%%s)(%%s)" % prefix_group_name
else:
pattern = "%s(%s)"
return pattern % (
prefix,
"|".join(
[
longstr,
longstr.replace('"', "'"),
shortstr,
shortstr.replace('"', "'"),
]
),
)
def get_string_pattern():
prefix = r"(?