diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 72a063cbb..595d3735a 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -2,7 +2,8 @@ We welcome pull requests from cmd2 users and seasoned Python developers alike! Follow these steps to contribute: -1. Find an issue that needs assistance by searching for the [Help Wanted](https://github.com/python-cmd2/cmd2/labels/help%20wanted) tag +1. Find an issue that needs assistance by searching for + the [Help Wanted](https://github.com/python-cmd2/cmd2/labels/help%20wanted) tag 2. Let us know you're working on it by posting a comment on the issue @@ -46,31 +47,32 @@ The tables below list all prerequisites along with the minimum required version #### Prerequisites to run cmd2 applications | Prerequisite | Minimum Version | -| --------------------------------------------------- |-----------------| +|-----------------------------------------------------|-----------------| | [python](https://www.python.org/downloads/) | `3.8` | | [pyperclip](https://github.com/asweigart/pyperclip) | `1.6` | | [setuptools](https://pypi.org/project/setuptools/) | `34.4` | | [wcwidth](https://pypi.python.org/pypi/wcwidth) | `0.1.7` | - #### Additional prerequisites to run cmd2 unit tests -| Prerequisite | Minimum Version | -| ------------------------------------------- | --------------- | -| [pytest](http://doc.pytest.org/en/latest/) | `3.0.6` | -| [pytest-mock](https://pypi.org/project/pytest-mock/) | `1.3` | +| Prerequisite | Minimum Version | +|------------------------------------------------------|-----------------| +| [pytest](http://doc.pytest.org/en/latest/) | `3.0.6` | +| [pytest-mock](https://pypi.org/project/pytest-mock/) | `1.3` | #### Additional prerequisites to build cmd2 documentation -| Prerequisite | Minimum Version | -| ------------------------------------------- | --------------- | -| [sphinx](http://www.sphinx-doc.org) | `2.0.0` | -| [sphinx-rtd-theme](https://github.com/snide/sphinx_rtd_theme) | `0.1.9` | + +| Prerequisite | Minimum Version | +|---------------------------------------------------------------|-----------------| +| [sphinx](http://www.sphinx-doc.org) | `2.0.0` | +| [sphinx-rtd-theme](https://github.com/snide/sphinx_rtd_theme) | `0.1.9` | #### Optional prerequisites for enhanced unit test features -| Prerequisite | Minimum Version | -| ------------------------------------------- | --------------- | -| [pytest-cov](https://pypi.python.org/pypi/pytest-cov) | `2.4` | -| [flake8](http://flake8.pycqa.org/en/latest/)| `3.0` | + +| Prerequisite | Minimum Version | +|-------------------------------------------------------|-----------------| +| [pytest-cov](https://pypi.python.org/pypi/pytest-cov) | `2.4` | +| [ruff](https://github.com/astral-sh/ruff) | `0.7.0` | If Python is already installed in your machine, run the following commands to validate the versions: @@ -90,9 +92,9 @@ installed simultaneously. #### Setting up your system -1. Install [Git](https://git-scm.com/) or your favorite Git client. If you aren't comfortable with Git at the -command-line, then both [SmartGit](http://www.syntevo.com/smartgit/) and [GitKraken](https://www.gitkraken.com) are -excellent cross-platform graphical Git clients. +1. Install [Git](https://git-scm.com/) or your favorite Git client. If you aren't comfortable with Git at the + command-line, then both [SmartGit](http://www.syntevo.com/smartgit/) and [GitKraken](https://www.gitkraken.com) are + excellent cross-platform graphical Git clients. 2. (Optional) [Set up an SSH key](https://help.github.com/articles/generating-an-ssh-key/) for GitHub. 3. Create a parent projects directory on your system. For this guide, it will be assumed that it is `~/src`. @@ -100,14 +102,14 @@ excellent cross-platform graphical Git clients. 1. Go to the top-level cmd2 repository: 2. Click the "Fork" button in the upper right hand corner of the interface -([more details here](https://help.github.com/articles/fork-a-repo/)) + ([more details here](https://help.github.com/articles/fork-a-repo/)) 3. After the repository has been forked, you will be taken to your copy of the cmd2 repo at `yourUsername/cmd2` #### Cloning your fork 1. Open a terminal / command line / Bash shell in your projects directory (_e.g.: `~/src/`_) 2. Clone your fork of cmd2, making sure to replace `yourUsername` with your GitHub username. This will download the -entire cmd2 repo to your projects directory. + entire cmd2 repo to your projects directory. ```sh $ git clone https://github.com/yourUsername/cmd2.git @@ -134,33 +136,34 @@ Do this prior to every time you create a branch for a PR: 1. Make sure you are on the `master` branch - > ```sh +> ```sh > $ git status > On branch master > Your branch is up-to-date with 'origin/master'. > ``` - > If your aren't on `master`, resolve outstanding files and commits and checkout the `master` branch +> If your aren't on `master`, resolve outstanding files and commits and checkout the `master` branch - > ```sh +> ```sh > $ git checkout master > ``` 2. Do a pull with rebase against `upstream` - > ```sh +> ```sh > $ git pull --rebase upstream master > ``` - > This will pull down all of the changes to the official master branch, without making an additional commit in your local repo. +> This will pull down all of the changes to the official master branch, without making an additional commit in your +> local repo. 3. (_Optional_) Force push your updated master branch to your GitHub fork - > ```sh +> ```sh > $ git push origin master --force > ``` - > This will overwrite the master branch of your fork. +> This will overwrite the master branch of your fork. ### Creating a branch @@ -187,43 +190,52 @@ and to push to GitHub: $ git push origin [name_of_your_new_branch] ``` -##### If you need more help with branching, take a look at _[this](https://github.com/Kunena/Kunena-Forum/wiki/Create-a-new-branch-with-git-and-manage-branches)_. +##### If you need more help with branching, take a look at +_[this](https://github.com/Kunena/Kunena-Forum/wiki/Create-a-new-branch-with-git-and-manage-branches)_. ### Setting up for cmd2 development + For doing cmd2 development, it is recommended you create a virtual environment using Conda or Virtualenv and install the package from the source. #### Create a new environment for cmd2 using Pipenv + `cmd2` has support for using [Pipenv](https://docs.pipenv.org/en/latest/) for development. -`Pipenv` essentially combines the features of `pip` and `virtualenv` into a single tool. `cmd2` contains a Pipfile which - makes it extremely easy to setup a `cmd2` development environment using `pipenv`. +`Pipenv` essentially combines the features of `pip` and `virtualenv` into a single tool. `cmd2` contains a Pipfile +which +makes it extremely easy to setup a `cmd2` development environment using `pipenv`. To create a virtual environment and install everything needed for `cmd2` development using `pipenv`, do the following from a GitHub checkout: + ```sh pipenv install --dev ``` To create a new virtualenv, using a specific version of Python you have installed (and on your PATH), use the --python VERSION flag, like so: + ```sh pipenv install --dev --python 3.8 ``` Then you can enter that virtual environment with: + ```sh pipenv shell ``` #### Create a new environment for cmd2 using Conda + ```sh $ conda create -n cmd2_py38 python=3.8 $ conda activate cmd2_py38 ``` #### Create a new environment for cmd using Virtualenv + We recommend that you use [pyenv](https://github.com/pyenv/pyenv) to manage your installed python versions. ```sh @@ -233,6 +245,7 @@ pyenv versions # Install python version defined pyenv install 3.8.2 ``` + With the Python version installed, you can set the virtualenv properly. ```sh @@ -246,6 +259,7 @@ Assuming you cloned the repository to `~/src/cmd2` you can install cmd2 in Changes to the source code are immediately available when the python interpreter imports `cmd2`, there is no need to re-install the module after every change. This command will also install all of the runtime dependencies for `cmd2` and modules used for development of `cmd2`: + ```sh $ cd ~/src/cmd2 $ pip install -e .[dev] @@ -259,11 +273,13 @@ learn the specific incantations required for each project you're familiar with. This project uses `invoke ` to provide a clean, high-level interface for these development tasks. To see the full list of functions available: + ```sh $ invoke -l ``` You can run multiple tasks in a single invocation, for example:: + ```sh $ invoke docs sdist wheel ``` @@ -276,6 +292,7 @@ If you want to see the details about what `invoke` is doing under the hood, have a look at `tasks.py`. Now you can check if everything is installed and working: + ```sh $ cd ~src/cmd2 $ invoke pytest @@ -284,34 +301,39 @@ $ invoke pytest If the tests are executed it means that dependencies and project are installed succesfully. You can also run the example app and see a prompt that says "(Cmd)" running the command: + ```sh $ python examples/example.py ``` You can type `help` to get help or `quit` to quit. If you see that, then congratulations -– you're all set. Otherwise, refer to the cmd2 [installation instructions](https://cmd2.readthedocs.io/en/latest/overview/installation.html). +– you're all set. Otherwise, refer to the +cmd2 [installation instructions](https://cmd2.readthedocs.io/en/latest/overview/installation.html). There also might be an error in the console of your Bash / terminal / command line that will help identify the problem. ### Making changes + This bit is up to you! #### How to find code in the cmd2 codebase to fix/edit -The cmd2 project directory structure is pretty simple and straightforward. All -actual code for cmd2 is located underneath the `cmd2` directory. The code to -generate the documentation is in the `docs` directory. Unit tests are in the -`tests` directory. The `examples` directory contains examples of how to use -cmd2. There are various other files in the root directory, but these are +The cmd2 project directory structure is pretty simple and straightforward. All +actual code for cmd2 is located underneath the `cmd2` directory. The code to +generate the documentation is in the `docs` directory. Unit tests are in the +`tests` directory. The `examples` directory contains examples of how to use +cmd2. There are various other files in the root directory, but these are primarily related to continuous integration and release deployment. #### Changes to the documentation files If you made changes to any file in the `/docs` directory, you need to build the Sphinx documentation and make sure your changes look good: + ```sh $ invoke docs ``` + In order to see the changes, use your web browser of choice to open `~/cmd2/docs/_build/html/index.html`. If you would rather use a webserver to view the documentation, including @@ -327,24 +349,29 @@ served (usually [http://localhost:8000](http://localhost:8000)). ### Static code analysis You should have some sort of [PEP 8](https://www.python.org/dev/peps/pep-0008/)-based linting running in your editor or -IDE or at the command line before you commit code. `cmd2` uses [flake8](http://flake8.pycqa.org/en/latest/) as part of -its continuous integration (CI) process. [pylint](https://www.pylint.org) is another good Python linter which can be -run at the command line but also can integrate with many IDEs and editors. +IDE or at the command line before you commit code. `cmd2` uses [ruff](https://github.com/astral-sh/ruff) as part of +its continuous integration (CI) process for both linting and auto-formatting. -> Please do not ignore any linting errors in code you write or modify, as they are meant to **help** you and to ensure a clean and simple code base. Don't worry about linting errors in code you don't touch though - cleaning up the legacy code is a work in progress. +> Please do not ignore any linting errors in code you write or modify, as they are meant to **help** you and to ensure a +> clean and simple code base. Don't worry about linting errors in code you don't touch though - cleaning up the legacy +> code is a work in progress. ### Running the test suite + When you're ready to share your code, run the test suite: + ```sh $ cd ~/cmd2 $ invoke pytest ``` + and ensure all tests pass. Running the test suite also calculates test code coverage. A summary of coverage is shown on the screen. A full report is available in `~/cmd2/htmlcov/index.html`. ### Squashing your commits + When you make a pull request, it is preferable for all of your changes to be in one commit. If you have made more then one commit, then you can _squash_ your commits. To do this, see [this article](http://forum.freecodecamp.com/t/how-to-squash-multiple-commits-into-one-with-git/13231). @@ -374,16 +401,16 @@ fork and re-fork. There are two methods of creating a pull request for cmd2: -- Editing files on a local clone (recommended) -- Editing files via the GitHub Interface +- Editing files on a local clone (recommended) +- Editing files via the GitHub Interface ##### Method 1: Editing via your local fork _(recommended)_ This is the recommended method. Read about [how to set up and maintain a local instance of cmd2](#maintaining-your-fork). -1. Perform the maintenance step of rebasing `master` -2. Ensure you're on the `master` branch using `git status`: +1. Perform the maintenance step of rebasing `master` +2. Ensure you're on the `master` branch using `git status`: ```sh $ git status @@ -393,29 +420,29 @@ Your branch is up-to-date with 'origin/master'. nothing to commit, working directory clean ``` -1. If you're not on master or your working directory is not clean, resolve - any outstanding files/commits and checkout master `git checkout master` +1. If you're not on master or your working directory is not clean, resolve + any outstanding files/commits and checkout master `git checkout master` -2. Create a branch off of `master` with git: `git checkout -B +2. Create a branch off of `master` with git: `git checkout -B branch/name-here` **Note:** Branch naming is important. Use a name like - `fix/short-fix-description` or `feature/short-feature-description`. Review - the [Contribution Guidelines](#contribution-guidelines) for more detail. + `fix/short-fix-description` or `feature/short-feature-description`. Review + the [Contribution Guidelines](#contribution-guidelines) for more detail. -3. Edit your file(s) locally with the editor of your choice +3. Edit your file(s) locally with the editor of your choice -4. Check your `git status` to see unstaged files +4. Check your `git status` to see unstaged files -5. Add your edited files: `git add path/to/filename.ext` You can also do: `git +5. Add your edited files: `git add path/to/filename.ext` You can also do: `git add .` to add all unstaged files. Take care, though, because you can - accidentally add files you don't want added. Review your `git status` first. + accidentally add files you don't want added. Review your `git status` first. -6. Commit your edits: `git commit -m "Brief description of commit"`. Do not add the issue number in the commit message. +6. Commit your edits: `git commit -m "Brief description of commit"`. Do not add the issue number in the commit message. -7. Squash your commits, if there are more than one +7. Squash your commits, if there are more than one -8. Push your commits to your GitHub Fork: `git push -u origin branch/name-here` +8. Push your commits to your GitHub Fork: `git push -u origin branch/name-here` -9. Go to [Common steps](#common-steps) +9. Go to [Common steps](#common-steps) ##### Method 2: Editing via the GitHub interface @@ -428,63 +455,66 @@ how to do it. ### Common steps -1. Once the edits have been committed, you will be prompted to create a pull - request on your fork's GitHub page - -2. By default, all pull requests should be against the cmd2 main repo, `master` - branch +1. Once the edits have been committed, you will be prompted to create a pull + request on your fork's GitHub page -3. Submit a pull request from your branch to cmd2's `master` branch +2. By default, all pull requests should be against the cmd2 main repo, `master` + branch -4. The title (also called the subject) of your PR should be descriptive of your - changes and succinctly indicate what is being fixed +3. Submit a pull request from your branch to cmd2's `master` branch - - **Do not add the issue number in the PR title or commit message** +4. The title (also called the subject) of your PR should be descriptive of your + changes and succinctly indicate what is being fixed - - Examples: `Add test cases for Unicode support`; `Correct typo in overview documentation` + - **Do not add the issue number in the PR title or commit message** -5. In the body of your PR include a more detailed summary of the changes you - made and why + - Examples: `Add test cases for Unicode support`; `Correct typo in overview documentation` - - If the PR is meant to fix an existing bug/issue, then, at the end of - your PR's description, append the keyword `closes` and #xxxx (where xxxx - is the issue number). Example: `closes #1337`. This tells GitHub to - close the existing issue if the PR is merged. +5. In the body of your PR include a more detailed summary of the changes you + made and why -6. Indicate what local testing you have done (e.g. what OS and version(s) of Python did you run the - unit test suite with) + - If the PR is meant to fix an existing bug/issue, then, at the end of + your PR's description, append the keyword `closes` and #xxxx (where xxxx + is the issue number). Example: `closes #1337`. This tells GitHub to + close the existing issue if the PR is merged. -7. Creating the PR causes our continuous integration (CI) systems to automatically run all of the - unit tests on all supported OSes and all supported versions of Python. You should watch your PR - to make sure that all unit tests pass on every version of Python for each of Linux, Windows, and - macOS. +6. Indicate what local testing you have done (e.g. what OS and version(s) of Python did you run the + unit test suite with) -8. If any unit tests fail, you should look at the details and fix the failures. You can then push - the fix to the same branch in your fork. The PR will automatically get updated and the CI system - will automatically run all of the unit tests again. +7. Creating the PR causes our continuous integration (CI) systems to automatically run all of the + unit tests on all supported OSes and all supported versions of Python. You should watch your PR + to make sure that all unit tests pass on every version of Python for each of Linux, Windows, and + macOS. +8. If any unit tests fail, you should look at the details and fix the failures. You can then push + the fix to the same branch in your fork. The PR will automatically get updated and the CI system + will automatically run all of the unit tests again. ### How we review and merge pull requests -cmd2 has a team of volunteer Maintainers. These Maintainers routinely go through open pull requests in a process called [Quality Assurance](https://en.wikipedia.org/wiki/Quality_assurance) (QA). We also use multiple continuous -integration (CI) providers to automatically run all of the unit tests on multiple operating systems and versions of Python. +cmd2 has a team of volunteer Maintainers. These Maintainers routinely go through open pull requests in a process +called [Quality Assurance](https://en.wikipedia.org/wiki/Quality_assurance) (QA). We also use multiple continuous +integration (CI) providers to automatically run all of the unit tests on multiple operating systems and versions of +Python. 1. If your changes can merge without conflicts and all unit tests pass for all OSes and supported versions of Python, -then your pull request (PR) will have a big green checkbox which says something like "All Checks Passed" next to it. -If this is not the case, there will be a link you can click on to get details regarding what the problem is. -It is your responsibility to make sure all unit tests are passing. Generally a Maintainer will not QA a -pull request unless it can merge without conflicts and all unit tests pass on all supported platforms. + then your pull request (PR) will have a big green checkbox which says something like "All Checks Passed" next to it. + If this is not the case, there will be a link you can click on to get details regarding what the problem is. + It is your responsibility to make sure all unit tests are passing. Generally a Maintainer will not QA a + pull request unless it can merge without conflicts and all unit tests pass on all supported platforms. -2. If a Maintainer QA's a pull request and confirms that the new code does what it is supposed to do without seeming to introduce any new bugs, -and doesn't present any backward compatibility issues, they will merge the pull request. - -If you would like to apply to join our Maintainer team, message [@tleonhardt](https://github.com/tleonhardt) with links to 5 of your pull requests that have been accepted. +2. If a Maintainer QA's a pull request and confirms that the new code does what it is supposed to do without seeming to + introduce any new bugs, + and doesn't present any backward compatibility issues, they will merge the pull request. +If you would like to apply to join our Maintainer team, message [@tleonhardt](https://github.com/tleonhardt) with links +to 5 of your pull requests that have been accepted. ### How we close stale issues We will close any issues that have been inactive for more than 60 days or pull requests that have been inactive for more than 30 days, except those that match any of the following criteria: + - bugs that are confirmed - pull requests that are waiting on other pull requests to be merged - features that are part of a cmd2 GitHub Milestone or Project @@ -517,37 +547,44 @@ Be sure to post in the PR conversation that you have made the requested changes. ### Other resources -- [PEP 8 Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) +- [PEP 8 Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) -- [Searching for your issue on GitHub](https://help.github.com/articles/searching-issues/) +- [Searching for your issue on GitHub](https://help.github.com/articles/searching-issues/) -- [Creating a new GitHub issue](https://help.github.com/articles/creating-an-issue/) +- [Creating a new GitHub issue](https://help.github.com/articles/creating-an-issue/) ### Advice Here is some advice regarding what makes a good pull request (PR) from the perspective of the cmd2 maintainers: + - Multiple smaller PRs divided by topic are better than a single large PR containing a bunch of unrelated changes - Maintaining backward compatibility is important - Good unit/functional tests are very important - Accurate documentation is also important -- Adding new features is of the lowest importance, behind bug fixes, unit test additions/improvements, code cleanup, and documentation +- Adding new features is of the lowest importance, behind bug fixes, unit test additions/improvements, code cleanup, and + documentation - It's best to create a dedicated branch for a PR, use it only for that PR, and delete it once the PR has been merged - It's good if the branch name is related to the PR contents, even if it's just "fix123" or "add_more_tests" - Code coverage of the unit tests matters, so try not to decrease it -- Think twice before adding dependencies to third-party libraries (outside of the Python standard library) because it could affect a lot of users +- Think twice before adding dependencies to third-party libraries (outside of the Python standard library) because it + could affect a lot of users ### Developing in an IDE -We recommend using [Visual Studio Code](https://code.visualstudio.com) with the [Python extension](https://code.visualstudio.com/docs/languages/python) and its [Integrated Terminal](https://code.visualstudio.com/docs/python/debugging) debugger for debugging since it has +We recommend using [Visual Studio Code](https://code.visualstudio.com) with +the [Python extension](https://code.visualstudio.com/docs/languages/python) and +its [Integrated Terminal](https://code.visualstudio.com/docs/python/debugging) debugger for debugging since it has excellent support for debugging console applications. -[PyCharm](https://www.jetbrains.com/pycharm/) is also quite good and has very nice [code inspection](https://www.jetbrains.com/help/pycharm/code-inspection.html) capabilities. +[PyCharm](https://www.jetbrains.com/pycharm/) is also quite good and has very +nice [code inspection](https://www.jetbrains.com/help/pycharm/code-inspection.html) capabilities. ## Branching Strategy and Semantic Versioning Starting with version 1.0.0, `cmd2` has adopted [Semantic Versioning](https://semver.org). ### Semantic Versioning Summary + Given a version number `MAJOR`.`MINOR`.`PATCH`, increment the: - `MAJOR` version when you make incompatible API changes, @@ -567,8 +604,8 @@ change needs to be committed for an upcoming `MAJOR` release, then this work sho to a **2.0.0** branch until such a time as we are ready to release version 2.0.0. Following this strategy, releases are always done from the **master** branch and `MAJOR` or `MINOR` -branches are merged to **master** immediately prior to doing a release. Once merged to **master**, the -other branches can be deleted. All releases are tagged so that they can be reproduced if necessary. +branches are merged to **master** immediately prior to doing a release. Once merged to **master**, the +other branches can be deleted. All releases are tagged so that they can be reproduced if necessary. ## Publishing a new release @@ -585,5 +622,6 @@ mostly automated. The manual steps are all git operations. Here's the checklist: 1. Run `invoke pypi` to clean, build, and upload a new release to [PyPi](https://pypi.org/) ## Acknowledgement + Thanks to the good folks at [freeCodeCamp](https://github.com/freeCodeCamp/freeCodeCamp) for creating an excellent `CONTRIBUTING` file which we have borrowed heavily from. diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index f168f598f..9c474be9c 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -2,7 +2,7 @@ # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions name: Format -on: [push, pull_request] +on: [ push, pull_request ] permissions: contents: read @@ -11,8 +11,8 @@ jobs: lint: strategy: matrix: - os: [ubuntu-latest] - python-version: ["3.12"] + os: [ ubuntu-latest ] + python-version: [ "3.12" ] fail-fast: false runs-on: ${{ matrix.os }} steps: @@ -26,8 +26,6 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install python prerequisites - run: pip install -U --user pip setuptools setuptools-scm black isort - - name: Black - run: python -m black --check --diff . - - name: isort - run: python -m isort --check-only . + run: pip install -U --user ruff + - name: Ruff format + run: ruff format --check diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f7a972277..967e9a5fc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,7 +2,7 @@ # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions name: Lint -on: [push, pull_request] +on: [ push, pull_request ] permissions: contents: read @@ -11,8 +11,8 @@ jobs: lint: strategy: matrix: - os: [ubuntu-latest] - python-version: ["3.12"] + os: [ ubuntu-latest ] + python-version: [ "3.12" ] fail-fast: false runs-on: ${{ matrix.os }} steps: @@ -26,6 +26,6 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install python prerequisites - run: pip install -U --user pip setuptools setuptools-scm nox - - name: Lint - run: python -m nox --non-interactive --session validate-${{ matrix.python-version }} -k flake8 + run: pip install -U --user ruff + - name: Ruff lint + run: ruff check diff --git a/.gitignore b/.gitignore index 11b71aa3a..c3c267ce6 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,6 @@ Pipfile.lock # pyenv version file .python-version + +# uv +uv.lock diff --git a/Pipfile b/Pipfile index b9cdea27e..d5dcff6a2 100644 --- a/Pipfile +++ b/Pipfile @@ -9,18 +9,15 @@ setuptools = ">=34.4" wcwidth = ">=0.1.7" [dev-packages] -black = "*" -cmd2 = {editable = true,path = "."} -cmd2_ext_test = {editable = true,path = "plugins/ext_test"} +cmd2 = { editable = true, path = "." } +cmd2_ext_test = { editable = true, path = "plugins/ext_test" } codecov = "*" doc8 = "*" -flake8 = "*" -gnureadline = {version = "*",sys_platform = "== 'darwin'"} +gnureadline = { version = "*", sys_platform = "== 'darwin'" } invoke = "*" ipython = "*" -isort = "*" mypy = "*" -pyreadline3 = {version = ">=3.4",sys_platform = "== 'win32'"} +pyreadline3 = { version = ">=3.4", sys_platform = "== 'win32'" } pytest = "*" pytest-cov = "*" pytest-mock = "*" diff --git a/cmd2/ansi.py b/cmd2/ansi.py index d2f6832a8..52bf382a1 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -2,7 +2,8 @@ """ Support for ANSI escape sequences which are used for things like applying style to text, setting the window title, and asynchronous alerts. - """ +""" + import functools import re from enum import ( @@ -62,25 +63,25 @@ def __repr__(self) -> str: """ # Regular expression to match ANSI style sequence -ANSI_STYLE_RE = re.compile(fr'{ESC}\[[^m]*m') +ANSI_STYLE_RE = re.compile(rf'{ESC}\[[^m]*m') # Matches standard foreground colors: CSI(30-37|90-97|39)m -STD_FG_RE = re.compile(fr'{ESC}\[(?:[39][0-7]|39)m') +STD_FG_RE = re.compile(rf'{ESC}\[(?:[39][0-7]|39)m') # Matches standard background colors: CSI(40-47|100-107|49)m -STD_BG_RE = re.compile(fr'{ESC}\[(?:(?:4|10)[0-7]|49)m') +STD_BG_RE = re.compile(rf'{ESC}\[(?:(?:4|10)[0-7]|49)m') # Matches eight-bit foreground colors: CSI38;5;(0-255)m -EIGHT_BIT_FG_RE = re.compile(fr'{ESC}\[38;5;(?:1?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])m') +EIGHT_BIT_FG_RE = re.compile(rf'{ESC}\[38;5;(?:1?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])m') # Matches eight-bit background colors: CSI48;5;(0-255)m -EIGHT_BIT_BG_RE = re.compile(fr'{ESC}\[48;5;(?:1?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])m') +EIGHT_BIT_BG_RE = re.compile(rf'{ESC}\[48;5;(?:1?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])m') # Matches RGB foreground colors: CSI38;2;(0-255);(0-255);(0-255)m -RGB_FG_RE = re.compile(fr'{ESC}\[38;2(?:;(?:1?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])){{3}}m') +RGB_FG_RE = re.compile(rf'{ESC}\[38;2(?:;(?:1?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])){{3}}m') # Matches RGB background colors: CSI48;2;(0-255);(0-255);(0-255)m -RGB_BG_RE = re.compile(fr'{ESC}\[48;2(?:;(?:1?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])){{3}}m') +RGB_BG_RE = re.compile(rf'{ESC}\[48;2(?:;(?:1?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])){{3}}m') def strip_style(text: str) -> str: diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index e4601307b..9bb86e9f1 100644 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -92,7 +92,7 @@ def _looks_like_flag(token: str, parser: argparse.ArgumentParser) -> bool: return False # Flags have to start with a prefix character - if not token[0] in parser.prefix_chars: + if token[0] not in parser.prefix_chars: return False # If it looks like a negative number, it is not a flag unless there are negative-number-like flags diff --git a/cmd2/clipboard.py b/cmd2/clipboard.py index eda92bf64..454e3484c 100644 --- a/cmd2/clipboard.py +++ b/cmd2/clipboard.py @@ -2,6 +2,7 @@ """ This module provides basic ability to copy from and paste to the clipboard/pastebuffer. """ + import typing import pyperclip # type: ignore[import] diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 566d78787..dbd993495 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -21,6 +21,7 @@ Git repository on GitHub at https://github.com/python-cmd2/cmd2 """ + # This module has many imports, quite a few of which are only # infrequently utilized. To reduce the initial overhead of # import this module, many of these imports are lazy-loaded @@ -2019,9 +2020,8 @@ def _determine_ap_completer_type(parser: argparse.ArgumentParser) -> Type[argpar :param parser: the parser to examine :return: type of ArgparseCompleter """ - completer_type: Optional[ - Type[argparse_completer.ArgparseCompleter] - ] = parser.get_ap_completer_type() # type: ignore[attr-defined] + Completer = Optional[Type[argparse_completer.ArgparseCompleter]] + completer_type: Completer = parser.get_ap_completer_type() # type: ignore[attr-defined] if completer_type is None: completer_type = argparse_completer.DEFAULT_AP_COMPLETER @@ -5537,7 +5537,7 @@ def cmdloop(self, intro: Optional[str] = None) -> int: # type: ignore[override] """ # cmdloop() expects to be run in the main thread to support extensive use of KeyboardInterrupts throughout the # other built-in functions. You are free to override cmdloop, but much of cmd2's features will be limited. - if not threading.current_thread() is threading.main_thread(): + if threading.current_thread() is not threading.main_thread(): raise RuntimeError("cmdloop must be run in the main thread") # Register signal handlers diff --git a/cmd2/command_definition.py b/cmd2/command_definition.py index 1dcd4116f..9f713d83d 100644 --- a/cmd2/command_definition.py +++ b/cmd2/command_definition.py @@ -2,6 +2,7 @@ """ Supports the definition of commands in separate classes to be composed into cmd2.Cmd """ + from typing import ( TYPE_CHECKING, Callable, diff --git a/cmd2/decorators.py b/cmd2/decorators.py index 601da6f97..cd9fd358c 100644 --- a/cmd2/decorators.py +++ b/cmd2/decorators.py @@ -1,5 +1,6 @@ # coding=utf-8 """Decorators for ``cmd2`` commands""" + import argparse from typing import ( TYPE_CHECKING, diff --git a/cmd2/plugin.py b/cmd2/plugin.py index affe2421d..e7e2c6863 100644 --- a/cmd2/plugin.py +++ b/cmd2/plugin.py @@ -1,6 +1,7 @@ # # coding=utf-8 """Classes for the cmd2 plugin system""" + from dataclasses import ( dataclass, ) diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index f89d2b18d..b02aa73a2 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -2,6 +2,7 @@ """ Imports the proper Readline for the platform and provides utility functions for it """ + import sys from enum import ( Enum, diff --git a/cmd2/table_creator.py b/cmd2/table_creator.py index c7c277562..409e7a994 100644 --- a/cmd2/table_creator.py +++ b/cmd2/table_creator.py @@ -5,6 +5,7 @@ The general use case is to inherit from TableCreator to create a table class with custom formatting options. There are already implemented and ready-to-use examples of this below TableCreator's code. """ + import copy import io from collections import ( diff --git a/cmd2/transcript.py b/cmd2/transcript.py index a38c19023..fdbcd0299 100644 --- a/cmd2/transcript.py +++ b/cmd2/transcript.py @@ -9,6 +9,7 @@ This file contains the class necessary to make that work. This class is used in cmd2.py::run_transcript_tests() """ + import re import unittest from typing import ( diff --git a/cmd2/utils.py b/cmd2/utils.py index d1e077fdc..f718d5f14 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -1,5 +1,6 @@ # coding=utf-8 """Shared utility functions""" + import argparse import collections import functools @@ -157,7 +158,7 @@ def __init__( :param choices_provider: function that provides choices for this argument :param completer: tab completion function that provides choices for this argument """ - if val_type == bool: + if val_type is bool: def get_bool_choices(_) -> List[str]: # type: ignore[no-untyped-def] """Used to tab complete lowercase boolean values""" @@ -1189,9 +1190,7 @@ def get_defining_class(meth: Callable[..., Any]) -> Optional[Type[Any]]: if isinstance(meth, functools.partial): return get_defining_class(meth.func) if inspect.ismethod(meth) or ( - inspect.isbuiltin(meth) - and getattr(meth, '__self__') is not None - and getattr(meth.__self__, '__class__') # type: ignore[attr-defined] + inspect.isbuiltin(meth) and getattr(meth, '__self__') is not None and getattr(meth.__self__, '__class__') ): for cls in inspect.getmro(meth.__self__.__class__): # type: ignore[attr-defined] if meth.__name__ in cls.__dict__: diff --git a/examples/alias_startup.py b/examples/alias_startup.py index 72e969fcc..1ad493ffa 100755 --- a/examples/alias_startup.py +++ b/examples/alias_startup.py @@ -1,9 +1,10 @@ #!/usr/bin/env python # coding=utf-8 """A simple example demonstrating the following: - 1) How to add custom command aliases using the alias command - 2) How to run an initialization script at startup +1) How to add custom command aliases using the alias command +2) How to run an initialization script at startup """ + import os import cmd2 diff --git a/examples/arg_decorators.py b/examples/arg_decorators.py index 3b02835ed..e42960b1c 100755 --- a/examples/arg_decorators.py +++ b/examples/arg_decorators.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 # coding=utf-8 """An example demonstrating how use one of cmd2's argument parsing decorators""" + import argparse import os diff --git a/examples/argparse_completion.py b/examples/argparse_completion.py index 506f94e26..daad63ab0 100644 --- a/examples/argparse_completion.py +++ b/examples/argparse_completion.py @@ -3,6 +3,7 @@ """ A simple example demonstrating how to integrate tab completion with argparse-based commands. """ + import argparse from typing import ( Dict, diff --git a/examples/async_printing.py b/examples/async_printing.py index 55dff35bd..e94ee89a0 100755 --- a/examples/async_printing.py +++ b/examples/async_printing.py @@ -4,6 +4,7 @@ A simple example demonstrating an application that asynchronously prints alerts, updates the prompt and changes the window title """ + import random import threading import time diff --git a/examples/basic.py b/examples/basic.py index 1f9683bf9..6ce4d2838 100755 --- a/examples/basic.py +++ b/examples/basic.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 # coding=utf-8 """A simple example demonstrating the following: - 1) How to add a command - 2) How to add help for that command - 3) Persistent history - 4) How to run an initialization script at startup - 5) How to add custom command aliases using the alias command - 6) Shell-like capabilities +1) How to add a command +2) How to add help for that command +3) Persistent history +4) How to run an initialization script at startup +5) How to add custom command aliases using the alias command +6) Shell-like capabilities """ + import cmd2 from cmd2 import ( Bg, diff --git a/examples/basic_completion.py b/examples/basic_completion.py index febe58a3a..c713f2b0d 100755 --- a/examples/basic_completion.py +++ b/examples/basic_completion.py @@ -12,6 +12,7 @@ familiar with argparse. The recommended approach for tab completing positional tokens and flags is to use argparse-based completion. For an example integrating tab completion with argparse, see argparse_completion.py """ + import functools from typing import ( List, diff --git a/examples/custom_parser.py b/examples/custom_parser.py index 6e5a33b4a..94df3b054 100644 --- a/examples/custom_parser.py +++ b/examples/custom_parser.py @@ -2,6 +2,7 @@ """ Defines the CustomParser used with override_parser.py example """ + import sys from cmd2 import ( diff --git a/examples/decorator_example.py b/examples/decorator_example.py index 75bc8dff4..ea8fd3b50 100755 --- a/examples/decorator_example.py +++ b/examples/decorator_example.py @@ -10,6 +10,7 @@ all the commands in the transcript against decorator_example.py, verifying that the output produced matches the transcript. """ + import argparse from typing import ( List, diff --git a/examples/dynamic_commands.py b/examples/dynamic_commands.py index eee5b8cce..82dde732d 100755 --- a/examples/dynamic_commands.py +++ b/examples/dynamic_commands.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # coding=utf-8 -"""A simple example demonstrating how do_* commands can be created in a loop. -""" +"""A simple example demonstrating how do_* commands can be created in a loop.""" + import functools import cmd2 diff --git a/examples/environment.py b/examples/environment.py index 3eaaa8d1d..1bb9812be 100755 --- a/examples/environment.py +++ b/examples/environment.py @@ -3,6 +3,7 @@ """ A sample application for cmd2 demonstrating customized environment parameters """ + import cmd2 diff --git a/examples/event_loops.py b/examples/event_loops.py index 86dc01fba..e5435181a 100755 --- a/examples/event_loops.py +++ b/examples/event_loops.py @@ -6,6 +6,7 @@ This opens up the possibility of registering cmd2 input with event loops, like asyncio, without occupying the main loop. """ + import cmd2 diff --git a/examples/example.py b/examples/example.py index da6c3c9ff..2ff64d747 100755 --- a/examples/example.py +++ b/examples/example.py @@ -10,6 +10,7 @@ the transcript against example.py, verifying that the output produced matches the transcript. """ + import random import cmd2 diff --git a/examples/exit_code.py b/examples/exit_code.py index 23b172a1c..d8e538ced 100755 --- a/examples/exit_code.py +++ b/examples/exit_code.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # coding=utf-8 -"""A simple example demonstrating the following how to emit a non-zero exit code in your cmd2 application. -""" +"""A simple example demonstrating the following how to emit a non-zero exit code in your cmd2 application.""" + from typing import ( List, ) diff --git a/examples/hello_cmd2.py b/examples/hello_cmd2.py index 0e639e29b..a67205834 100755 --- a/examples/hello_cmd2.py +++ b/examples/hello_cmd2.py @@ -3,6 +3,7 @@ """ This is intended to be a completely bare-bones cmd2 application suitable for rapid testing and debugging. """ + from cmd2 import ( cmd2, ) diff --git a/examples/help_categories.py b/examples/help_categories.py index b2a1623fa..5c349422c 100755 --- a/examples/help_categories.py +++ b/examples/help_categories.py @@ -5,6 +5,7 @@ It also demonstrates the effects of decorator order when it comes to argparse errors occurring. """ + import functools import cmd2 diff --git a/examples/initialization.py b/examples/initialization.py index 292756d9d..426a5a4a2 100755 --- a/examples/initialization.py +++ b/examples/initialization.py @@ -1,17 +1,18 @@ #!/usr/bin/env python3 # coding=utf-8 """A simple example cmd2 application demonstrating the following: - 1) Colorizing/stylizing output - 2) Using multiline commands - 3) Persistent history - 4) How to run an initialization script at startup - 5) How to group and categorize commands when displaying them in help - 6) Opting-in to using the ipy command to run an IPython shell - 7) Allowing access to your application in py and ipy - 8) Displaying an intro banner upon starting your application - 9) Using a custom prompt - 10) How to make custom attributes settable at runtime + 1) Colorizing/stylizing output + 2) Using multiline commands + 3) Persistent history + 4) How to run an initialization script at startup + 5) How to group and categorize commands when displaying them in help + 6) Opting-in to using the ipy command to run an IPython shell + 7) Allowing access to your application in py and ipy + 8) Displaying an intro banner upon starting your application + 9) Using a custom prompt +10) How to make custom attributes settable at runtime """ + import cmd2 from cmd2 import ( Bg, diff --git a/examples/migrating.py b/examples/migrating.py index fab40d25f..22efadab9 100755 --- a/examples/migrating.py +++ b/examples/migrating.py @@ -3,6 +3,7 @@ """ A sample application for cmd which can be used to show how to migrate to cmd2. """ + import cmd import random diff --git a/examples/modular_commands/commandset_basic.py b/examples/modular_commands/commandset_basic.py index ab52a326a..a4b7582f5 100644 --- a/examples/modular_commands/commandset_basic.py +++ b/examples/modular_commands/commandset_basic.py @@ -2,6 +2,7 @@ """ A simple example demonstrating a loadable command set """ + from typing import ( List, ) diff --git a/examples/modular_commands/commandset_custominit.py b/examples/modular_commands/commandset_custominit.py index e24ac291c..a3f4f59ad 100644 --- a/examples/modular_commands/commandset_custominit.py +++ b/examples/modular_commands/commandset_custominit.py @@ -2,6 +2,7 @@ """ A simple example demonstrating a loadable command set """ + from cmd2 import ( Cmd, CommandSet, diff --git a/examples/modular_commands_main.py b/examples/modular_commands_main.py index 3aeb8b2a2..74483987b 100644 --- a/examples/modular_commands_main.py +++ b/examples/modular_commands_main.py @@ -4,6 +4,7 @@ A complex example demonstrating a variety of methods to load CommandSets using a mix of command decorators with examples of how to integrate tab completion with argparse-based commands. """ + import argparse from typing import ( Iterable, diff --git a/examples/modular_subcommands.py b/examples/modular_subcommands.py index 082903fb1..14d117814 100644 --- a/examples/modular_subcommands.py +++ b/examples/modular_subcommands.py @@ -10,6 +10,7 @@ The `load` and `unload` command will load and unload the CommandSets. The available top level commands as well as subcommands to the `cut` command will change depending on which CommandSets are loaded. """ + import argparse import cmd2 diff --git a/examples/paged_output.py b/examples/paged_output.py index afed8a6e7..0f7173b2e 100755 --- a/examples/paged_output.py +++ b/examples/paged_output.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # coding=utf-8 -"""A simple example demonstrating the using paged output via the ppaged() method. -""" +"""A simple example demonstrating the using paged output via the ppaged() method.""" + import os from typing import ( List, diff --git a/examples/persistent_history.py b/examples/persistent_history.py index 330e35379..ab4b89f2b 100755 --- a/examples/persistent_history.py +++ b/examples/persistent_history.py @@ -5,6 +5,7 @@ This will allow end users of your cmd2-based application to use the arrow keys and Ctrl+r in a manner which persists across invocations of your cmd2 application. This can make it much easier for them to use your application. """ + import cmd2 diff --git a/examples/python_scripting.py b/examples/python_scripting.py index 397870858..e2403c0c2 100755 --- a/examples/python_scripting.py +++ b/examples/python_scripting.py @@ -20,6 +20,7 @@ This application and the "examples/scripts/conditional.py" script serve as an example for one way in which this can be done. """ + import os import cmd2 diff --git a/examples/read_input.py b/examples/read_input.py index 3a13e9d3a..bfc43380b 100644 --- a/examples/read_input.py +++ b/examples/read_input.py @@ -3,6 +3,7 @@ """ A simple example demonstrating the various ways to call cmd2.Cmd.read_input() for input history and tab completion """ + from typing import ( List, ) diff --git a/examples/remove_settable.py b/examples/remove_settable.py index a7b871266..fad671cbe 100755 --- a/examples/remove_settable.py +++ b/examples/remove_settable.py @@ -3,6 +3,7 @@ """ A sample application for cmd2 demonstrating how to remove one of the built-in runtime settable parameters. """ + import cmd2 diff --git a/examples/scripts/conditional.py b/examples/scripts/conditional.py index eb7106958..dd3adcca0 100644 --- a/examples/scripts/conditional.py +++ b/examples/scripts/conditional.py @@ -10,6 +10,7 @@ Note: The "app" function is defined within the cmd2 embedded Python environment and in there "self" is your cmd2 application instance. Note: self only exists in this environment if self_in_py is True. """ + import os import sys diff --git a/examples/scripts/script.py b/examples/scripts/script.py index 5195b8cc9..339fbf2c8 100644 --- a/examples/scripts/script.py +++ b/examples/scripts/script.py @@ -3,4 +3,5 @@ """ Trivial example of a Python script which can be run inside a cmd2 application. """ + print("This is a python script running ...") diff --git a/examples/table_creation.py b/examples/table_creation.py index 78b3a0f08..852f2d84d 100755 --- a/examples/table_creation.py +++ b/examples/table_creation.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # coding=utf-8 """Examples of using the cmd2 table creation API""" + import functools import sys from typing import ( diff --git a/examples/unicode_commands.py b/examples/unicode_commands.py index 0a7c5ac74..6c76a76e7 100755 --- a/examples/unicode_commands.py +++ b/examples/unicode_commands.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # coding=utf-8 -"""A simple example demonstrating support for unicode command names. -""" +"""A simple example demonstrating support for unicode command names.""" + import math import cmd2 diff --git a/plugins/ext_test/tasks.py b/plugins/ext_test/tasks.py index 6370af0c1..640117863 100644 --- a/plugins/ext_test/tasks.py +++ b/plugins/ext_test/tasks.py @@ -8,6 +8,7 @@ - wheel >= 0.31.0 - setuptools >= 39.1.0 """ + import os import pathlib import shutil diff --git a/plugins/tasks.py b/plugins/tasks.py index a22eb310d..4ef83255d 100644 --- a/plugins/tasks.py +++ b/plugins/tasks.py @@ -8,6 +8,7 @@ - wheel >= 0.31.0 - setuptools >= 39.1.0 """ + import invoke from plugins.ext_test import ( diff --git a/pyproject.toml b/pyproject.toml index de8bb4063..50aab79d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,3 +25,172 @@ exclude = ''' | htmlcov )/ ''' + +[tool.ruff] +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", +] + +# Same as Black. +line-length = 127 +indent-width = 4 + +# Assume Python 3.13 +target-version = "py313" +output-format = "full" + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or +# McCabe complexity (`C901`) by default. +select = [ + # https://beta.ruff.rs/docs/rules + # "A", # flake8-builtins + # "ARG", # flake8-unused-arguments + "ASYNC", # flake8-async + # "B", # flake8-bugbear + # "BLE", # flake8-blind-except + # "C4", # flake8-comprehensions + "C90", # McCabe cyclomatic complexity + "DJ", # flake8-django + # "DTZ", # flake8-datetimez + "E", # pycodestyle + # "EM", # flake8-errmsg + # "EXE", # flake8-executable + "F", # Pyflakes + "FA", # flake8-future-annotations + # "FLY", # flynt + "G", # flake8-logging-format + # "I", # isort + "ICN", # flake8-import-conventions + # "INP", # flake8-no-pep420 + "INT", # flake8-gettext + # "ISC", # flake8-implicit-str-concat + # "N", # pep8-naming + "NPY", # NumPy-specific rules + "PD", # pandas-vet + # "PGH", # pygrep-hooks + # "PIE", # flake8-pie + # "PL", # Pylint + # "PT", # flake8-pytest-style + # "PYI", # flake8-pyi + "RSE", # flake8-raise + # "RUF", # Ruff-specific rules + # "S", # flake8-bandit + # "SIM", # flake8-simplify + # "SLF", # flake8-self + # "T10", # flake8-debugger + # "TD", # flake8-todos + # "TID", # flake8-tidy-imports + # "UP", # pyupgrade + # "W", # pycodestyle + # "YTT", # flake8-2020 + # "ANN", # flake8-annotations # FIX ME? + # "COM", # flake8-commas + # "D", # pydocstyle -- FIX ME? + # "ERA", # eradicate -- DO NOT FIX + # "FBT", # flake8-boolean-trap # FIX ME + # "PTH", # flake8-use-pathlib # FIX ME + # "Q", # flake8-quotes + # "RET", # flake8-return # FIX ME? + # "T20", # flake8-print + # "TCH", # flake8-type-checking + # "TRY", # tryceratops +] +ignore = [ + # `ruff rule S101` for a description of that rule + "B904", # Within an `except` clause, raise exceptions with `raise ... from err` -- FIX ME + "B905", # `zip()` without an explicit `strict=` parameter -- FIX ME + "E501", # Line too long + "EM101", # Exception must not use a string literal, assign to variable first + "EXE001", # Shebang is present but file is not executable -- DO NOT FIX + "G004", # Logging statement uses f-string + "PLC1901", # `{}` can be simplified to `{}` as an empty string is falsey + "PLW060", # Using global for `{name}` but no assignment is done -- DO NOT FIX + "PLW2901", # PLW2901: Redefined loop variable -- FIX ME + "PT011", # `pytest.raises(Exception)` is too broad, set the `match` parameter or use a more specific exception + "PT018", # Assertion should be broken down into multiple parts + "S101", # Use of `assert` detected -- DO NOT FIX + "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes -- FIX ME + "SLF001", # Private member accessed: `_Iterator` -- FIX ME + "UP038", # Use `X | Y` in `{}` call instead of `(X, Y)` -- DO NOT FIX +] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +mccabe.max-complexity = 49 + +per-file-ignores."cmd2/__init__.py" = [ + "E402", # Module level import not at top of file + "F401", # Unused import +] + +per-file-ignores."examples/override_parser.py" = [ + "E402", # Module level import not at top of file +] + +per-file-ignores."examples/scripts/*.py" = [ + "F821", # Undefined name `app` +] + +per-file-ignores."tests/pyscript/*.py" = [ + "F821", # Undefined name `app` +] + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "preserve" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = false + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" diff --git a/setup.cfg b/setup.cfg index 5f26578f8..2a898896f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,41 +7,10 @@ addopts = --cov-report=term --cov-report=html -[flake8] -count = True -ignore = E203,W503,E704 -max-complexity = 26 -max-line-length = 127 -show-source = True -statistics = True -exclude = - .git - __pycache__ - .tox - .nox - .eggs - *.eggs, - .venv, - .idea, - .pytest_cache, - .vscode, - build, - dist, - htmlcov - -[isort] -line_length = 1 -skip = cmd2/__init__.py,.git,__pycache,.tox,.nox,.venv,.eggs,.idea,.vscode,build,dist.htmlcov -profile = black -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -use_parentheses = true - [doc8] -ignore-path=docs/_build,.git,.idea,.pytest_cache,.tox,.nox,.venv,.vscode,build,cmd2,examples,tests,cmd2.egg-info,dist,htmlcov,__pycache__,*.egg,plugins -max-line-length=120 -verbose=0 +ignore-path = docs/_build,.git,.idea,.pytest_cache,.tox,.nox,.venv,.vscode,build,cmd2,examples,tests,cmd2.egg-info,dist,htmlcov,__pycache__,*.egg,plugins +max-line-length = 120 +verbose = 0 [mypy] disallow_incomplete_defs = True diff --git a/setup.py b/setup.py index 749f254ab..dfd1a6bac 100755 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ """ Setuptools setup file, used to install or test 'cmd2' """ + import codecs from setuptools import ( @@ -33,6 +34,7 @@ Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 +Programming Language :: Python :: 3.13 Programming Language :: Python :: Implementation :: CPython Topic :: Software Development :: Libraries :: Python Modules """.splitlines(), @@ -40,11 +42,11 @@ ) ) # noqa: E128 -SETUP_REQUIRES = ['setuptools >= 34.4', 'setuptools_scm >= 3.0'] +SETUP_REQUIRES = ['setuptools', 'setuptools_scm'] INSTALL_REQUIRES = [ - 'pyperclip >= 1.6', - 'wcwidth >= 0.1.7', + 'pyperclip', + 'wcwidth', ] EXTRAS_REQUIRE = { @@ -55,7 +57,7 @@ "gnureadline; sys_platform=='darwin'", # include gnureadline on macOS to ensure it is available in nox env 'codecov', 'coverage', - 'pytest>=4.6', + 'pytest', 'pytest-cov', 'pytest-mock', ], @@ -63,9 +65,6 @@ 'dev': [ 'codecov', 'doc8', - 'flake8', - 'black', - 'isort', 'invoke', 'mypy', 'nox', @@ -75,11 +74,12 @@ 'sphinx', 'sphinx-rtd-theme', 'sphinx-autobuild', - 'twine>=1.11', + 'ruff', + 'twine', ], 'validate': [ - 'flake8', 'mypy', + 'ruff', 'types-setuptools', ], } diff --git a/tasks.py b/tasks.py index e0eb16a0f..8c84ce4fe 100644 --- a/tasks.py +++ b/tasks.py @@ -8,6 +8,7 @@ - wheel >= 0.31.0 - setuptools >= 39.1.0 """ + import os import pathlib import re @@ -346,23 +347,23 @@ def pypi_test(context): namespace.add_task(pypi_test) -# Flake8 - linter and tool for style guide enforcement and linting +# ruff fast linter @invoke.task(post=[plugin_tasks.flake8]) -def flake8(context): - """Run flake8 linter and tool for style guide enforcement""" +def lint(context): + """Run ruff fast linter""" with context.cd(TASK_ROOT_STR): - context.run("flake8") + context.run("ruff check") -namespace.add_task(flake8) +namespace.add_task(lint) -# Black and isort auto-formatting +# ruff fast formatter @invoke.task() def format(context): - """Run black and isort auto-formatting for code style enforcement""" + """Run ruff format --checkt""" with context.cd(TASK_ROOT_STR): - context.run("black . && isort .") + context.run("ruff format --check") namespace.add_task(format) diff --git a/tests/conftest.py b/tests/conftest.py index 35ff2e89d..644ae7cca 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,7 @@ """ Cmd2 unit/functional testing """ + import argparse import sys from contextlib import ( diff --git a/tests/pyscript/raises_exception.py b/tests/pyscript/raises_exception.py index 738edaf28..ab4670890 100644 --- a/tests/pyscript/raises_exception.py +++ b/tests/pyscript/raises_exception.py @@ -3,4 +3,5 @@ """ Example demonstrating what happens when a Python script raises an exception """ + 1 + 'blue' diff --git a/tests/pyscript/recursive.py b/tests/pyscript/recursive.py index b88ba5a52..206f356cb 100644 --- a/tests/pyscript/recursive.py +++ b/tests/pyscript/recursive.py @@ -4,6 +4,7 @@ """ Example demonstrating that calling run_pyscript recursively inside another Python script isn't allowed """ + import os import sys diff --git a/tests/script.py b/tests/script.py index 5195b8cc9..339fbf2c8 100644 --- a/tests/script.py +++ b/tests/script.py @@ -3,4 +3,5 @@ """ Trivial example of a Python script which can be run inside a cmd2 application. """ + print("This is a python script running ...") diff --git a/tests/test_ansi.py b/tests/test_ansi.py index a2c49702a..65ec68a9d 100644 --- a/tests/test_ansi.py +++ b/tests/test_ansi.py @@ -3,6 +3,7 @@ """ Unit testing for cmd2/ansi.py module """ + import pytest from cmd2 import ( diff --git a/tests/test_argparse.py b/tests/test_argparse.py index 19512aff9..7a682c5ce 100644 --- a/tests/test_argparse.py +++ b/tests/test_argparse.py @@ -3,6 +3,7 @@ """ Cmd2 testing for argument parsing """ + import argparse from typing import ( Optional, @@ -404,7 +405,7 @@ def test_add_another_subcommand(subcommand_app): to add_parser() write the correct prog value to the parser being added. """ base_parser = subcommand_app._command_parsers.get('base') - subcommand_parser = find_subcommand(subcommand_app._command_parsers.get('base'), []) + find_subcommand(subcommand_app._command_parsers.get('base'), []) for sub_action in base_parser._actions: if isinstance(sub_action, argparse._SubParsersAction): new_parser = sub_action.add_parser('new_sub', help='stuff') @@ -449,16 +450,16 @@ def test_unittest_mock(): with mock.patch.object(ArgparseApp, 'namespace_provider'): with pytest.raises(CommandSetRegistrationError): - app = ArgparseApp() + ArgparseApp() with mock.patch.object(ArgparseApp, 'namespace_provider', spec=True): - app = ArgparseApp() + ArgparseApp() with mock.patch.object(ArgparseApp, 'namespace_provider', spec_set=True): - app = ArgparseApp() + ArgparseApp() with mock.patch.object(ArgparseApp, 'namespace_provider', autospec=True): - app = ArgparseApp() + ArgparseApp() def test_pytest_mock_invalid(mocker): @@ -468,7 +469,7 @@ def test_pytest_mock_invalid(mocker): mocker.patch.object(ArgparseApp, 'namespace_provider') with pytest.raises(CommandSetRegistrationError): - app = ArgparseApp() + ArgparseApp() @pytest.mark.parametrize( @@ -481,4 +482,4 @@ def test_pytest_mock_invalid(mocker): ) def test_pytest_mock_valid(mocker, spec_param): mocker.patch.object(ArgparseApp, 'namespace_provider', **spec_param) - app = ArgparseApp() + ArgparseApp() diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py index a764a45f5..a2fb89eb1 100644 --- a/tests/test_argparse_completer.py +++ b/tests/test_argparse_completer.py @@ -3,6 +3,7 @@ """ Unit/functional testing for argparse completer in cmd2 """ + import argparse import numbers from typing import ( diff --git a/tests/test_argparse_custom.py b/tests/test_argparse_custom.py index 768cac0bc..a3f85558c 100644 --- a/tests/test_argparse_custom.py +++ b/tests/test_argparse_custom.py @@ -2,6 +2,7 @@ """ Unit/functional testing for argparse customizations in cmd2 """ + import argparse import pytest diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index d76c0bbfc..92a955398 100755 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -3,10 +3,10 @@ """ Cmd2 unit/functional testing """ + import builtins import io import os -import readline import signal import sys import tempfile @@ -149,7 +149,7 @@ def test_base_shortcuts(base_app): def test_command_starts_with_shortcut(): with pytest.raises(ValueError) as excinfo: - app = cmd2.Cmd(shortcuts={'help': 'fake'}) + cmd2.Cmd(shortcuts={'help': 'fake'}) assert "Invalid command name 'help'" in str(excinfo.value) @@ -813,9 +813,9 @@ def test_get_paste_buffer_exception(base_app, mocker, capsys): def test_allow_clipboard_initializer(base_app): - assert base_app.allow_clipboard == True + assert base_app.allow_clipboard is True noclipcmd = cmd2.Cmd(allow_clipboard=False) - assert noclipcmd.allow_clipboard == False + assert noclipcmd.allow_clipboard is False # if clipboard access is not allowed, cmd2 should check that first @@ -855,9 +855,7 @@ def _expected_no_editor_error(): """ EXCEPTION of type '{}' occurred with message: Please use 'set editor' to specify your text editing program of choice. To enable full traceback, run the following command: 'set debug true' -""".format( - expected_exception - ) +""".format(expected_exception) ) return expected_text @@ -1360,9 +1358,7 @@ def test_select_options(select_app, monkeypatch): 1. sweet 2. salty {} with salty sauce, yum! -""".format( - food - ) +""".format(food) ) # Make sure our mock was called with the expected arguments @@ -1388,9 +1384,7 @@ def test_select_invalid_option_too_big(select_app, monkeypatch): 2. salty '3' isn't a valid choice. Pick a number between 1 and 2: {} with sweet sauce, yum! -""".format( - food - ) +""".format(food) ) # Make sure our mock was called exactly twice with the expected arguments @@ -1419,9 +1413,7 @@ def test_select_invalid_option_too_small(select_app, monkeypatch): 2. salty '0' isn't a valid choice. Pick a number between 1 and 2: {} with sweet sauce, yum! -""".format( - food - ) +""".format(food) ) # Make sure our mock was called exactly twice with the expected arguments @@ -1445,9 +1437,7 @@ def test_select_list_of_strings(select_app, monkeypatch): 1. math 2. science Good luck learning {}! -""".format( - 'science' - ) +""".format('science') ) # Make sure our mock was called with the expected arguments @@ -1468,9 +1458,7 @@ def test_select_list_of_tuples(select_app, monkeypatch): 1. Netflix 2. WebSurfing Have fun procrasinating with {}! -""".format( - 'YouTube' - ) +""".format('YouTube') ) # Make sure our mock was called with the expected arguments @@ -1491,9 +1479,7 @@ def test_select_uneven_list_of_tuples(select_app, monkeypatch): 1. Electric Guitar 2. Drums Charm us with the {}... -""".format( - 'Drums' - ) +""".format('Drums') ) # Make sure our mock was called with the expected arguments @@ -1523,9 +1509,7 @@ def test_select_return_type(select_app, monkeypatch, selection, type_str): 2. String 3. Method The return type is {} -""".format( - type_str - ) +""".format(type_str) ) # Make sure our mock was called with the expected arguments diff --git a/tests/test_completion.py b/tests/test_completion.py index 2eebaaebe..67f0fe075 100755 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -6,6 +6,7 @@ These are primarily tests related to readline completer functions which handle tab completion of cmd2/cmd commands, file system paths, and shell commands. """ + import enum import os import sys @@ -274,7 +275,7 @@ def test_cmd2_help_completion_nomatch(cmd2_app): def test_set_allow_style_completion(cmd2_app): """Confirm that completing allow_style presents AllowStyle strings""" text = '' - line = 'set allow_style'.format(text) + line = 'set allow_style' endidx = len(line) begidx = endidx - len(text) @@ -288,7 +289,7 @@ def test_set_allow_style_completion(cmd2_app): def test_set_bool_completion(cmd2_app): """Confirm that completing a boolean Settable presents true and false strings""" text = '' - line = 'set debug'.format(text) + line = 'set debug' endidx = len(line) begidx = endidx - len(text) @@ -1331,7 +1332,7 @@ def test_help_subcommand_completion_nomatch_scu(scu_app): begidx = endidx - len(text) first_match = complete_tester(text, line, begidx, endidx, scu_app) - assert first_match == None + assert first_match is None def test_subcommand_tab_completion_scu(scu_app): diff --git a/tests/test_history.py b/tests/test_history.py index 38b539c68..1a3bd744b 100755 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -3,6 +3,7 @@ """ Test history functions of cmd2 """ + import os import tempfile from unittest import ( @@ -478,7 +479,7 @@ def test_history_item_instantiate(): Statement, ) - statement = Statement( + Statement( 'history', raw='help history', command='help', @@ -898,7 +899,7 @@ def test_history_file_is_directory(capsys): def test_history_can_create_directory(mocker): # Mock out atexit.register so the persistent file doesn't written when this function # exists because we will be deleting the directory it needs to go to. - mock_register = mocker.patch('atexit.register') + mocker.patch('atexit.register') # Create a temp path for us to use and let it get deleted with tempfile.TemporaryDirectory() as test_dir: diff --git a/tests/test_parsing.py b/tests/test_parsing.py index 37a93ba26..83df13cf5 100755 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -3,6 +3,7 @@ """ Test the parsing logic in parsing.py """ + import dataclasses import pytest diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 61b140ab0..eb91892c4 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -3,6 +3,7 @@ """ Test plugin infrastructure and hooks. """ + import argparse import sys from unittest import ( diff --git a/tests/test_run_pyscript.py b/tests/test_run_pyscript.py index 3732e57e1..594a30b75 100644 --- a/tests/test_run_pyscript.py +++ b/tests/test_run_pyscript.py @@ -3,6 +3,7 @@ """ Unit/functional testing for run_pytest in cmd2 """ + import builtins import os from unittest import ( diff --git a/tests/test_table_creator.py b/tests/test_table_creator.py index 585ed62ab..fbbdfbc4a 100644 --- a/tests/test_table_creator.py +++ b/tests/test_table_creator.py @@ -3,6 +3,7 @@ """ Unit testing for cmd2/table_creator.py module """ + import pytest from cmd2 import ( diff --git a/tests/test_transcript.py b/tests/test_transcript.py index ed193a00c..986221ff2 100644 --- a/tests/test_transcript.py +++ b/tests/test_transcript.py @@ -3,6 +3,7 @@ """ Cmd2 functional testing based on transcript """ + import os import random import re diff --git a/tests/test_utils.py b/tests/test_utils.py index e8198d4be..2779a38b8 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -3,6 +3,7 @@ """ Unit testing for cmd2/utils.py module. """ + import os import signal import sys diff --git a/tests/test_utils_defining_class.py b/tests/test_utils_defining_class.py index 5d667678a..8b6ede8bf 100644 --- a/tests/test_utils_defining_class.py +++ b/tests/test_utils_defining_class.py @@ -3,6 +3,7 @@ """ Unit testing for get_defining_class in cmd2/utils.py module. """ + import functools import cmd2.utils as cu @@ -23,7 +24,8 @@ def func_with_overrides(self): def child_function(self): pass - lambda1 = lambda: 1 + def lambda1(): + return 1 lambda2 = (lambda: lambda: 2)() diff --git a/tests_isolated/test_commandset/conftest.py b/tests_isolated/test_commandset/conftest.py index 41d5b6a4d..c8c6d34b4 100644 --- a/tests_isolated/test_commandset/conftest.py +++ b/tests_isolated/test_commandset/conftest.py @@ -2,6 +2,7 @@ """ Cmd2 unit/functional testing """ + import sys from contextlib import ( redirect_stderr, diff --git a/tests_isolated/test_commandset/test_categories.py b/tests_isolated/test_commandset/test_categories.py index 71f1db8e2..986ae3fa9 100644 --- a/tests_isolated/test_commandset/test_categories.py +++ b/tests_isolated/test_commandset/test_categories.py @@ -3,6 +3,7 @@ """ Simple example demonstrating basic CommandSet usage. """ + from typing import ( Any, ) diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py index e400437b1..c7293d411 100644 --- a/tests_isolated/test_commandset/test_commandset.py +++ b/tests_isolated/test_commandset/test_commandset.py @@ -963,7 +963,7 @@ def cut_banana(self, ns: argparse.Namespace): self.poutput('cutting banana: ' + ns.direction) with pytest.raises(CommandSetRegistrationError): - app = BadSubcommandApp() + BadSubcommandApp() def test_commandset_settables():