Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ See the `dependencies` list under the `[project]` heading in [pyproject.toml](..

| Prerequisite | Minimum Version | Purpose |
| --------------------------------------------------- | --------------- | -------------------------------------- |
| [python](https://www.python.org/downloads/) | `3.9` | Python programming language |
| [python](https://www.python.org/downloads/) | `3.10` | Python programming language |
| [pyperclip](https://github.com/asweigart/pyperclip) | `1.8` | Cross-platform clipboard functions |
| [wcwidth](https://pypi.python.org/pypi/wcwidth) | `0.2.10` | Measure the displayed width of unicode |

Expand Down Expand Up @@ -520,13 +520,11 @@ on how to do it.

4. The title (also called the subject) of your PR should be descriptive of your changes and
succinctly indicate what is being fixed

- **Do not add the issue number in the PR title or commit message**

- Examples: `Add test cases for Unicode support`; `Correct typo in overview documentation`

5. In the body of your PR include a more detailed summary of the changes you made and why

- 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.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"]
fail-fast: false

runs-on: ${{ matrix.os }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/typecheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
fail-fast: false
defaults:
run:
Expand Down
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repos:
- id: trailing-whitespace

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.12.4"
rev: "v0.12.7"
hooks:
- id: ruff-format
args: [--config=pyproject.toml]
Expand All @@ -21,5 +21,5 @@ repos:
hooks:
- id: prettier
additional_dependencies:
- prettier@3.5.3
- prettier-plugin-toml@2.0.5
- prettier@3.6.2
- prettier-plugin-toml@2.0.6
8 changes: 1 addition & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
## 3.0.0 (TBD)

- Breaking Changes

- `cmd2` 3.0 supports Python 3.10+ (removed support for Python 3.9)
- No longer setting parser's `prog` value in `with_argparser()` since it gets set in
`Cmd._build_parser()`. This code had previously been restored to support backward
compatibility in `cmd2` 2.0 family.

- Enhancements

- Simplified the process to set a custom parser for `cmd2's` built-in commands. See
[custom_parser.py](https://github.com/python-cmd2/cmd2/blob/main/examples/custom_parser.py)
example for more details.
Expand All @@ -30,7 +29,6 @@
## 2.6.2 (June 26, 2025)

- Enhancements

- Added explicit support for free-threaded versions of Python, starting with version 3.14

- Bug Fixes
Expand Down Expand Up @@ -1316,12 +1314,10 @@
## 0.8.5 (April 15, 2018)

- Bug Fixes

- Fixed a bug with all argument decorators where the wrapped function wasn't returning a value
and thus couldn't cause the cmd2 app to quit

- Enhancements

- Added support for verbose help with -v where it lists a brief summary of what each command
does
- Added support for categorizing commands into groups within the help menu
Expand Down Expand Up @@ -1353,12 +1349,10 @@
## 0.8.3 (April 09, 2018)

- Bug Fixes

- Fixed `help` command not calling functions for help topics
- Fixed not being able to use quoted paths when redirecting with `<` and `>`

- Enhancements

- Tab completion has been overhauled and now supports completion of strings with quotes and
spaces.
- Tab completion will automatically add an opening quote if a string with a space is completed.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ On all operating systems, the latest stable version of `cmd2` can be installed u
pip install -U cmd2
```

cmd2 works with Python 3.9+ on Windows, macOS, and Linux. It is pure Python code with few 3rd-party
cmd2 works with Python 3.10+ on Windows, macOS, and Linux. It is pure Python code with few 3rd-party
dependencies. It works with both conventional CPython and free-threaded variants.

For information on other installation options, see
Expand Down
17 changes: 8 additions & 9 deletions cmd2/ansi.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from typing import (
IO,
Any,
Optional,
cast,
)

Expand Down Expand Up @@ -924,14 +923,14 @@ def __str__(self) -> str:
def style(
value: Any,
*,
fg: Optional[FgColor] = None,
bg: Optional[BgColor] = None,
bold: Optional[bool] = None,
dim: Optional[bool] = None,
italic: Optional[bool] = None,
overline: Optional[bool] = None,
strikethrough: Optional[bool] = None,
underline: Optional[bool] = None,
fg: FgColor | None = None,
bg: BgColor | None = None,
bold: bool | None = None,
dim: bool | None = None,
italic: bool | None = None,
overline: bool | None = None,
strikethrough: bool | None = None,
underline: bool | None = None,
) -> str:
"""Apply ANSI colors and/or styles to a string and return it.

Expand Down
26 changes: 12 additions & 14 deletions cmd2/argparse_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
from typing import (
IO,
TYPE_CHECKING,
Optional,
Union,
cast,
)

Expand Down Expand Up @@ -105,8 +103,8 @@ class _ArgumentState:

def __init__(self, arg_action: argparse.Action) -> None:
self.action = arg_action
self.min: Union[int, str]
self.max: Union[float, int, str]
self.min: int | str
self.max: float | int | str
self.count = 0
self.is_remainder = self.action.nargs == argparse.REMAINDER

Expand Down Expand Up @@ -141,7 +139,7 @@ def __init__(self, flag_arg_state: _ArgumentState) -> None:
:param flag_arg_state: information about the unfinished flag action.
"""
arg = f'{argparse._get_action_name(flag_arg_state.action)}'
err = f'{generate_range_error(cast(int, flag_arg_state.min), cast(Union[int, float], flag_arg_state.max))}'
err = f'{generate_range_error(cast(int, flag_arg_state.min), cast(int | float, flag_arg_state.max))}'
error = f"Error: argument {arg}: {err} ({flag_arg_state.count} entered)"
super().__init__(error)

Expand All @@ -163,7 +161,7 @@ class ArgparseCompleter:
"""Automatic command line tab completion based on argparse parameters."""

def __init__(
self, parser: argparse.ArgumentParser, cmd2_app: 'Cmd', *, parent_tokens: Optional[dict[str, list[str]]] = None
self, parser: argparse.ArgumentParser, cmd2_app: 'Cmd', *, parent_tokens: dict[str, list[str]] | None = None
) -> None:
"""Create an ArgparseCompleter.

Expand Down Expand Up @@ -203,7 +201,7 @@ def __init__(
self._subcommand_action = action

def complete(
self, text: str, line: str, begidx: int, endidx: int, tokens: list[str], *, cmd_set: Optional[CommandSet] = None
self, text: str, line: str, begidx: int, endidx: int, tokens: list[str], *, cmd_set: CommandSet | None = None
) -> list[str]:
"""Complete text using argparse metadata.

Expand All @@ -228,10 +226,10 @@ def complete(
skip_remaining_flags = False

# _ArgumentState of the current positional
pos_arg_state: Optional[_ArgumentState] = None
pos_arg_state: _ArgumentState | None = None

# _ArgumentState of the current flag
flag_arg_state: Optional[_ArgumentState] = None
flag_arg_state: _ArgumentState | None = None

# Non-reusable flags that we've parsed
matched_flags: list[str] = []
Expand Down Expand Up @@ -523,7 +521,7 @@ def _complete_flags(self, text: str, line: str, begidx: int, endidx: int, matche

return matches

def _format_completions(self, arg_state: _ArgumentState, completions: Union[list[str], list[CompletionItem]]) -> list[str]:
def _format_completions(self, arg_state: _ArgumentState, completions: list[str] | list[CompletionItem]) -> list[str]:
"""Format CompletionItems into hint table."""
# Nothing to do if we don't have at least 2 completions which are all CompletionItems
if len(completions) < 2 or not all(isinstance(c, CompletionItem) for c in completions):
Expand Down Expand Up @@ -625,7 +623,7 @@ def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: in
break
return []

def print_help(self, tokens: list[str], file: Optional[IO[str]] = None) -> None:
def print_help(self, tokens: list[str], file: IO[str] | None = None) -> None:
"""Supports cmd2's help command in the printing of help text.

:param tokens: arguments passed to help command
Expand All @@ -636,7 +634,7 @@ def print_help(self, tokens: list[str], file: Optional[IO[str]] = None) -> None:
# If so, we will let the subcommand's parser handle the rest of the tokens via another ArgparseCompleter.
if tokens and self._subcommand_action is not None:
parser = cast(
Optional[argparse.ArgumentParser],
argparse.ArgumentParser | None,
self._subcommand_action.choices.get(tokens[0]),
)

Expand All @@ -657,15 +655,15 @@ def _complete_arg(
arg_state: _ArgumentState,
consumed_arg_values: dict[str, list[str]],
*,
cmd_set: Optional[CommandSet] = None,
cmd_set: CommandSet | None = None,
) -> list[str]:
"""Tab completion routine for an argparse argument.

:return: list of completions
:raises CompletionError: if the completer or choices function this calls raises one.
"""
# Check if the arg provides choices to the user
arg_choices: Union[list[str], ChoicesCallable]
arg_choices: list[str] | ChoicesCallable
if arg_state.action.choices is not None:
arg_choices = list(arg_state.action.choices)
if not arg_choices:
Expand Down
Loading
Loading