././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1711292398.4275527
rope-1.13.0/ 0000755 0001751 0000177 00000000000 14600037756 012156 5 ustar 00runner docker ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1711292392.0
rope-1.13.0/CHANGELOG.md 0000644 0001751 0000177 00000026265 14600037750 013774 0 ustar 00runner docker # **Upcoming release**
- ...
# Release 1.13.0
- #781, #783 Isolate tests that uses external_fixturepkg into a venv (@lieryan)
- #751 Check for ast.Attributes when finding occurrences in fstrings (@sandratsy)
- #777, #698 add validation to refuse Rename refactoring to a python keyword (@lieryan)
- #730 Match on module aliases for autoimport suggestions (@MrBago)
- #755 Remove dependency on `build` package being installed while running tests (@lieryan)
- #780 Improved function parser to use ast parser instead of Worder (@lieryan)
- #752 Update pre-commit (@bagel897)
- #782 Integrate codecov with GHA (@lieryan)
- #754 Minor type hint improvements (@lieryan)
# Release 1.12.0
- #733 skip directories with perm error when building autoimport index (@MrBago)
- #722, #723 Remove site-packages from packages search tree (@tkrabel)
- #738 Implement os.PathLike on Resource (@lieryan)
- #739, #736 Ensure autoimport requests uses indexes (@lieryan)
- #734, #735 raise exception when extracting the start of a block without the end
# Release 1.11.0
- #710, #561 Implement `except*` syntax (@lieryan)
- #711 allow building documentation without having rope module installed (@kloczek)
- #719 Allows the in-memory db to be shared across threads (@tkrabel)
- #720 create one sqlite3.Connection per thread using a thread local (@tkrabel)
- #715 change AutoImport's `get_modules` to be case sensitive (@bagel897)
# Release 1.10.0
- #708, #709 Add support for Python 3.12 (@lieryan)
# Release 1.9.0
- #624, #693 Implement `nonlocal` keyword (@lieryan)
- #697, #565 Automatically purge autoimport.db when there is schema change
# Release 1.8.0
- #650 Install pre-commit hooks on rope repository (@lieryan)
- #655 Remove unused __init__() methods (@edreamleo, @lieryan)
- #656 Reformat using black 23.1.0 (@edreamleo)
- #674 Fix/supress all mypy complaints (@edreamleo)
- #680 Remove a do-nothing statement in soi._handle_first_parameter (@edreamleo)
- #687, #688 Fix autoimport not scanning packages recursively (@lieryan)
# Release 1.7.0
## Feature
- #548 Implement MoveGlobal using string as destination module names (@lieryan)
## Bug
- #627 Fix parsing of octal literal (@lieryan)
- #643, #435 Fix fstrings with mismatched parens (@apmorton)
- #646 Fix renaming kwargs when refactoring from imports (@apmorton)
- #648 Remove __init__ from import statement when using sqlite autoimport (@bagel897)
## Improvements
- rope.contrib.generate improvements
- #640 Remove unnecessary eval in generate.py (@edreamleo)
- #641 Add type annotations for rope.contrib.generate.create_generate() (@edreamleo)
- call_for_nodes() improvements
- #634 Remove call_for_nodes(recursive) argument (@edreamleo)
- #642 Add comments & docstrings related to call_for_nodes (@edreamleo, @lieryan)
- Data storage improvements
- #604 Fix test that sometimes leaves files behind in the current working directory (@lieryan)
- #606 Deprecate compress_objectdb and compress_history (@lieryan)
- #607 Remove importing from legacy files with `.pickle` suffix (@lieryan)
- #611 Implement JSON DataFile serialization (@lieryan)
- #630 SQLite models improvements (@lieryan)
- #631 Implement version hash (@lieryan)
## Tech Debt
- #594 Tidy up patchedast (@Alex-CodeLab)
- #595 Global default DEFAULT_TASK_HANDLE (@Alex-CodeLab)
- #609, #610, #612, #613 Fix pyflakes issues (@edreamleo)
- #615 Remove 'unicode' from builtins dict (@edreamleo)
- #616, #621 Remove `file` builtins (@edreamleo)
- #618 Separate pynames and pynamesdef and remove star-import (@edreamleo, @lieryan)
- #620 Remove unused import in occurrences.py (@edreamleo)
- #625 Remove support for deprecated ast nodes (@lieryan)
## Tests/Dev
- #626 Install pre-commit hooks on rope repository (@lieryan)
- #628 Add isort to pre-commit (@lieryan)
- #638 Add a function to identify ast Constant nodes more granularly (@lieryan)
## Docs
- #636 Update readme to reflect 1.0 has been released. (@maxnoe)
# Release 1.6.0
## New features & Enhancements
- #559, #560 Improve handling of whitespace in import and from-import statements (@lieryan)
- #566, #567, #597 Fix variables in kwonlyargs and posonlyargs not being correctly passed to extracted methods (@lieryan)
## Unit Test
- #589, #596 Fix issue with `sample_project()` creating directories where it shouldn't when running tests (@lieryan)
- #547 Add config file for linters
- #593 Remove `only_for` decorator for all python versions less than 3.7 (@edreamleo)
## Tech Debt
- Code quality
- #546 Remove unused vars in test (@lieryan, @edreamleo)
- #551, #552 Numerous flake8 linter complaints (@edreamleo)
- #558 Fix typos (@kianmeng)
- #583, #584 More consistent import style (@edreamleo)
- Python 2-related tech debt
- #533 Refactoring to Remove usage of unicode type (@lieryan)
- #549, #553 Remove rope.base.utils.pycompat (@dreamleo)
- #555 Fix some python2-isms (@lieryan)
- Rope's AST Wrapper
- #536, #578 walk does not return a value (@edreamleo)
- #537, #538 Remove special case code from walk (@edreamleo)
- #581 Remove functions in rope.base.ast that has functionally identical implementation in stdlib's ast (@lieryan, @edreamleo)
- #582 Refactoring rope.base.ast and remove rope.base.astutils (@lieryan, @edreamleo)
- pynames and pyobjects
- #569, #572 rename pynames to pynamesdef in pyobjectsdef.ph (@edreamleo)
# Release 1.5.1
- #531 Add alternative way to retrieve version number from pyproject.toml
# Release 1.5.0
Date: 2022-11-23
- #492 Feat: Global configuration support (@bagel897)
- #519 Move pytest to pyproject.toml (@gliptak, @bagel897)
- #509 Fix read/write analysis of the left-hand side of an augmented assignment (@lieryan)
- #522 Implement patchedast parsing of MatchMapping (@lieryan)
- #514 Fix inlining dictionary with inline comment (@lieryan)
# Release 1.4.0
Date: 2022-10-22
## Bug fixes
- #506, #507 Fix issue with parsing function call args list
- #411, #505 Fix extracting generator without parens
- #18, #510 When the function is a builtin function, the call parameter's name was sometimes incorrectly identified as an AssignedName. This led to rename refactoring incorrectly renaming these parameters.
# Release 1.3.0
Date: 2022-07-29
## Bug fixes
- #496, #497 Add MatMul operator to patchedast
- #495 Fix autoimport collection for compiled modules
## Improvement
- #501, #502 Autoimport improvements
# Release 1.2.0
Date: 2022-04-22
## New feature
- #473 Pyproject.toml support (@bageljrkhanofemus)
- #489 Rope now publishes documentations to rope.readthedocs.org (@bageljrkhanofemus)
- #490 Migrate from setup.py to pyproject.toml (@bageljrkhanofemus)
## Improvement
- #479 Add ABC and type hints for TaskHandle and JobSet (@bageljrkhanofemus)
- #486 Drop Python 2 support (@bageljrkhanofemus, @lieryan)
- #487 Improved value inference of __all__ declaration (@lieryan)
- #424 Add some basic __repr__ to make it easier for debugging (@lieryan)
# Release 1.1.1
## Bug fixes
- #476 Fix rope.contrib.autoimport package missing from release (@bageljrkhanofemus)
# Release 1.1.0
Date: 2022-05-25
## New feature
- #464 Add new autoimport implementation that uses a sqllite3 database, cache all available modules quickly, search for names and produce import statements, sort import statements. (@bageljrkhanofemus)
## Bug fixes
- #419 Fix bug while moving decorated function (@dryobates)
- #439 Fix bug while moving decorated class (@dryobates)
- #461 Fix bug while extracting method with list comprehension in class method (@dryobates)
- #440 Fix bug while inlining function with type hints in signature (@dryobates)
## Deprecation
- The pickle-based autoimport implementation is still the default, but will be deprecated sometime in the future.
# Release 1.0.0
Date: 2022-04-08
## Syntax support
- #400 Drop Python 2.7 support
## Bug fixes
- #459 Fix bug while extracting method with augmented assignment to subscript in try block (@dryobates)
# Release 0.23.0
## Syntax support
- #451, $456 Implement structural pattern matching (PEP634) (@lieryan)
- #458 Improve the heuristic for joining lines when extracting one line
expression (@lieryan)
## Bug fixes
- #134, #453 Preserve newline format when writing files (@lieryan)
- #457 Fix extract info collection for list comprehension with multiple targets
(@lieryan)
## Documentation
- #455 Fix typo (@Jasha10)
# Release 0.22.0
Date: 2021-11-23
## 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)
## Misc
- #447 Add Python 3.10 to tests
# 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=1711292392.0
rope-1.13.0/COPYING 0000644 0001751 0000177 00000016744 14600037750 013217 0 ustar 00runner docker 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=1711292392.0
rope-1.13.0/MANIFEST.in 0000644 0001751 0000177 00000000510 14600037750 013702 0 ustar 00runner docker include README.rst COPYING setup.py MANIFEST.in CHANGELOG.md ropetest-package-fixtures/external_fixturepkg/dist/external_fixturepkg-1.0.0-py3-none-any.whl ropetest-package-fixtures/external_fixturepkg/dist/external_fixturepkg-1.0.0.tar.gz
recursive-include rope *.py
recursive-include docs *.rst
recursive-include ropetest *.py
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1711292398.4275527
rope-1.13.0/PKG-INFO 0000644 0001751 0000177 00000014665 14600037756 013267 0 ustar 00runner docker Metadata-Version: 2.1
Name: rope
Version: 1.13.0
Summary: a python refactoring library...
Author-email: Ali Gholami Rudi
Maintainer-email: Lie Ryan
License: LGPL-3.0-or-later
Project-URL: Source, https://github.com/python-rope/rope
Project-URL: Documentation, https://rope.readthedocs.io/
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 :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development
Requires-Python: >=3.8
Description-Content-Type: text/x-rst
License-File: COPYING
Requires-Dist: pytoolconfig[global]>=1.2.2
Provides-Extra: doc
Requires-Dist: pytoolconfig[doc]; extra == "doc"
Requires-Dist: sphinx>=4.5.0; extra == "doc"
Requires-Dist: sphinx-autodoc-typehints>=1.18.1; extra == "doc"
Requires-Dist: sphinx-rtd-theme>=1.0.0; extra == "doc"
Provides-Extra: dev
Requires-Dist: pytest>=7.0.1; extra == "dev"
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
Requires-Dist: pytest-timeout>=2.1.0; extra == "dev"
Requires-Dist: build>=0.7.0; extra == "dev"
Requires-Dist: pre-commit>=2.20.0; extra == "dev"
Provides-Extra: release
Requires-Dist: toml>=0.10.2; extra == "release"
Requires-Dist: twine>=4.0.2; extra == "release"
Requires-Dist: pip-tools>=6.12.1; extra == "release"
.. _GitHub python-rope / rope: https://github.com/python-rope/rope
=========================================================================
rope -- the world's most advanced open source Python refactoring library
=========================================================================
|Build status badge| |Latest version badge| |Download count badge| |ReadTheDocs status badge| |Codecov badge|
.. |Build status badge| image:: https://github.com/python-rope/rope/actions/workflows/main.yml/badge.svg
:target: https://github.com/python-rope/rope/actions/workflows/main.yml
:alt: Build Status
.. |Latest version badge| image:: https://badge.fury.io/py/rope.svg
:target: https://badge.fury.io/py/rope
:alt: Latest version
.. |Download count badge| image:: https://img.shields.io/pypi/dm/rope.svg
:alt: Download count
.. |ReadTheDocs status badge| image:: https://readthedocs.org/projects/rope/badge/?version=latest
:target: https://rope.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
.. |Codecov badge| image:: https://codecov.io/gh/python-rope/rope/graph/badge.svg?token=pU08MBXFIS
:target: https://codecov.io/gh/python-rope/rope
:alt: Codecov
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 up to Python 3.10 is supported. Please file bugs and contribute
patches if you encounter gaps.
Since version 1.0.0, rope no longer support running on Python 2.
If you need Python 2 support, then check out the `python2` branch or the 0.x.x
releases.
Getting Started
===============
* `Documentation `_
* `How to use Rope in my IDE or Text editor? `_
* `Configuration `_
* `List of features `_
* `Overview of some of rope's features `_
* `Using as a library `_
* `Contributing `_
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 maintainer of Rope is 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
- Matej Cepl (`@mcepl`_) as former long-time Rope maintainer
- Nick Smith (`@soupytwist`_) as former Rope maintainer
- `all of our current and former contributors`_
- `all authors of editor integrations`_
- all maintainers of distro/package managers
.. _`@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
.. _`all authors of editor integrations`: https://github.com/python-rope/rope/wiki/How-to-use-Rope-in-my-IDE-or-Text-editor%3F
Packaging Status
================
.. image:: https://repology.org/badge/vertical-allrepos/python:rope.svg?exclude_unsupported=1
:target: https://repology.org/project/python:rope/versions
:alt: Packaging status
.. image:: https://repology.org/badge/vertical-allrepos/rope.svg?exclude_unsupported=1
:target: https://repology.org/project/rope/versions
:alt: Packaging status
License
=======
This program is under the terms of LGPL v3+ (GNU Lesser General Public License).
Have a look at `COPYING`_ for more information.
.. _`COPYING`: COPYING
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1711292392.0
rope-1.13.0/README.rst 0000644 0001751 0000177 00000011067 14600037750 013644 0 ustar 00runner docker
.. _GitHub python-rope / rope: https://github.com/python-rope/rope
=========================================================================
rope -- the world's most advanced open source Python refactoring library
=========================================================================
|Build status badge| |Latest version badge| |Download count badge| |ReadTheDocs status badge| |Codecov badge|
.. |Build status badge| image:: https://github.com/python-rope/rope/actions/workflows/main.yml/badge.svg
:target: https://github.com/python-rope/rope/actions/workflows/main.yml
:alt: Build Status
.. |Latest version badge| image:: https://badge.fury.io/py/rope.svg
:target: https://badge.fury.io/py/rope
:alt: Latest version
.. |Download count badge| image:: https://img.shields.io/pypi/dm/rope.svg
:alt: Download count
.. |ReadTheDocs status badge| image:: https://readthedocs.org/projects/rope/badge/?version=latest
:target: https://rope.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
.. |Codecov badge| image:: https://codecov.io/gh/python-rope/rope/graph/badge.svg?token=pU08MBXFIS
:target: https://codecov.io/gh/python-rope/rope
:alt: Codecov
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 up to Python 3.10 is supported. Please file bugs and contribute
patches if you encounter gaps.
Since version 1.0.0, rope no longer support running on Python 2.
If you need Python 2 support, then check out the `python2` branch or the 0.x.x
releases.
Getting Started
===============
* `Documentation `_
* `How to use Rope in my IDE or Text editor? `_
* `Configuration `_
* `List of features `_
* `Overview of some of rope's features `_
* `Using as a library `_
* `Contributing `_
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 maintainer of Rope is 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
- Matej Cepl (`@mcepl`_) as former long-time Rope maintainer
- Nick Smith (`@soupytwist`_) as former Rope maintainer
- `all of our current and former contributors`_
- `all authors of editor integrations`_
- all maintainers of distro/package managers
.. _`@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
.. _`all authors of editor integrations`: https://github.com/python-rope/rope/wiki/How-to-use-Rope-in-my-IDE-or-Text-editor%3F
Packaging Status
================
.. image:: https://repology.org/badge/vertical-allrepos/python:rope.svg?exclude_unsupported=1
:target: https://repology.org/project/python:rope/versions
:alt: Packaging status
.. image:: https://repology.org/badge/vertical-allrepos/rope.svg?exclude_unsupported=1
:target: https://repology.org/project/rope/versions
:alt: Packaging status
License
=======
This program is under the terms of LGPL v3+ (GNU Lesser General Public License).
Have a look at `COPYING`_ for more information.
.. _`COPYING`: COPYING
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1711292398.3995526
rope-1.13.0/docs/ 0000755 0001751 0000177 00000000000 14600037756 013106 5 ustar 00runner docker ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1711292392.0
rope-1.13.0/docs/configuration.rst 0000644 0001751 0000177 00000003430 14600037750 016501 0 ustar 00runner docker Configuration
=============
Rope supports the following configuration formats
1. pyproject.toml
2. config.py
3. pytool.toml
pyproject.toml
--------------
Will be used if [tool.rope] is configured.
.. code-block:: toml
[tool.rope]
split_imports = true
autoimport.aliases = [
['dt', 'datetime'],
['mp', 'multiprocessing'],
]
config.py
---------
You can also configure rope via a config.py in the ropefolder directory.
It will be used if ``[tool.rope]`` is not present in ``pyproject.toml`` or ``pyproject.toml`` isn't present and config.py is present.
.. code-block:: python3
def set_prefs(prefs):
prefs["ignored_resources"] = [
"*.pyc",
"*~",
".ropeproject",
".hg",
".svn",
"_svn",
".git",
".tox",
".venv",
"venv",
]
Additionally, you can run an executable function at startup of rope.
.. code-block:: python3
def project_opened(project):
"""This function is called after opening the project"""
# Do whatever you like here!
pytool.toml
-----------
If neither a config.py or a pyproject.toml is present, rope will use a pytool.toml.
It follows the exact same syntax as ``pyproject.toml``.
- Mac OS X: ``~/Library/Application Support/pytool.toml``.
- Unix: ``~/.config/pytool.toml``` or in $XDG_CONFIG_HOME, if defined
- Windows: ``C:\Users\\AppData\Local\pytool.toml``
Options
-------
.. autopytoolconfigtable:: rope.base.prefs.Prefs
Old Configuration File
----------------------
This is a sample config.py. While this config.py works and all options here should be supported, the above documentation reflects the latest version of rope.
.. literalinclude:: default_config.py
:language: python3
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1711292392.0
rope-1.13.0/docs/contributing.rst 0000644 0001751 0000177 00000011314 14600037750 016341 0 ustar 00runner docker ======================
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 :ref:`dev/issues:Rope Issues`. 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
Write Text Editors or IDE plugins for Rope
------------------------------------------
See pylsp-rope_, ropemacs_, ropevim_, eric4_.
.. _pylsp-rope: https://github.com/python-rope/pylsp-rope/
.. _ropemacs: https://github.com/python-rope/ropemacs/
.. _ropevim: https://github.com/python-rope/ropevim/
.. _eric4: http://eric-ide.python-projects.org/
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
:ref:`library:Using Rope As A Library` for more information.
There's also some really good `tour of Rope's codebase`_
by Austin Bingham (author of `Traad`_).
The first 10 minutes of the video talked about Rope in general, the rest are
more specific to Traad.
.. _tour of Rope's codebase: https://youtu.be/NvV5OrVk24c
.. _traad: https://github.com/abingham/traad/
Source Repository
=================
Rope uses GitHub_. The repository exists at
`python-rope/rope`_.
Setting up for local development
================================
#. Clone repository: ``git clone https://github.com/python-rope/rope.git``
#. Create a virtualenv: ``python -m venv rope-venv``
#. Activate the virtualenv
#. Install the project into the venv: ``pip install -e '.[doc,dev]'``
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`_. To run the test::
pytest -v
Many of rope's tests are still written using
``unittest.TestCase`` style, but running the test suite using
vanilla ``unittest`` is no longer supported.
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/
.. _gha-cache-key:
Updating gha-cache-key.txt
--------------------------
``gha-cache-key.txt`` file is used as cache-key for Github Action to cache pip
packages. Refer to `PR #650`_ to see how it works.
.. _`PR #650`: https://github.com/python-rope/rope/pull/650
To re-generate the cache key, run this command:
.. code-block:: sh
$ pip-compile --extra dev --generate-hashes -o gha-cache-key.txt
$ git add gha-cache-key.txt
$ git commit
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1711292398.3995526
rope-1.13.0/docs/dev/ 0000755 0001751 0000177 00000000000 14600037756 013664 5 ustar 00runner docker ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1711292392.0
rope-1.13.0/docs/dev/issues.rst 0000644 0001751 0000177 00000007502 14600037750 015727 0 ustar 00runner docker =============
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=1711292392.0
rope-1.13.0/docs/index.rst 0000644 0001751 0000177 00000001016 14600037750 014737 0 ustar 00runner docker .. rope documentation master file, created by
sphinx-quickstart on Sat May 21 18:16:44 2022.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to rope's documentation!
================================
.. toctree::
:maxdepth: 2
:caption: Contents:
overview
rope
library
configuration
contributing
release-process
dev/issues
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1711292392.0
rope-1.13.0/docs/library.rst 0000644 0001751 0000177 00000067773 14600037750 015322 0 ustar 00runner docker =========================
Using Rope As A Library
=========================
If you need other features, send a feature request. Have a look at
:ref:`contributing:Contributing to Rope`.
.. 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 rope 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 ref:`overview:Restructurings`):
.. 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.
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 committed 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.
There are currently two implementations of autoimport in rope, a deprecated
implementation that uses pickle-based storage
(rope.contrib.autoimport.pickle.AutoImport) and a new, experimental one that
uses sqlite3 database (rope.contrib.autoimport.sqlite.AutoImport). New and
existing integrations should migrate to the sqlite3 storage as the pickle-based
autoimport will be removed in the future.
`rope.contrib.autoimport.sqlite`
--------------------------------
Currently, the sqlite3-based only stores autoimport cache in an in-memory
sqlite3 database, you can make it write the import cache to persistent storage
by passing memory=False to AutoImport constructor. This default will change in
the future, if you want to always store the autoimport cache in-memory, then
you need to explicitly pass memory=True.
It must be closed when done with the ``AutoImport.close()`` method.
AutoImport can search for a name from both modules and statements you can import from them.
.. code-block:: python
from rope.base.project import Project
from rope.contrib.autoimport import AutoImport
project = Project("/path/to/project")
autoimport = AutoImport(project, memory=False)
autoimport.generate_resource_cache() # Generates a cache of the local modules, from the project you're working on
autoimport.generate_modules_cache() # Generates a cache of external modules
print(autoimport.search("Dict"))
autoimport.close()
project.close()
It provides two new search methods:
- search_full() - returns a list of mostly unsorted tuples. This has itemkind and source information.
- search() - simpler wrapper around search_full with a basic sorting algorithm
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 dependent 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=1711292392.0
rope-1.13.0/docs/overview.rst 0000644 0001751 0000177 00000074450 14600037750 015512 0 ustar 00runner docker ===============
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 :ref:`library:Using Rope As A Library` more useful.
.. contents:: Table of Contents
``.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 code. 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.
Moving Global Classes/Functions/Variables
-----------------------------------------
You can move global classes/function/variables to another module by using the
Move refactoring on a global object:
For instance, in this refactoring, if you are moving ``twice()`` to
``pkg1.mod2``:
.. code-block:: python
# pkg1/mod1.py
def twice(a):
return a * 2
print(twice(4))
.. code-block:: python
# pkg1/mod3.py
import pkg1.mod1
pkg1.mod1.twice(13)
When asked for the destination module, put in ``pkg1.mod2``. Rope will update
all the imports.
.. code-block:: python
# pkg1/mod1.py
import pkg1.mod2
print(pkg1.mod2.twice(4))
.. code-block:: python
# pkg1/mod2.py
def twice(a):
return a * 2
.. code-block:: python
# pkg1/mod3.py
import pkg1.mod2
pkg1.mod2.twice(13)
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 tries 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
:ref:`library:Restructuring`.
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
:ref:`library:Writing A \`FileSystemCommands\``.
.. _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=1711292392.0
rope-1.13.0/docs/release-process.rst 0000644 0001751 0000177 00000001652 14600037750 016732 0 ustar 00runner docker Release Process
===============
Pre-Release
-----------
1. Update :ref:`gha-cache-key.txt `:
``pip-compile --extra dev --generate-hashes -o gha-cache-key.txt --resolver=backtracking``
Release
-------
1. Ensure tickets assigned to Milestones are up to date
2. Update ``CHANGELOG.md``
3. Close milestone
4. Increment version number in ``pyproject.toml``
5. `git commit && git push`
6. Tag the release with the tag annotation containing the release information,
``python bin/tag-release.py``
7. Create Github Release
8. Publish release announcements to GitHub Discussions
Release Schedule
================
Rope has a release schedule once a month, usually sometime close to the 15th of
each month. However, this schedule is not a guaranteed date, if there is a
particularly urgent change or if there's not enough pull requests for the
month, there may be additional releases or the release window may be skipped.
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1711292392.0
rope-1.13.0/docs/rope.rst 0000644 0001751 0000177 00000002663 14600037750 014606 0 ustar 00runner docker 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 :ref:`overview:Rope Overview`.
.. _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=1711292392.0
rope-1.13.0/pyproject.toml 0000644 0001751 0000177 00000004522 14600037750 015067 0 ustar 00runner docker [project]
name = 'rope'
description = 'a python refactoring library...'
readme = 'README.rst'
requires-python = '>=3.8'
classifiers = [
'Development Status :: 4 - Beta',
'Operating System :: OS Independent',
'Environment :: X11 Applications',
'Environment :: Win32 (MS Windows)',
'Environment :: MacOS X',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)',
'Natural Language :: English',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Topic :: Software Development',
]
version = '1.13.0'
dependencies = ['pytoolconfig[global] >= 1.2.2']
[[project.authors]]
name = 'Ali Gholami Rudi'
email = 'aligrudi@users.sourceforge.net'
[[project.maintainers]]
name = 'Lie Ryan'
email = 'lieryan.24@proton.me'
[project.license]
text = 'LGPL-3.0-or-later'
[project.urls]
Source = 'https://github.com/python-rope/rope'
Documentation = 'https://rope.readthedocs.io/'
[project.optional-dependencies]
doc = [
'pytoolconfig[doc]',
"sphinx>=4.5.0",
"sphinx-autodoc-typehints>=1.18.1",
"sphinx-rtd-theme>=1.0.0",
]
dev = [
'pytest>=7.0.1',
'pytest-cov>=4.1.0',
'pytest-timeout>=2.1.0',
'build>=0.7.0',
'pre-commit>=2.20.0',
]
release = [
'toml>=0.10.2',
'twine>=4.0.2',
'pip-tools>=6.12.1',
]
[tool.setuptools]
packages = [
'rope',
'rope.base',
'rope.base.oi',
'rope.base.oi.type_hinting',
'rope.base.oi.type_hinting.providers',
'rope.base.oi.type_hinting.resolvers',
'rope.base.utils',
'rope.contrib',
'rope.contrib.autoimport',
'rope.refactor',
'rope.refactor.importutils',
]
[tool.black]
target-version = [
'py36',
'py37',
'py38',
'py39',
]
include = 'rope/.*\.pyi?$'
force-exclude = 'ropetest|rope/base/prefs.py'
[tool.isort]
profile = "black"
[tool.pytest.ini_options]
testpaths = "ropetest"
python_files = [
"*test.py",
"__init__.py",
]
markers = [
"time_limit: sets a maximum amount of time the test can run",
]
[build-system]
requires = [
'setuptools',
]
build-backend = 'setuptools.build_meta'
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1711292398.3995526
rope-1.13.0/rope/ 0000755 0001751 0000177 00000000000 14600037756 013123 5 ustar 00runner docker ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1711292392.0
rope-1.13.0/rope/__init__.py 0000644 0001751 0000177 00000002605 14600037750 015231 0 ustar 00runner docker """rope, a python refactoring library"""
import importlib.metadata
try:
VERSION = importlib.metadata.version("rope")
except importlib.metadata.PackageNotFoundError:
def get_fallback_version():
import pathlib
import re
pyproject = (
pathlib.Path(__file__).resolve().parent.parent / "pyproject.toml"
).read_text()
version = re.search("version.*=.*'(.*)'", pyproject)
return version.group(1) if version else None
VERSION = get_fallback_version()
INFO = __doc__
COPYRIGHT = """\
Copyright (C) 2021-2023 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=1711292398.4035525
rope-1.13.0/rope/base/ 0000755 0001751 0000177 00000000000 14600037756 014035 5 ustar 00runner docker ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1711292392.0
rope-1.13.0/rope/base/__init__.py 0000644 0001751 0000177 00000000241 14600037750 016135 0 ustar 00runner docker """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=1711292392.0
rope-1.13.0/rope/base/arguments.py 0000644 0001751 0000177 00000006323 14600037750 016412 0 ustar 00runner docker import rope.base.evaluate
from rope.base import ast
class Arguments:
"""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:
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:
def __init__(self, pyname, arguments, scope):
"""`arguments` 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=1711292392.0
rope-1.13.0/rope/base/ast.py 0000644 0001751 0000177 00000004731 14600037750 015175 0 ustar 00runner docker import ast
import sys
from ast import * # noqa: F401,F403
from rope.base import fscommands
try:
# Suppress the mypy complaint: Module "ast" has no attribute "_const_node_type_names"
from ast import _const_node_type_names # type:ignore
except ImportError:
# backported from stdlib `ast`
assert sys.version_info < (3, 8)
_const_node_type_names = {
bool: "NameConstant", # should be before int
type(None): "NameConstant",
int: "Num",
float: "Num",
complex: "Num",
str: "Str",
bytes: "Bytes",
type(...): "Ellipsis",
}
def parse(source, filename="", *args, **kwargs): # type: ignore
if isinstance(source, str):
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=filename, *args, **kwargs)
except (TypeError, ValueError) as e:
error = SyntaxError()
error.lineno = 1
error.filename = filename
error.msg = str(e)
raise error
def call_for_nodes(node, callback):
"""
Pre-order depth-first traversal of AST nodes, calling `callback(node)` for
each node visited.
When each node is visited, `callback(node)` will be called with the visited
`node`, then its children node will be visited.
If `callback(node)` returns `True` for a node, then the descendants of that
node will not be visited.
See _ResultChecker._find_node for an example.
"""
result = callback(node)
if not result:
for child in ast.iter_child_nodes(node):
call_for_nodes(child, callback)
class RopeNodeVisitor(ast.NodeVisitor):
def visit(self, node):
"""Modified from ast.NodeVisitor to match rope's existing Visitor implementation"""
method = "_" + node.__class__.__name__
visitor = getattr(self, method, self.generic_visit)
return visitor(node)
def get_const_subtype_name(node):
"""Get pre-3.8 ast node name"""
# fmt: off
assert sys.version_info >= (3, 8), "This should only be called in Python 3.8 and above"
# fmt: on
assert isinstance(node, ast.Constant)
return _const_node_type_names[type(node.value)]
def get_node_type_name(node):
return (
get_const_subtype_name(node)
if isinstance(node, ast.Constant)
else node.__class__.__name__
)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1711292392.0
rope-1.13.0/rope/base/builtins.py 0000644 0001751 0000177 00000063230 14600037750 016236 0 ustar 00runner docker """This module tries to support builtin types and functions."""
import inspect
import io
import rope.base.evaluate
from rope.base import arguments, ast, pynames, pyobjects, utils
class BuiltinModule(pyobjects.AbstractModule):
def __init__(self, name, pycore=None, initial={}):
super().__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:
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().__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:
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:
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().__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().__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().__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().__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().__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().__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().__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().__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().__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().__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 = [node.arg for node in self.arguments.args if isinstance(node, ast.arg)]
if self.arguments.vararg:
result.append("*" + self.arguments.vararg.arg)
if self.arguments.kwarg:
result.append("**" + self.arguments.kwarg.arg)
return result
@property
def parent(self):
return self.scope.pyobject
class BuiltinObject(BuiltinClass):
def __init__(self):
super().__init__(object, {})
class BuiltinType(BuiltinClass):
def __init__(self):
super().__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()),
"open": BuiltinName(BuiltinFunction(function=_open_function, builtin=open)),
"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)),
"input": BuiltinName(BuiltinFunction(function=_input_function, builtin=input)),
}
builtins = BuiltinModule("builtins", initial=_initial_builtins)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1711292392.0
rope-1.13.0/rope/base/change.py 0000644 0001751 0000177 00000032553 14600037750 015636 0 ustar 00runner docker import datetime
import difflib
import os
import time
from typing import Union
import rope.base.fscommands
from rope.base import exceptions, taskhandle, utils
from rope.base.fscommands import FileContent
class Change:
"""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.DEFAULT_JOB_SET):
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.DEFAULT_JOB_SET):
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 = f"{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.DEFAULT_JOB_SET):
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 {}\nrename to {}".format(
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().__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().__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:
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: Union[str, FileContent]):
data: FileContent
if not isinstance(contents, bytes):
data = rope.base.fscommands.unicode_to_file_data(
contents,
newlines=resource.newlines,
)
else:
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 OSError 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:
def convertChangeSet(self, change):
description = change.description
changes = [self(child) for child in change.changes]
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:
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=1711292392.0
rope-1.13.0/rope/base/codeanalyze.py 0000644 0001751 0000177 00000026776 14600037750 016721 0 ustar 00runner docker import bisect
import re
import token
import tokenize
class ChangeCollector:
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:
"""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:
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:
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:
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:
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:
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"(?