├── .coveragerc ├── .github ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── dependabot.yml ├── images │ └── graph.drawio.png └── workflows │ ├── codeql-analysis.yml │ ├── docs.yml │ ├── quality.yml │ ├── tests.yml │ └── typecheck.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .prettierignore ├── .prettierrc ├── .readthedocs.yaml ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── Pipfile ├── README.md ├── cmd2.png ├── cmd2 ├── __init__.py ├── ansi.py ├── argparse_completer.py ├── argparse_custom.py ├── clipboard.py ├── cmd2.py ├── command_definition.py ├── constants.py ├── decorators.py ├── exceptions.py ├── history.py ├── parsing.py ├── plugin.py ├── py.typed ├── py_bridge.py ├── rl_utils.py ├── table_creator.py ├── transcript.py └── utils.py ├── codecov.yml ├── docs ├── api │ ├── ansi.md │ ├── argparse_completer.md │ ├── argparse_custom.md │ ├── cmd.md │ ├── command_definition.md │ ├── constants.md │ ├── decorators.md │ ├── exceptions.md │ ├── history.md │ ├── index.md │ ├── parsing.md │ ├── plugin.md │ ├── py_bridge.md │ ├── table_creator.md │ └── utils.md ├── doc_conventions.md ├── examples │ ├── alternate_event_loops.md │ ├── examples.md │ ├── first_app.md │ └── index.md ├── features │ ├── argument_processing.md │ ├── builtin_commands.md │ ├── clipboard.md │ ├── commands.md │ ├── completion.md │ ├── disable_commands.md │ ├── embedded_python_shells.md │ ├── generating_output.md │ ├── help.md │ ├── history.md │ ├── hooks.md │ ├── index.md │ ├── initialization.md │ ├── misc.md │ ├── modular_commands.md │ ├── multiline_commands.md │ ├── os.md │ ├── packaging.md │ ├── plugins.md │ ├── prompt.md │ ├── redirection.md │ ├── scripting.md │ ├── settings.md │ ├── shortcuts_aliases_macros.md │ ├── startup_commands.md │ ├── table_creation.md │ └── transcripts.md ├── index.md ├── javascripts │ └── readthedocs.js ├── migrating │ ├── incompatibilities.md │ ├── index.md │ ├── minimum.md │ ├── next_steps.md │ └── why.md ├── overrides │ └── main.html ├── overview │ ├── alternatives.md │ ├── index.md │ ├── installation.md │ ├── integrating.md │ └── resources.md ├── plugins │ ├── external_test.md │ └── index.md ├── requirements.txt ├── stylesheets │ └── readthedocs.css └── testing.md ├── examples ├── .cmd2rc ├── README.md ├── alias_startup.py ├── arg_decorators.py ├── arg_print.py ├── argparse_completion.py ├── async_printing.py ├── basic.py ├── basic_completion.py ├── cmd_as_argument.py ├── colors.py ├── custom_parser.py ├── decorator_example.py ├── default_categories.py ├── dynamic_commands.py ├── environment.py ├── event_loops.py ├── example.py ├── exit_code.py ├── first_app.py ├── hello_cmd2.py ├── help_categories.py ├── hooks.py ├── initialization.py ├── migrating.py ├── modular_commands │ ├── __init__.py │ ├── commandset_basic.py │ ├── commandset_complex.py │ └── commandset_custominit.py ├── modular_commands_basic.py ├── modular_commands_dynamic.py ├── modular_commands_main.py ├── modular_subcommands.py ├── override_parser.py ├── paged_output.py ├── persistent_history.py ├── pirate.py ├── pretty_print.py ├── python_scripting.py ├── read_input.py ├── remove_builtin_commands.py ├── remove_settable.py ├── scripts │ ├── arg_printer.py │ ├── conditional.py │ ├── nested.txt │ ├── quit.txt │ ├── save_help_text.py │ ├── script.py │ └── script.txt ├── subcommands.py ├── table_creation.py ├── tmux_launch.sh ├── tmux_split.sh ├── transcripts │ ├── exampleSession.txt │ ├── pirate.transcript │ ├── quit.txt │ └── transcript_regex.txt └── unicode_commands.py ├── mkdocs.yml ├── package.json ├── plugins ├── README.txt ├── ext_test │ ├── CHANGELOG.md │ ├── README.md │ ├── build-pyenvs.sh │ ├── cmd2_ext_test │ │ ├── __init__.py │ │ ├── cmd2_ext_test.py │ │ ├── py.typed │ │ └── pylintrc │ ├── examples │ │ └── example.py │ ├── noxfile.py │ ├── pyproject.toml │ ├── setup.py │ ├── tasks.py │ └── tests │ │ ├── __init__.py │ │ ├── pylintrc │ │ └── test_ext_test.py ├── tasks.py └── template │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── build-pyenvs.sh │ ├── cmd2_myplugin │ ├── __init__.py │ ├── myplugin.py │ └── pylintrc │ ├── examples │ └── example.py │ ├── noxfile.py │ ├── setup.py │ ├── tasks.py │ └── tests │ ├── __init__.py │ ├── pylintrc │ └── test_myplugin.py ├── pyproject.toml ├── readme_files ├── shout_out.csv └── shoutout.txt ├── tasks.py ├── tests ├── .cmd2rc ├── __init__.py ├── conftest.py ├── pyscript │ ├── echo.py │ ├── environment.py │ ├── help.py │ ├── py_locals.py │ ├── pyscript_dir.py │ ├── raises_exception.py │ ├── recursive.py │ ├── self_in_py.py │ ├── stdout_capture.py │ └── stop.py ├── relative_multiple.txt ├── script.py ├── script.txt ├── scripts │ ├── binary.bin │ ├── empty.txt │ ├── help.txt │ ├── nested.txt │ ├── one_down.txt │ ├── postcmds.txt │ ├── precmds.txt │ └── utf8.txt ├── test_ansi.py ├── test_argparse.py ├── test_argparse_completer.py ├── test_argparse_custom.py ├── test_cmd2.py ├── test_completion.py ├── test_history.py ├── test_parsing.py ├── test_plugin.py ├── test_run_pyscript.py ├── test_table_creator.py ├── test_transcript.py ├── test_utils.py ├── test_utils_defining_class.py └── transcripts │ ├── bol_eol.txt │ ├── characterclass.txt │ ├── dotstar.txt │ ├── extension_notation.txt │ ├── failure.txt │ ├── from_cmdloop.txt │ ├── multiline_no_regex.txt │ ├── multiline_regex.txt │ ├── no_output.txt │ ├── no_output_last.txt │ ├── regex_set.txt │ ├── singleslash.txt │ ├── slashes_escaped.txt │ ├── slashslash.txt │ ├── spaces.txt │ └── word_boundaries.txt └── tests_isolated ├── __init__.py └── test_commandset ├── __init__.py ├── conftest.py ├── test_argparse_subcommands.py ├── test_categories.py └── test_commandset.py /.coveragerc: -------------------------------------------------------------------------------- 1 | # .coveragerc to control coverage.py 2 | [run] 3 | # Source 4 | source = plugins/*/cmd2_*/ 5 | cmd2/ 6 | # (boolean, default False): whether to measure branch coverage in addition to statement coverage. 7 | branch = False 8 | 9 | 10 | [report] 11 | # A list of regular expressions. Any line that matches one of these regexes is excluded from being reported as missing 12 | exclude_lines = 13 | # Have to re-enable the standard pragma 14 | pragma: no cover 15 | 16 | # Don't complain if non-runnable code isn't run: 17 | if __name__ == .__main__.: 18 | 19 | # (integer): the number of digits after the decimal point to display for reported coverage percentages. 20 | precision = 1 21 | 22 | 23 | [html] 24 | # (string, default "htmlcov"): where to write the HTML report files. 25 | directory = htmlcov 26 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------- 2 | # CODEOWNERS - For automated review request for 3 | # high impact files. 4 | # 5 | # Important: The order in this file cascades. 6 | # 7 | # https://help.github.com/articles/about-codeowners 8 | # ------------------------------------------------- 9 | 10 | # Lines starting with '#' are comments. 11 | # Each line is a file pattern followed by one or more owners. 12 | # Owners of code are automatically nominated to review PRs involving that code. 13 | 14 | # These owners will be the default owners for everything in the repo. 15 | * @tleonhardt 16 | 17 | # Order is important. The last matching pattern has the most precedence. 18 | # So if a pull request only touches javascript files, only these owners 19 | # will be requested to review. 20 | #*.js @octocat @github/js 21 | 22 | # You can also use email addresses if you prefer. 23 | #docs/* docs@example.com 24 | 25 | # cmd2 code 26 | cmd2/__init__.py @tleonhardt @kotfu 27 | cmd2/ansi.py @kmvanbrunt @tleonhardt 28 | cmd2/argparse_*.py @kmvanbrunt @anselor 29 | cmd2/clipboard.py @tleonhardt 30 | cmd2/cmd2.py @tleonhardt @kmvanbrunt @kotfu 31 | cmd2/command_definition.py @anselor 32 | cmd2/constants.py @kotfu 33 | cmd2/decorators.py @kotfu @kmvanbrunt @anselor 34 | cmd2/exceptions.py @kmvanbrunt @anselor 35 | cmd2/history.py @kotfu @tleonhardt 36 | cmd2/parsing.py @kotfu @kmvanbrunt 37 | cmd2/plugin.py @kotfu 38 | cmd2/py_bridge.py @kmvanbrunt 39 | cmd2/rl_utils.py @kmvanbrunt 40 | cmd2/table_creator.py @kmvanbrunt 41 | cmd2/transcript.py @kotfu 42 | cmd2/utils.py @tleonhardt @kotfu @kmvanbrunt 43 | 44 | # Documentation 45 | docs/* @tleonhardt @kotfu 46 | 47 | # Examples 48 | examples/async_printing.py @kmvanbrunt 49 | examples/environment.py @kotfu 50 | examples/tab_*.py @kmvanbrunt 51 | examples/modular_*.py @anselor 52 | examples/modular_commands/* @anselor 53 | 54 | plugins/template/* @kotfu 55 | plugins/ext_test/* @anselor 56 | 57 | # Unit Tests 58 | tests/pyscript/* @kmvanbrunt 59 | tests/transcripts/* @kotfu 60 | tests/__init__.py @kotfu 61 | tests/conftest.py @kotfu @tleonhardt 62 | tests/test_argparse.py @kotfu 63 | tests/test_argparse_*.py @kmvanbrunt 64 | tests/test_comp*.py @kmvanbrunt 65 | tests/test_pars*.py @kotfu 66 | tests/test_run_pyscript.py @kmvanbrunt 67 | tests/test_transcript.py @kotfu 68 | 69 | tests_isolated/test_commandset/* @anselor 70 | 71 | # Top-level project stuff 72 | setup.py @tleonhardt @kotfu 73 | tasks.py @kotfu 74 | 75 | # GitHub stuff 76 | .github/* @tleonhardt 77 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # cmd2 Contributor Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at todd.leonhardt at gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | 8 | - package-ecosystem: "pip" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/images/graph.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cmd2/cmd2/28f226acbea4c1043449b0de72ca2c8bdebf3a56/.github/images/graph.drawio.png -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [master] 9 | schedule: 10 | - cron: "0 6 * * 4" 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | analyze: 17 | permissions: 18 | actions: read # for github/codeql-action/init to get workflow details 19 | contents: read # for actions/checkout to fetch code 20 | security-events: write # for github/codeql-action/autobuild to send a status report 21 | name: Analyze 22 | runs-on: ubuntu-latest 23 | 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | # Override automatic language detection by changing the below list 28 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 29 | language: ["python"] 30 | # Learn more... 31 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 32 | 33 | steps: 34 | - name: Checkout repository 35 | uses: actions/checkout@v4 36 | with: 37 | # We must fetch at least the immediate parents so that if this is 38 | # a pull request then we can checkout the head. 39 | fetch-depth: 2 40 | 41 | # If this run was triggered by a pull request event, then checkout 42 | # the head of the pull request instead of the merge commit. 43 | - run: git checkout HEAD^2 44 | if: ${{ github.event_name == 'pull_request' }} 45 | 46 | # Initializes the CodeQL tools for scanning. 47 | - name: Initialize CodeQL 48 | uses: github/codeql-action/init@v3 49 | with: 50 | languages: ${{ matrix.language }} 51 | # If you wish to specify custom queries, you can do so here or in a config file. 52 | # By default, queries listed here will override any specified in a config file. 53 | # Prefix the list here with "+" to use these queries and those in the config file. 54 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v3 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 https://git.io/JvXDl 63 | 64 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 65 | # and modify them (or add more) to build your code if your project 66 | # uses a compiled language 67 | 68 | #- run: | 69 | # make bootstrap 70 | # make release 71 | 72 | - name: Perform CodeQL Analysis 73 | uses: github/codeql-action/analyze@v3 74 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | # For documentation on GitHub Actions Workflows, see: 2 | # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions 3 | name: Docs 4 | on: 5 | pull_request: 6 | types: [opened, synchronize, reopened] 7 | push: 8 | branches: [master] 9 | 10 | workflow_dispatch: 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | check-docs: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Check out 20 | uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 # Needed for setuptools_scm to work correctly 23 | 24 | - name: Install uv and set the python version 25 | uses: astral-sh/setup-uv@v6 26 | with: 27 | python-version: "3.13" 28 | - name: Install the project 29 | run: uv sync --group docs 30 | - name: Check if the MkDocs documentation can be built 31 | run: uv run mkdocs build -s 32 | -------------------------------------------------------------------------------- /.github/workflows/quality.yml: -------------------------------------------------------------------------------- 1 | # For documentation on GitHub Actions Workflows, see: 2 | # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions 3 | name: Quality 4 | on: 5 | pull_request: 6 | types: [opened, synchronize, reopened] 7 | push: 8 | branches: [master] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | quality: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Check out 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 # Needed for setuptools_scm to work correctly 21 | 22 | - uses: actions/cache@v4 23 | with: 24 | path: ~/.cache/pre-commit 25 | key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} 26 | - name: Install uv and set the python version 27 | uses: astral-sh/setup-uv@v6 28 | with: 29 | python-version: "3.13" 30 | - name: Install the project 31 | run: uv sync --group quality 32 | - name: Run pre-commit 33 | run: uv run pre-commit run -a --show-diff-on-failure 34 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | # For documentation on GitHub Actions Workflows, see: 2 | # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions 3 | name: Tests 4 | on: 5 | pull_request: 6 | types: [opened, synchronize, reopened] 7 | push: 8 | branches: [master] 9 | 10 | jobs: 11 | tests: 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, macos-latest, windows-latest] 15 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14-dev"] 16 | fail-fast: false 17 | 18 | runs-on: ${{ matrix.os }} 19 | defaults: 20 | run: 21 | shell: bash 22 | steps: 23 | - name: Check out 24 | uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 0 # Needed for setuptools_scm to work correctly 27 | - name: Install uv 28 | uses: astral-sh/setup-uv@v6 29 | 30 | - name: Set up Python ${{ matrix.python-version }} 31 | uses: actions/setup-python@v5 32 | with: 33 | python-version: ${{ matrix.python-version }} 34 | allow-prereleases: true 35 | - name: Install the project 36 | run: uv sync --all-extras --dev 37 | 38 | - name: Run tests 39 | run: uv run python -m pytest --cov --cov-config=pyproject.toml --cov-report=xml tests 40 | 41 | - name: Run isolated tests 42 | run: uv run python -m pytest --cov --cov-config=pyproject.toml --cov-report=xml tests_isolated 43 | 44 | - name: Upload test results to Codecov 45 | if: ${{ !cancelled() }} 46 | uses: codecov/test-results-action@v1 47 | with: 48 | flags: python${{ matrix.python-version }} 49 | name: codecov-umbrella-test-results 50 | token: ${{ secrets.CODECOV_TOKEN }} 51 | - name: Upload coverage to Codecov 52 | uses: codecov/codecov-action@v5 53 | with: 54 | env_vars: OS,PYTHON 55 | fail_ci_if_error: true 56 | flags: unittests 57 | name: codecov-umbrella 58 | token: ${{ secrets.CODECOV_TOKEN }} 59 | verbose: true 60 | -------------------------------------------------------------------------------- /.github/workflows/typecheck.yml: -------------------------------------------------------------------------------- 1 | # For documentation on GitHub Actions Workflows, see: 2 | # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions 3 | name: TypeCheck 4 | on: 5 | pull_request: 6 | types: [opened, synchronize, reopened] 7 | push: 8 | branches: [master] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | type-check: 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 19 | fail-fast: false 20 | defaults: 21 | run: 22 | shell: bash 23 | steps: 24 | - name: Check out 25 | uses: actions/checkout@v4 26 | with: 27 | fetch-depth: 0 # Needed for setuptools_scm to work correctly 28 | 29 | - name: Install uv and set the python version 30 | uses: astral-sh/setup-uv@v6 31 | with: 32 | python-version: ${{ matrix.python-version }} 33 | 34 | - name: Check typing 35 | run: uv run mypy . 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python development, test, and build 2 | __pycache__ 3 | target 4 | build 5 | dist 6 | *.egg-info 7 | .eggs 8 | .cache 9 | *.pyc 10 | .tox 11 | .nox 12 | .pytest_cache 13 | 14 | # Code Coverage 15 | .coverage 16 | htmlcov 17 | junit 18 | coverage.xml 19 | 20 | # PyCharm 21 | .idea 22 | 23 | # Visual Studio Code 24 | .vscode 25 | 26 | # mypy optional static type checker 27 | .mypy_cache 28 | 29 | # mypy plugin for PyCharm 30 | dmypy.json 31 | dmypy.sock 32 | 33 | # ruff formatter/linter 34 | .ruff_cache 35 | 36 | # cmd2 history file used in hello_cmd2.py 37 | cmd2_history.dat 38 | 39 | # Pipenv Lock file 40 | Pipfile.lock 41 | 42 | # Virtualenv directory 43 | .venv 44 | 45 | # Commitizen configuration 46 | .cz.toml 47 | 48 | # pyenv version file 49 | .python-version 50 | 51 | # uv 52 | uv.lock 53 | 54 | # Node/npm used for installing Prettier locally to override the outdated version that is bundled with the VSCode extension 55 | node_modules/ 56 | package-lock.json 57 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: "v5.0.0" 4 | hooks: 5 | - id: check-case-conflict 6 | - id: check-merge-conflict 7 | - id: check-toml 8 | - id: end-of-file-fixer 9 | - id: trailing-whitespace 10 | 11 | - repo: https://github.com/astral-sh/ruff-pre-commit 12 | rev: "v0.11.10" 13 | hooks: 14 | - id: ruff-format 15 | args: [--config=pyproject.toml] 16 | - id: ruff-check 17 | args: [--config=pyproject.toml, --fix, --exit-non-zero-on-fix] 18 | 19 | - repo: https://github.com/pre-commit/mirrors-prettier 20 | rev: "v3.1.0" 21 | hooks: 22 | - id: prettier 23 | additional_dependencies: 24 | - prettier@3.5.3 25 | - prettier-plugin-toml@2.0.5 26 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Markdown documentation files with non-standards syntax for mkdocstrings that Prettier should not auto-format 2 | docs/features/initialization.md 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "*.md", 5 | "options": { 6 | "tabWidth": 4 7 | } 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for MkDocs projects 2 | # See https://docs.readthedocs.io/en/stable/intro/mkdocs.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Optionally build your docs in additional formats such as PDF and ePub 8 | # formats: 9 | # - pdf 10 | # - epub 11 | formats: all 12 | 13 | # Build documentation in the "docs/" directory with MkDocs 14 | mkdocs: 15 | configuration: mkdocs.yml 16 | 17 | # Set the OS, Python version and other tools you might need 18 | build: 19 | os: ubuntu-24.04 20 | tools: 21 | python: "3.13" 22 | jobs: 23 | install: 24 | - pip install . 25 | - pip install dependency-groups 26 | - pip-install-dependency-groups docs 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2008-2024 Catherine Devlin and others 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE README.md CHANGELOG.md noxfile.py tasks.py Pipfile 2 | recursive-include examples * 3 | recursive-include tests * 4 | recursive-include docs * 5 | prune .github 6 | prune docs/_build 7 | prune docs/.nox 8 | exclude .github .gitignore azure-pipelines.yml 9 | global-exclude htmlcov/** .coverage* __pycache__/** .gitignore 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Simple Makefile for use with a uv-based development environment 2 | .PHONY: install 3 | install: ## Install the virtual environment with dependencies 4 | @echo "🚀 Creating uv Python virtual environment" 5 | @uv python install 3.13 6 | @uv sync --python=3.13 7 | @echo "🚀 Installing Git pre-commit hooks locally" 8 | @uv run pre-commit install 9 | @echo "🚀 Installing Prettier using npm" 10 | @npm install 11 | 12 | .PHONY: check 13 | check: ## Run code quality tools. 14 | @echo "🚀 Checking lock file consistency with 'pyproject.toml'" 15 | @uv lock --locked 16 | @echo "🚀 Linting code and documentation: Running pre-commit" 17 | @uv run pre-commit run -a 18 | @echo "🚀 Static type checking: Running mypy" 19 | @uv run mypy 20 | 21 | .PHONY: format 22 | format: ## Perform ruff formatting 23 | @uv run ruff format 24 | 25 | .PHONY: lint 26 | lint: ## Perform ruff linting 27 | @uv run ruff check --fix 28 | 29 | .PHONY: typecheck 30 | typecheck: ## Perform type checking 31 | @uv run mypy 32 | 33 | .PHONY: test 34 | test: ## Test the code with pytest. 35 | @echo "🚀 Testing code: Running pytest" 36 | @uv run python -m pytest --cov --cov-config=pyproject.toml --cov-report=xml tests 37 | @uv run python -m pytest --cov --cov-config=pyproject.toml --cov-report=xml tests_isolated 38 | 39 | .PHONY: docs-test 40 | docs-test: ## Test if documentation can be built without warnings or errors 41 | @uv run mkdocs build -s 42 | 43 | .PHONY: docs 44 | docs: ## Build and serve the documentation 45 | @uv run mkdocs serve 46 | 47 | .PHONY: build 48 | build: clean-build ## Build wheel file 49 | @echo "🚀 Creating wheel file" 50 | @uvx --from build pyproject-build --installer uv 51 | 52 | .PHONY: clean-build 53 | clean-build: ## Clean build artifacts 54 | @echo "🚀 Removing build artifacts" 55 | @uv run python -c "import shutil; import os; shutil.rmtree('dist') if os.path.exists('dist') else None" 56 | 57 | .PHONY: tag 58 | tag: ## Add a Git tag and push it to origin with syntax: make tag TAG=tag_name 59 | @echo "🚀 Creating git tag: ${TAG}" 60 | @git tag -a ${TAG} -m "" 61 | @echo "🚀 Pushing tag to origin: ${TAG}" 62 | @git push origin ${TAG} 63 | 64 | .PHONY: validate-tag 65 | validate-tag: ## Check to make sure that a tag exists for the current HEAD and it looks like a valid version number 66 | @echo "🚀 Validating version tag" 67 | @uv run inv validatetag 68 | 69 | .PHONY: publish-test 70 | publish-test: validate-tag build ## Test publishing a release to PyPI. 71 | @echo "🚀 Publishing: Dry run." 72 | @uvx twine upload --repository testpypi dist/* 73 | 74 | .PHONY: publish 75 | publish: validate-tag build ## Publish a release to PyPI. 76 | @echo "🚀 Publishing." 77 | @uvx twine upload dist/* 78 | 79 | .PHONY: help 80 | help: 81 | @uv run python -c "import re; \ 82 | [[print(f'\033[36m{m[0]:<20}\033[0m {m[1]}') for m in re.findall(r'^([a-zA-Z_-]+):.*?## (.*)$$', open(makefile).read(), re.M)] for makefile in ('$(MAKEFILE_LIST)').strip().split()]" 83 | 84 | .DEFAULT_GOAL := help 85 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [packages] 7 | pyperclip = "*" 8 | setuptools = "*" 9 | wcwidth = "*" 10 | 11 | [dev-packages] 12 | build = "*" 13 | cmd2 = { editable = true, path = "." } 14 | cmd2_ext_test = { editable = true, path = "plugins/ext_test" } 15 | codecov = "*" 16 | gnureadline = { version = "*", sys_platform = "== 'darwin'" } 17 | invoke = "*" 18 | ipython = "*" 19 | mypy = "*" 20 | pyreadline3 = { version = ">=3.4", sys_platform = "== 'win32'" } 21 | pytest = "*" 22 | pytest-cov = "*" 23 | pytest-mock = "*" 24 | ruff = "*" 25 | setuptools-scm = "*" 26 | mkdocs-include-markdown-plugin = "*" 27 | mkdocs-macros-plugin = "*" 28 | mkdocs-material = "*" 29 | mkdocstrings= {version = "*", extras = ["python"]} 30 | twine = ">=1.11" 31 | 32 | [pipenv] 33 | allow_prereleases = true 34 | -------------------------------------------------------------------------------- /cmd2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cmd2/cmd2/28f226acbea4c1043449b0de72ca2c8bdebf3a56/cmd2.png -------------------------------------------------------------------------------- /cmd2/__init__.py: -------------------------------------------------------------------------------- 1 | """Import certain things for backwards compatibility.""" 2 | 3 | import argparse 4 | import contextlib 5 | import importlib.metadata as importlib_metadata 6 | import sys 7 | 8 | with contextlib.suppress(importlib_metadata.PackageNotFoundError): 9 | __version__ = importlib_metadata.version(__name__) 10 | 11 | from .ansi import ( 12 | Bg, 13 | Cursor, 14 | EightBitBg, 15 | EightBitFg, 16 | Fg, 17 | RgbBg, 18 | RgbFg, 19 | TextStyle, 20 | style, 21 | ) 22 | from .argparse_custom import ( 23 | Cmd2ArgumentParser, 24 | Cmd2AttributeWrapper, 25 | CompletionItem, 26 | register_argparse_argument_parameter, 27 | set_default_argument_parser_type, 28 | ) 29 | 30 | # Check if user has defined a module that sets a custom value for argparse_custom.DEFAULT_ARGUMENT_PARSER. 31 | # Do this before loading cmd2.Cmd class so its commands use the custom parser. 32 | cmd2_parser_module = getattr(argparse, 'cmd2_parser_module', None) 33 | if cmd2_parser_module is not None: 34 | import importlib 35 | 36 | importlib.import_module(cmd2_parser_module) 37 | 38 | from . import plugin 39 | from .argparse_completer import set_default_ap_completer_type 40 | from .cmd2 import Cmd 41 | from .command_definition import CommandSet, with_default_category 42 | from .constants import COMMAND_NAME, DEFAULT_SHORTCUTS 43 | from .decorators import as_subcommand_to, with_argparser, with_argument_list, with_category 44 | from .exceptions import ( 45 | Cmd2ArgparseError, 46 | CommandSetRegistrationError, 47 | CompletionError, 48 | PassThroughException, 49 | SkipPostcommandHooks, 50 | ) 51 | from .parsing import Statement 52 | from .py_bridge import CommandResult 53 | from .utils import CompletionMode, CustomCompletionSettings, Settable, categorize 54 | 55 | __all__: list[str] = [ # noqa: RUF022 56 | 'COMMAND_NAME', 57 | 'DEFAULT_SHORTCUTS', 58 | # ANSI Exports 59 | 'Cursor', 60 | 'Bg', 61 | 'Fg', 62 | 'EightBitBg', 63 | 'EightBitFg', 64 | 'RgbBg', 65 | 'RgbFg', 66 | 'TextStyle', 67 | 'style', 68 | # Argparse Exports 69 | 'Cmd2ArgumentParser', 70 | 'Cmd2AttributeWrapper', 71 | 'CompletionItem', 72 | 'register_argparse_argument_parameter', 73 | 'set_default_argument_parser_type', 74 | 'set_default_ap_completer_type', 75 | # Cmd2 76 | 'Cmd', 77 | 'CommandResult', 78 | 'CommandSet', 79 | 'Statement', 80 | # Decorators 81 | 'with_argument_list', 82 | 'with_argparser', 83 | 'with_category', 84 | 'with_default_category', 85 | 'as_subcommand_to', 86 | # Exceptions 87 | 'Cmd2ArgparseError', 88 | 'CommandSetRegistrationError', 89 | 'CompletionError', 90 | 'SkipPostcommandHooks', 91 | # modules 92 | 'plugin', 93 | # Utilities 94 | 'categorize', 95 | 'CompletionMode', 96 | 'CustomCompletionSettings', 97 | 'Settable', 98 | ] 99 | -------------------------------------------------------------------------------- /cmd2/clipboard.py: -------------------------------------------------------------------------------- 1 | """Module provides basic ability to copy from and paste to the clipboard/pastebuffer.""" 2 | 3 | import typing 4 | 5 | import pyperclip # type: ignore[import] 6 | 7 | 8 | def get_paste_buffer() -> str: 9 | """Get the contents of the clipboard / paste buffer. 10 | 11 | :return: contents of the clipboard 12 | """ 13 | return typing.cast(str, pyperclip.paste()) 14 | 15 | 16 | def write_to_paste_buffer(txt: str) -> None: 17 | """Copy text to the clipboard / paste buffer. 18 | 19 | :param txt: text to copy to the clipboard 20 | """ 21 | pyperclip.copy(txt) 22 | -------------------------------------------------------------------------------- /cmd2/constants.py: -------------------------------------------------------------------------------- 1 | """Constants used throughout ``cmd2``.""" 2 | 3 | # Unless documented in https://cmd2.readthedocs.io/en/latest/api/index.html 4 | # nothing here should be considered part of the public API of this module 5 | 6 | INFINITY = float('inf') 7 | 8 | # Used for command parsing, output redirection, tab completion and word 9 | # breaks. Do not change. 10 | QUOTES = ['"', "'"] 11 | REDIRECTION_PIPE = '|' 12 | REDIRECTION_OUTPUT = '>' 13 | REDIRECTION_APPEND = '>>' 14 | REDIRECTION_CHARS = [REDIRECTION_PIPE, REDIRECTION_OUTPUT] 15 | REDIRECTION_TOKENS = [REDIRECTION_PIPE, REDIRECTION_OUTPUT, REDIRECTION_APPEND] 16 | COMMENT_CHAR = '#' 17 | MULTILINE_TERMINATOR = ';' 18 | 19 | LINE_FEED = '\n' 20 | 21 | # One character ellipsis 22 | HORIZONTAL_ELLIPSIS = '…' 23 | 24 | DEFAULT_SHORTCUTS = {'?': 'help', '!': 'shell', '@': 'run_script', '@@': '_relative_run_script'} 25 | 26 | # Used as the command name placeholder in disabled command messages. 27 | COMMAND_NAME = "" 28 | 29 | # All command functions start with this 30 | COMMAND_FUNC_PREFIX = 'do_' 31 | 32 | # All help functions start with this 33 | HELP_FUNC_PREFIX = 'help_' 34 | 35 | # All command completer functions start with this 36 | COMPLETER_FUNC_PREFIX = 'complete_' 37 | 38 | # The custom help category a command belongs to 39 | CMD_ATTR_HELP_CATEGORY = 'help_category' 40 | CLASS_ATTR_DEFAULT_HELP_CATEGORY = 'cmd2_default_help_category' 41 | 42 | # The argparse parser for the command 43 | CMD_ATTR_ARGPARSER = 'argparser' 44 | 45 | # Whether or not tokens are unquoted before sending to argparse 46 | CMD_ATTR_PRESERVE_QUOTES = 'preserve_quotes' 47 | 48 | # subcommand attributes for the base command name and the subcommand name 49 | SUBCMD_ATTR_COMMAND = 'parent_command' 50 | SUBCMD_ATTR_NAME = 'subcommand_name' 51 | SUBCMD_ATTR_ADD_PARSER_KWARGS = 'subcommand_add_parser_kwargs' 52 | 53 | # arpparse attribute linking to command set instance 54 | PARSER_ATTR_COMMANDSET = 'command_set' 55 | 56 | # custom attributes added to argparse Namespaces 57 | NS_ATTR_SUBCMD_HANDLER = '__subcmd_handler__' 58 | 59 | # For cases prior to Python 3.11 when shutil.get_terminal_size().columns can return 0. 60 | DEFAULT_TERMINAL_WIDTH = 80 61 | -------------------------------------------------------------------------------- /cmd2/exceptions.py: -------------------------------------------------------------------------------- 1 | """Custom exceptions for cmd2.""" 2 | 3 | from typing import Any 4 | 5 | ############################################################################################################ 6 | # The following exceptions are part of the public API 7 | ############################################################################################################ 8 | 9 | 10 | class SkipPostcommandHooks(Exception): # noqa: N818 11 | """For when a command has a failed bad enough to skip post command hooks, but not bad enough to print to the user.""" 12 | 13 | 14 | class Cmd2ArgparseError(SkipPostcommandHooks): 15 | """A ``SkipPostcommandHooks`` exception for when a command fails to parse its arguments. 16 | 17 | Normally argparse raises a SystemExit exception in these cases. To avoid stopping the command 18 | loop, catch the SystemExit and raise this instead. If you still need to run post command hooks 19 | after parsing fails, just return instead of raising an exception. 20 | """ 21 | 22 | 23 | class CommandSetRegistrationError(Exception): 24 | """For when an error occurs while a CommandSet is being added or removed from a cmd2 application.""" 25 | 26 | 27 | class CompletionError(Exception): 28 | """Raised during tab completion operations to report any sort of error you want printed. 29 | 30 | This can also be used just to display a message, even if it's not an error. For instance, ArgparseCompleter raises 31 | CompletionErrors to display tab completion hints and sets apply_style to False so hints aren't colored like error text. 32 | 33 | Example use cases: 34 | 35 | - Reading a database to retrieve a tab completion data set failed 36 | - A previous command line argument that determines the data set being completed is invalid 37 | - Tab completion hints 38 | """ 39 | 40 | def __init__(self, *args: Any, apply_style: bool = True) -> None: 41 | """Initialize CompletionError instance. 42 | 43 | :param apply_style: If True, then ansi.style_error will be applied to the message text when printed. 44 | Set to False in cases where the message text already has the desired style. 45 | Defaults to True. 46 | """ 47 | self.apply_style = apply_style 48 | 49 | super().__init__(*args) 50 | 51 | 52 | class PassThroughException(Exception): # noqa: N818 53 | """Normally all unhandled exceptions raised during commands get printed to the user. 54 | 55 | This class is used to wrap an exception that should be raised instead of printed. 56 | """ 57 | 58 | def __init__(self, *args: Any, wrapped_ex: BaseException) -> None: 59 | """Initialize PassThroughException instance. 60 | 61 | :param wrapped_ex: the exception that will be raised. 62 | """ 63 | self.wrapped_ex = wrapped_ex 64 | super().__init__(*args) 65 | 66 | 67 | ############################################################################################################ 68 | # The following exceptions are NOT part of the public API and are intended for internal use only. 69 | ############################################################################################################ 70 | 71 | 72 | class Cmd2ShlexError(Exception): 73 | """Raised when shlex fails to parse a command line string in StatementParser.""" 74 | 75 | 76 | class EmbeddedConsoleExit(SystemExit): 77 | """Custom exception class for use with the py command.""" 78 | 79 | 80 | class EmptyStatement(Exception): # noqa: N818 81 | """Custom exception class for handling behavior when the user just presses .""" 82 | 83 | 84 | class RedirectionError(Exception): 85 | """Custom exception class for when redirecting or piping output fails.""" 86 | -------------------------------------------------------------------------------- /cmd2/plugin.py: -------------------------------------------------------------------------------- 1 | """Classes for the cmd2 plugin system.""" 2 | 3 | from dataclasses import ( 4 | dataclass, 5 | ) 6 | from typing import Optional 7 | 8 | from .parsing import ( 9 | Statement, 10 | ) 11 | 12 | 13 | @dataclass 14 | class PostparsingData: 15 | """Data class containing information passed to postparsing hook methods.""" 16 | 17 | stop: bool 18 | statement: Statement 19 | 20 | 21 | @dataclass 22 | class PrecommandData: 23 | """Data class containing information passed to precommand hook methods.""" 24 | 25 | statement: Statement 26 | 27 | 28 | @dataclass 29 | class PostcommandData: 30 | """Data class containing information passed to postcommand hook methods.""" 31 | 32 | stop: bool 33 | statement: Statement 34 | 35 | 36 | @dataclass 37 | class CommandFinalizationData: 38 | """Data class containing information passed to command finalization hook methods.""" 39 | 40 | stop: bool 41 | statement: Optional[Statement] 42 | -------------------------------------------------------------------------------- /cmd2/py.typed: -------------------------------------------------------------------------------- 1 | # PEP 561 2 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | # Components allow you to isolate and categorize coverage data from your project with virtual filters 2 | component_management: 3 | individual_components: 4 | - component_id: cmd2 # this is an identifier that should not be changed 5 | name: cmd2 # this is a display name, and can be changed freely 6 | paths: 7 | - cmd2/** 8 | - component_id: plugins 9 | name: plugins 10 | paths: 11 | - plugins/** 12 | 13 | # Ignore certain paths, all files under these paths will be skipped during processing 14 | ignore: 15 | - "examples" # ignore example code folder 16 | - "tests" # ignore unit test code folder 17 | - "tests_isolated" # ignore integration test code folder 18 | -------------------------------------------------------------------------------- /docs/api/ansi.md: -------------------------------------------------------------------------------- 1 | # cmd2.ansi 2 | 3 | ::: cmd2.ansi 4 | -------------------------------------------------------------------------------- /docs/api/argparse_completer.md: -------------------------------------------------------------------------------- 1 | # cmd2.argparse_completer 2 | 3 | ::: cmd2.argparse_completer 4 | -------------------------------------------------------------------------------- /docs/api/argparse_custom.md: -------------------------------------------------------------------------------- 1 | # cmd2.argparse_custom 2 | 3 | ::: cmd2.argparse_custom 4 | -------------------------------------------------------------------------------- /docs/api/cmd.md: -------------------------------------------------------------------------------- 1 | # cmd2.Cmd 2 | 3 | ::: cmd2.Cmd 4 | -------------------------------------------------------------------------------- /docs/api/command_definition.md: -------------------------------------------------------------------------------- 1 | # cmd2.command_definition 2 | 3 | ::: cmd2.command_definition 4 | -------------------------------------------------------------------------------- /docs/api/constants.md: -------------------------------------------------------------------------------- 1 | # cmd2.constants 2 | 3 | ::: cmd2.constants 4 | -------------------------------------------------------------------------------- /docs/api/decorators.md: -------------------------------------------------------------------------------- 1 | # cmd2.decorators 2 | 3 | ::: cmd2.decorators 4 | -------------------------------------------------------------------------------- /docs/api/exceptions.md: -------------------------------------------------------------------------------- 1 | # cmd2.exceptions 2 | 3 | ::: cmd2.exceptions 4 | -------------------------------------------------------------------------------- /docs/api/history.md: -------------------------------------------------------------------------------- 1 | # cmd2.history 2 | 3 | ::: cmd2.history 4 | -------------------------------------------------------------------------------- /docs/api/index.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | These pages document the public API for `cmd2`. If a method, class, function, attribute, or constant is not documented here, consider it private and subject to change. There are many classes, methods, functions, and constants in the source code which do not begin with an underscore but are not documented here. When looking at the source code for this library, you cannot safely assume that because something doesn't start with an underscore, it is a public API. 4 | 5 | If a release of this library changes any of the items documented here, the version number will be incremented according to the [Semantic Version Specification](https://semver.org). 6 | 7 | ## Modules 8 | 9 | - [cmd2.Cmd](./cmd.md) - functions and attributes of the main class in this library 10 | - [cmd2.ansi](./ansi.md) - convenience classes and functions for generating ANSI escape sequences to style text in the terminal 11 | - [cmd2.argparse_completer](./argparse_completer.md) - classes for `argparse`-based tab completion 12 | - [cmd2.argparse_custom](./argparse_custom.md) - classes and functions for extending `argparse` 13 | - [cmd2.command_definition](./command_definition.md) - supports the definition of commands in separate classes to be composed into cmd2.Cmd 14 | - [cmd2.constants](./constants.md) - just like it says on the tin 15 | - [cmd2.decorators](./decorators.md) - decorators for `cmd2` commands 16 | - [cmd2.exceptions](./exceptions.md) - custom `cmd2` exceptions 17 | - [cmd2.history](./history.md) - classes for storing the history of previously entered commands 18 | - [cmd2.parsing](./parsing.md) - classes for parsing and storing user input 19 | - [cmd2.plugin](./plugin.md) - data classes for hook methods 20 | - [cmd2.py_bridge](./py_bridge.md) - classes for bridging calls from the embedded python environment to the host app 21 | - [cmd2.table_creator](./table_creator.md) - table creation module 22 | - [cmd2.utils](./utils.md) - various utility classes and functions 23 | -------------------------------------------------------------------------------- /docs/api/parsing.md: -------------------------------------------------------------------------------- 1 | # cmd2.parsing 2 | 3 | ::: cmd2.parsing 4 | -------------------------------------------------------------------------------- /docs/api/plugin.md: -------------------------------------------------------------------------------- 1 | # cmd2.plugin 2 | 3 | ::: cmd2.plugin 4 | -------------------------------------------------------------------------------- /docs/api/py_bridge.md: -------------------------------------------------------------------------------- 1 | # cmd2.py_bridge 2 | 3 | ::: cmd2.py_bridge 4 | -------------------------------------------------------------------------------- /docs/api/table_creator.md: -------------------------------------------------------------------------------- 1 | # cmd2.table_creator 2 | 3 | ::: cmd2.table_creator 4 | -------------------------------------------------------------------------------- /docs/api/utils.md: -------------------------------------------------------------------------------- 1 | # cmd2.utils 2 | 3 | ## Settings 4 | 5 | ::: cmd2.utils.Settable 6 | 7 | ## Quote Handling 8 | 9 | ::: cmd2.utils.is_quoted 10 | 11 | ::: cmd2.utils.quote_string 12 | 13 | ::: cmd2.utils.quote_string_if_needed 14 | 15 | ::: cmd2.utils.strip_quotes 16 | 17 | ## IO Handling 18 | 19 | ::: cmd2.utils.StdSim 20 | 21 | ::: cmd2.utils.ByteBuf 22 | 23 | ::: cmd2.utils.ProcReader 24 | 25 | ## Tab Completion 26 | 27 | ::: cmd2.utils.CompletionMode 28 | 29 | ::: cmd2.utils.CustomCompletionSettings 30 | 31 | ## Text Alignment 32 | 33 | ::: cmd2.utils.TextAlignment 34 | 35 | ::: cmd2.utils.align_text 36 | 37 | ::: cmd2.utils.align_left 38 | 39 | ::: cmd2.utils.align_right 40 | 41 | ::: cmd2.utils.align_center 42 | 43 | ::: cmd2.utils.truncate_line 44 | 45 | ## Miscellaneous 46 | 47 | ::: cmd2.utils.to_bool 48 | 49 | ::: cmd2.utils.categorize 50 | 51 | ::: cmd2.utils.remove_duplicates 52 | 53 | ::: cmd2.utils.alphabetical_sort 54 | 55 | ::: cmd2.utils.natural_sort 56 | 57 | ::: cmd2.utils.suggest_similar 58 | -------------------------------------------------------------------------------- /docs/doc_conventions.md: -------------------------------------------------------------------------------- 1 | # Documentation Conventions 2 | 3 | ## Guiding Principles 4 | 5 | Follow the [Documentation Principles](http://www.writethedocs.org/guide/writing/docs-principles/) described by [Write The Docs](http://www.writethedocs.org) 6 | 7 | In addition: 8 | 9 | - We have gone to great lengths to retain compatibility with the standard library cmd, the documentation should make it easy for developers to understand how to move from cmd to cmd2, and what benefits that will provide 10 | - We should provide both descriptive and reference documentation. 11 | - API reference documentation should be generated from docstrings in the code 12 | - Documentation should include rich hyperlinking to other areas of the documentation, and to the API reference 13 | 14 | ## Style Checker 15 | 16 | We strongly encourage all developers to use [Prettier](https://prettier.io/) for formatting all **Markdown** and YAML files. The easiest way to do this is to integrated it with your IDE and configure your IDE to format on save. You can also install `prettier` either using `npm` or OS package manager such as `brew` or `apt`. 17 | 18 | ## Naming Files 19 | 20 | All source files in the documentation must: 21 | 22 | - have all lower case file names 23 | - if the name has multiple words, separate them with an underscore 24 | - end in '.rst' 25 | 26 | ## Indenting 27 | 28 | In Markdown all indenting is significant. Use 4 spaces per indenting level. 29 | 30 | ## Wrapping 31 | 32 | Hard wrap all text so that line lengths are no greater than 120 characters. It makes everything easier when editing documentation, and has no impact on reading documentation because we render to html. 33 | 34 | ## Titles and Headings 35 | 36 | Reference the [Markdown Basic Syntax](https://www.markdownguide.org/basic-syntax/) for synatx basics or [The Markdown Guide](https://www.markdownguide.org/) for a more complete reference. 37 | 38 | ## Inline Code 39 | 40 | Code blocks can be created in two ways: 41 | 42 | - Indent the block - this will show as a monospace code block, but won't include highighting 43 | - use the triple backticks followed by the code language, e.e. `python` and close with triple backticks 44 | 45 | If you want to show non-Python code, like shell commands, then use a different language such as `javascript`, `shell`, `json`, etc. 46 | 47 | ## Links 48 | 49 | See the [Links](https://www.markdownguide.org/basic-syntax/) Markdown syntax documentation. 50 | 51 | ## API Documentation 52 | 53 | The API documentation is mostly pulled from docstrings in the source code using the MkDocs [mkdocstrings](https://mkdocstrings.github.io/) plugin. 54 | 55 | When using `mkdocstinrgs`, it must be preceded by a blank line before and after, i.e.: 56 | 57 | ```markdown 58 | ::: cmd2.history.History 59 | 60 | ::: cmd2.history.HistoryItem 61 | ``` 62 | 63 | ### Links to API Reference 64 | 65 | To reference a method or function, do the following: 66 | 67 | TODO: Figure out how to do this 68 | 69 | ## Referencing cmd2 70 | 71 | Whenever you reference `cmd2` in the documentation, enclose it in backticks. This indicates to Markdown that this words represents code and will stand out when rendered as HTML. 72 | -------------------------------------------------------------------------------- /docs/examples/alternate_event_loops.md: -------------------------------------------------------------------------------- 1 | # Alternate Event Loops 2 | 3 | Throughout this documentation we have focused on the **90%** use case, that is the use case we believe around **90+%** of our user base is looking for. This focuses on ease of use and the best out-of-the-box experience where developers get the most functionality for the least amount of effort. We are talking about running `cmd2` applications with the `cmdloop()` method: 4 | 5 | ```py 6 | from cmd2 import Cmd 7 | class App(Cmd): 8 | # customized attributes and methods here 9 | app = App() 10 | app.cmdloop() 11 | ``` 12 | 13 | However, there are some limitations to this way of using `cmd2`, mainly that `cmd2` owns the inner loop of a program. This can be unnecessarily restrictive and can prevent using libraries which depend on controlling their own event loop. 14 | 15 | Many Python concurrency libraries involve or require an event loop which they are in control of such as [asyncio](https://docs.python.org/3/library/asyncio.html), [gevent](http://www.gevent.org/), [Twisted](https://twistedmatrix.com), etc. 16 | 17 | `cmd2` applications can be executed in a fashion where `cmd2` doesn't own the main loop for the program by using code like the following: 18 | 19 | ```py 20 | import cmd2 21 | 22 | class Cmd2EventBased(cmd2.Cmd): 23 | def __init__(self): 24 | cmd2.Cmd.__init__(self) 25 | 26 | # ... your class code here ... 27 | 28 | if __name__ == '__main__': 29 | app = Cmd2EventBased() 30 | app.preloop() 31 | 32 | # Do this within whatever event loop mechanism you wish to run a single command 33 | cmd_line_text = "help history" 34 | app.runcmds_plus_hooks([cmd_line_text]) 35 | 36 | app.postloop() 37 | ``` 38 | 39 | The `cmd2.Cmd.runcmds_plus_hooks()` method runs multiple commands via `cmd2.Cmd.onecmd_plus_hooks`. 40 | 41 | The `cmd2.Cmd.onecmd_plus_hooks()` method will do the following to execute a single command in a normal fashion: 42 | 43 | 1. Parse user input into a `cmd2.Statement` object 44 | 1. Call methods registered with `cmd2.Cmd.register_postparsing_hook()` 45 | 1. Redirect output, if user asked for it and it's allowed 46 | 1. Start timer 47 | 1. Call methods registered with `cmd2.Cmd.register_precmd_hook` 48 | 1. Call `cmd2.Cmd.precmd` - for backwards compatibility with `cmd.Cmd` 49 | 1. Add statement to [History](../features/history.md) 50 | 1. Call `do_command` method 51 | 1. Call methods registered with `cmd2.Cmd.register_postcmd_hook()` 52 | 1. Call `cmd2.Cmd.postcmd` - for backwards compatibility with `cmd.Cmd` 53 | 1. Stop timer and display the elapsed time 54 | 1. Stop redirecting output if it was redirected 55 | 1. Call methods registered with `cmd2.Cmd.register_cmdfinalization_hook()` 56 | 57 | Running in this fashion enables the ability to integrate with an external event loop. However, how to integrate with any specific event loop is beyond the scope of this documentation. Please note that running in this fashion comes with several disadvantages, including: 58 | 59 | - Requires the developer to write more code 60 | - Does not support transcript testing 61 | - Does not allow commands at invocation via command-line arguments 62 | -------------------------------------------------------------------------------- /docs/examples/examples.md: -------------------------------------------------------------------------------- 1 | # List of cmd2 examples 2 | 3 | {% 4 | include-markdown "../../examples/README.md" 5 | %} 6 | -------------------------------------------------------------------------------- /docs/examples/index.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | 4 | 5 | - [First Application](first_app.md) 6 | - [Alternate Event Loops](alternate_event_loops.md) 7 | - [List of cmd2 examples](examples.md) 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/features/clipboard.md: -------------------------------------------------------------------------------- 1 | # Clipboard Integration 2 | 3 | Nearly every operating system has some notion of a short-term storage area which can be accessed by any program. Usually this is called the clipboard, but sometimes people refer to it as the paste buffer. 4 | 5 | `cmd2` integrates with the operating system clipboard using the [pyperclip](https://github.com/asweigart/pyperclip) module. Command output can be sent to the clipboard by ending the command with a greater than symbol: 6 | 7 | ```text 8 | mycommand args > 9 | ``` 10 | 11 | Think of it as though you are redirecting output to an unnamed, ephemeral place, you know, like the clipboard. You can also append output to the current contents of the clipboard by ending the command with two greater than symbols: 12 | 13 | ```text 14 | mycommand arg1 arg2 >> 15 | ``` 16 | 17 | ## Developers 18 | 19 | You can control whether the above user features of adding output to the operating system clipboard are allowed for the user by setting the [cmd2.Cmd.allow_clipboard][] attribute. The default value is `True`. Set it to `False` and the above functionality will generate an error message instead of adding the output to the clipboard. [cmd2.Cmd.allow_clipboard][] can be set upon initialization, and you can change it at any time from within your code. 20 | 21 | If you would like your `cmd2` based application to be able to use the clipboard in additional or alternative ways, you can use the following methods (which work uniformly on Windows, macOS, and Linux). 22 | 23 | ::: cmd2.clipboard 24 | handler: python 25 | options: 26 | show_root_heading: false 27 | show_source: false 28 | -------------------------------------------------------------------------------- /docs/features/embedded_python_shells.md: -------------------------------------------------------------------------------- 1 | # Embedded Python Shells 2 | 3 | ## Python (optional) 4 | 5 | If the `cmd2.Cmd` class is instantiated with `include_py=True`, then the optional `py` command will be present and run an interactive Python shell: 6 | 7 | ```py 8 | from cmd2 import Cmd 9 | class App(Cmd): 10 | def __init__(self): 11 | Cmd.__init__(self, include_py=True) 12 | ``` 13 | 14 | The Python shell can run CLI commands from you application using the object named in `self.pyscript_name` (defaults to `app`). This wrapper provides access to execute commands in your `cmd2` application while maintaining isolation from the full `Cmd` instance. For example, any application command can be run with `app("command ...")`. 15 | 16 | You may optionally enable full access to to your application by setting `self.self_in_py` to `True`. Enabling this flag adds `self` to the python session, which is a reference to your `cmd2` application. This can be useful for debugging your application. 17 | 18 | Any local or global variable created within the Python session will not persist in the CLI's environment. 19 | 20 | Anything in `self.py_locals` is always available in the Python environment. 21 | 22 | All of these parameters are also available to Python scripts which run in your application via the `run_pyscript` command: 23 | 24 | - supports tab completion of file system paths 25 | - has the ability to pass command-line arguments to the scripts invoked 26 | 27 | This command provides a more complicated and more powerful scripting capability than that provided by the simple text file scripts. Python scripts can include conditional control flow logic. See the **python_scripting.py** `cmd2` application and the **script_conditional.py** script in the `examples` source code directory for an example of how to achieve this in your own applications. See [Scripting](./scripting.md) for an explanation of both scripting methods in **cmd2** applications. 28 | 29 | A simple example of using `run_pyscript` is shown below along with the [arg_printer](https://github.com/python-cmd2/cmd2/blob/master/examples/scripts/arg_printer.py) script: 30 | 31 | ```sh 32 | (Cmd) run_pyscript examples/scripts/arg_printer.py foo bar baz 33 | Running Python script 'arg_printer.py' which was called with 3 arguments 34 | arg 1: 'foo' 35 | arg 2: 'bar' 36 | arg 3: 'baz' 37 | ``` 38 | 39 | ## IPython (optional) 40 | 41 | **If** [IPython](http://ipython.readthedocs.io) is installed on the system **and** the `cmd2.Cmd` class is instantiated with `include_ipy=True`, then the optional `ipy` command will run an interactive IPython shell: 42 | 43 | ```py 44 | from cmd2 import Cmd 45 | class App(Cmd): 46 | def __init__(self): 47 | Cmd.__init__(self, include_ipy=True) 48 | ``` 49 | 50 | The `ipy` command enters an interactive [IPython](http://ipython.readthedocs.io) session. Similar to an interactive Python session, this shell can access your application instance via `self` if `self.self_in_py` is `True` and any changes to your application made via `self` will persist. However, any local or global variable created within the `ipy` shell will not persist in the CLI's environment 51 | 52 | Also, as in the interactive Python session, the `ipy` shell has access to the contents of `self.py_locals` and can call back into the application using the `app` object (or your custom name). 53 | 54 | [IPython](http://ipython.readthedocs.io) provides many advantages, including: 55 | 56 | > - Comprehensive object introspection 57 | > - Get help on objects with `?` 58 | > - Extensible tab completion, with support by default for completion of python variables and keywords 59 | > - Good built-in [ipdb](https://pypi.org/project/ipdb/) debugger 60 | 61 | The object introspection and tab completion make IPython particularly efficient for debugging as well as for interactive experimentation and data analysis. 62 | -------------------------------------------------------------------------------- /docs/features/index.md: -------------------------------------------------------------------------------- 1 | # Features 2 | 3 |
4 | 5 | - [Argument Processing](argument_processing.md) 6 | - [Builtin Commands](builtin_commands.md) 7 | - [Clipboard Integration](clipboard.md) 8 | - [Commands](commands.md) 9 | - [Completion](completion.md) 10 | - [Disabling Commands](disable_commands.md) 11 | - [Embedded Python Shells](embedded_python_shells.md) 12 | - [Generating Output](generating_output.md) 13 | - [Help](help.md) 14 | - [History](history.md) 15 | - [Hooks](hooks.md) 16 | - [Initialization](initialization.md) 17 | - [Miscellaneous Features](misc.md) 18 | - [Modular Commands](modular_commands.md) 19 | - [Multiline Commands](multiline_commands.md) 20 | - [Integrating with the OS](os.md) 21 | - [Packaging a cmd2 application for distribution](packaging.md) 22 | - [Plugins](plugins.md) 23 | - [Prompt](prompt.md) 24 | - [Output Redirection and Pipes](redirection.md) 25 | - [Scripting](scripting.md) 26 | - [Settings](settings.md) 27 | - [Shortcuts, Aliases, and Macros](shortcuts_aliases_macros.md) 28 | - [Startup Commands](startup_commands.md) 29 | - [Table Creation](table_creation.md) 30 | - [Transcripts](transcripts.md) 31 | 32 |
33 | -------------------------------------------------------------------------------- /docs/features/misc.md: -------------------------------------------------------------------------------- 1 | # Miscellaneous Features 2 | 3 | ## Timer {: #Timer } 4 | 5 | Turn the timer setting on, and `cmd2` will show the wall time it takes for each command to execute. 6 | 7 | ## Exiting 8 | 9 | Mention quit, and EOF handling built into `cmd2`. 10 | 11 | ## select 12 | 13 | Presents numbered options to user, as bash `select`. 14 | 15 | `app.select` is called from within a method (not by the user directly; it is `app.select`, not `app.do_select`). 16 | 17 | ::: cmd2.Cmd.select 18 | 19 | ```py 20 | def do_eat(self, arg): 21 | sauce = self.select('sweet salty', 'Sauce? ') 22 | result = '{food} with {sauce} sauce, yum!' 23 | result = result.format(food=arg, sauce=sauce) 24 | self.stdout.write(result + '\n') 25 | ``` 26 | 27 | ```text 28 | (Cmd) eat wheaties 29 | 1. sweet 30 | 2. salty 31 | Sauce? 2 32 | wheaties with salty sauce, yum! 33 | ``` 34 | 35 | ## Disabling Commands 36 | 37 | `cmd2` supports disabling commands during runtime. This is useful if certain commands should only be available when the application is in a specific state. When a command is disabled, it will not show up in the help menu or tab complete. If a user tries to run the command, a command-specific message supplied by the developer will be printed. The following functions support this feature. 38 | 39 | - **enable_command** 40 | : Enable an individual command 41 | - **enable_category** 42 | : Enable an entire category of commands 43 | - **disable_command** 44 | : Disable an individual command and set the message that will print when this command is run or help is called on it while disabled 45 | - **disable_category** 46 | : Disable an entire category of commands and set the message that will print when anything in this category is run or help is called on it while disabled 47 | 48 | See the definitions of these functions for descriptions of their arguments. 49 | 50 | See the `do_enable_commands()` and `do_disable_commands()` functions in the [HelpCategories](https://github.com/python-cmd2/cmd2/blob/master/examples/help_categories.py) example for a demonstration. 51 | 52 | ## Default to shell 53 | 54 | Every `cmd2` application can execute operating-system level (shell) commands with `shell` or a `!` shortcut: 55 | 56 | (Cmd) shell which python 57 | /usr/bin/python 58 | (Cmd) !which python 59 | /usr/bin/python 60 | 61 | However, if the parameter `default_to_shell` is `True`, then _every_ command will be attempted on the operating system. Only if that attempt fails (i.e., produces a nonzero return value) will the application's own `default` method be called. 62 | 63 | (Cmd) which python 64 | /usr/bin/python 65 | (Cmd) my dog has fleas 66 | sh: my: not found 67 | *** Unknown syntax: my dog has fleas 68 | -------------------------------------------------------------------------------- /docs/features/multiline_commands.md: -------------------------------------------------------------------------------- 1 | # Multiline Commands 2 | 3 | Command input may span multiple lines for the commands whose names are listed in the `multiline_commands` argument to `cmd2.Cmd.__init__()`. These commands will be executed only after the user has entered a _terminator_. By default, the command terminator is `;`; specifying the `terminators` optional argument to `cmd2.Cmd.__init__()` allows different terminators. A blank line is _always_ considered a command terminator (cannot be overridden). 4 | 5 | In multiline commands, output redirection characters like `>` and `|` are part of the command arguments unless they appear after the terminator. 6 | 7 | ## Continuation prompt 8 | 9 | When a user types a **Multiline Command** it may span more than one line of input. The prompt for the first line of input is specified by the [cmd2.Cmd.prompt][] instance attribute - see [Customizing the Prompt](./prompt.md#customizing-the-prompt). The prompt for subsequent lines of input is defined by the `cmd2.Cmd.continuation_prompt` attribute. 10 | 11 | ## Use cases 12 | 13 | Multiline commands should probably be used sparingly in order to preserve a good user experience for your `cmd2`-based line-oriented command interpreter application. 14 | 15 | However, some use cases benefit significantly from the ability to have commands that span more than one line. For example, you might want the ability for your user to type in a SQL command, which can often span lines and which are terminated with a semicolon. 16 | 17 | We estimate that less than 5 percent of `cmd2` applications use this feature. But it is here for those use cases where it provides value. 18 | -------------------------------------------------------------------------------- /docs/features/packaging.md: -------------------------------------------------------------------------------- 1 | # Packaging a cmd2 application for distribution 2 | 3 | As a general-purpose tool for building interactive command-line applications, `cmd2` is designed to be used in many ways. How you distribute your `cmd2` application to customers or end users is up to you. See the [Overview of Packaging for Python](https://packaging.python.org/overview/) from the Python Packaging Authority for a thorough discussion of the extensive options within the Python ecosystem. 4 | 5 | ## Publishing to the Python Package Index (PyPI) 6 | 7 | The easiest way is to use to follow the tutorial for [Packaging Python Projects](https://packaging.python.org/en/latest/tutorials/packaging-projects/). This will show you how to package your application as a Python package and uploadi to the Python Package Index ([PyPI](https://pypi.org/)). Once published there, users will be able to install it using idiomatic Python packaging tools such as [pip](https://pip.pypa.io/) or [uv](https://github.com/astral-sh/uv). 8 | 9 | Small tweaks on this process can allow you to publish to private PyPI mirror such as one hosted on [AWS CodeArtifact](https://aws.amazon.com/codeartifact/). 10 | 11 | ## Packaging your application in a container using Docker 12 | 13 | Packing your Python application in a [Docker](https://www.docker.com/) container is a great when it comes to cross-platform portability and convenience since your this container will inlude all dependencies for your application and run them in an isolated environment which won't conflict with operating system dependencies. 14 | 15 | This convenient blog post will show you [How to "Dockerize" Your Python Applications](https://www.docker.com/blog/how-to-dockerize-your-python-applications/). 16 | 17 | ## Packing your application along with Python in an installer 18 | 19 | For developers wishing to package a `cmd2` application into a single binary image or compressed file, we can recommend all of the following based on personal and professional experience: 20 | 21 | - [PyInstaller](https://www.pyinstaller.org) 22 | - Freeze (package) Python programs into stand-alone executables 23 | - PyInstaller bundles a Python application and all its dependencies into a single package 24 | - The user can run the packaged app without installing a Python interpreter or any modules 25 | - [Nuitka](https://nuitka.net) 26 | - Nuitka is a Python compiler written in Python 27 | - You feed it your Python app, it does a lot of clever things, and spits out an executable or extension module 28 | - This can be particularly convenient if you wish to obfuscate the Python source code behind your application 29 | - [Conda Constructor](https://github.com/conda/constructor) 30 | - Allows you to create a custom Python distro based on [Miniconda](https://docs.conda.io/en/latest/miniconda.html) 31 | - [PyOxidizer](https://github.com/indygreg/PyOxidizer) 32 | - PyOxidizer is a utility for producing binaries that embed Python 33 | - PyOxidizer is capable of producing a single file executable - with a copy of Python and all its dependencies statically linked and all resources embedded in the executable 34 | - You can copy a single executable file to another machine and run a Python application contained within. It just works. 35 | 36 | !!! warning 37 | 38 | We haven't personally tested PyOxidizer with `cmd2` applications like everything else on this page, though we have heard good things about it 39 | -------------------------------------------------------------------------------- /docs/features/prompt.md: -------------------------------------------------------------------------------- 1 | # Prompt 2 | 3 | `cmd2` issues a configurable prompt before soliciting user input. 4 | 5 | ## Customizing the Prompt 6 | 7 | This prompt can be configured by setting the `cmd2.Cmd.prompt` instance attribute. This contains the string which should be printed as a prompt for user input. See the [Pirate](https://github.com/python-cmd2/cmd2/blob/master/examples/pirate.py#L39) example for the simple use case of statically setting the prompt. 8 | 9 | ## Continuation Prompt 10 | 11 | When a user types a [Multiline Command](./multiline_commands.md) it may span more than one line of input. The prompt for the first line of input is specified by the `cmd2.Cmd.prompt` instance attribute. The prompt for subsequent lines of input is defined by the `cmd2.Cmd.continuation_prompt` attribute.See the [Initialization](https://github.com/python-cmd2/cmd2/blob/master/examples/initialization.py#L42) example for a demonstration of customizing the continuation prompt. 12 | 13 | ## Updating the prompt 14 | 15 | If you wish to update the prompt between commands, you can do so using one of the [Application Lifecycle Hooks](./hooks.md#application-lifecycle-hooks) such as a [Postcommand hook](./hooks.md#postcommand-hooks). See [PythonScripting](https://github.com/python-cmd2/cmd2/blob/master/examples/python_scripting.py#L38-L55) for an example of dynamically updating the prompt. 16 | 17 | ## Asynchronous Feedback 18 | 19 | `cmd2` provides these functions to provide asynchronous feedback to the user without interfering with the command line. This means the feedback is provided to the user when they are still entering text at the prompt. To use this functionality, the application must be running in a terminal that supports VT100 control characters and readline. Linux, Mac, and Windows 10 and greater all support these. 20 | 21 | ::: cmd2.Cmd.async_alert 22 | 23 | ::: cmd2.Cmd.async_update_prompt 24 | 25 | ::: cmd2.Cmd.async_refresh_prompt 26 | 27 | ::: cmd2.Cmd.need_prompt_refresh 28 | 29 | `cmd2` also provides a function to change the title of the terminal window. This feature requires the application be running in a terminal that supports VT100 control characters. Linux, Mac, and Windows 10 and greater all support these. 30 | 31 | ::: cmd2.Cmd.set_window_title 32 | 33 | The easiest way to understand these functions is to see the [AsyncPrinting](https://github.com/python-cmd2/cmd2/blob/master/examples/async_printing.py) example for a demonstration. 34 | -------------------------------------------------------------------------------- /docs/features/redirection.md: -------------------------------------------------------------------------------- 1 | # Output Redirection and Pipes 2 | 3 | As in POSIX shells, output of a command can be redirected and/or piped. This feature is fully cross-platform and works identically on Windows, macOS, and Linux. 4 | 5 | ## Output Redirection 6 | 7 | ### Redirect to a file 8 | 9 | Redirecting the output of a `cmd2` command to a file works just like in POSIX shells: 10 | 11 | - send to a file with `>`, as in `mycommand args > filename.txt` 12 | - append to a file with `>>`, as in `mycommand args >> filename.txt` 13 | 14 | If you need to include any of these redirection characters in your command, you can enclose them in quotation marks, `mycommand 'with > in the argument'`. 15 | 16 | ### Redirect to the clipboard 17 | 18 | `cmd2` output redirection supports an additional feature not found in most shells - if the file name following the `>` or `>>` is left blank, then the output is redirected to the operating system clipboard so that it can then be pasted into another program. 19 | 20 | - overwrite the clipboard with `mycommand args >` 21 | - append to the clipboard with `mycommand args >>` 22 | 23 | ## Pipes 24 | 25 | Piping the output of a `cmd2` command to a shell command works just like in POSIX shells: 26 | 27 | - pipe as input to a shell command with `|`, as in `mycommand args | wc` 28 | 29 | ## Multiple Pipes and Redirection 30 | 31 | Multiple pipes, optionally followed by a redirect, are supported. Thus, it is possible to do something like the following: 32 | 33 | (Cmd) help | grep py | wc > output.txt 34 | 35 | The above runs the **help** command, pipes its output to **grep** searching for any lines containing _py_, then pipes the output of grep to the **wc** "word count" command, and finally writes redirects the output of that to a file called _output.txt_. 36 | 37 | ## Disabling Redirection 38 | 39 | !!! note 40 | 41 | If you wish to disable cmd2's output redirection and pipes features, you can do so by setting the `allow_redirection` attribute of your `cmd2.Cmd` class instance to `False`. This would be useful, for example, if you want to restrict the ability for an end user to write to disk or interact with shell commands for security reasons: 42 | 43 | ```py 44 | from cmd2 import Cmd 45 | class App(Cmd): 46 | def __init__(self): 47 | super().__init__(allow_redirection=False) 48 | ``` 49 | 50 | cmd2's parser will still treat the `>`, `>>`, and `|` symbols as output redirection and pipe symbols and will strip arguments after them from the command line arguments accordingly. But output from a command will not be redirected to a file or piped to a shell command. 51 | 52 | ## Limitations of Redirection 53 | 54 | Some limitations apply to redirection and piping within `cmd2` applications: 55 | 56 | - Can only pipe to shell commands, not other `cmd2` application commands 57 | - **stdout** gets redirected/piped, **stderr** does not 58 | -------------------------------------------------------------------------------- /docs/features/startup_commands.md: -------------------------------------------------------------------------------- 1 | # Startup Commands 2 | 3 | `cmd2` provides a couple different ways for running commands immediately after your application starts up: 4 | 5 | 1. Commands at Invocation 6 | 1. Startup Script 7 | 8 | Commands run as part of a startup script are always run immediately after the application finishes initializing so they are guaranteed to run before any _Commands At Invocation_. 9 | 10 | ## Commands At Invocation 11 | 12 | You can send commands to your app as you invoke it by including them as extra arguments to the program. `cmd2` interprets each argument as a separate command, so you should enclose each command in quotation marks if it is more than a one-word command. You can use either single or double quotes for this purpose. 13 | 14 | $ python examples/example.py "say hello" "say Gracie" quit 15 | hello 16 | Gracie 17 | 18 | You can end your commands with a **quit** command so that your `cmd2` application runs like a non-interactive command-line utility (CLU). This means that it can then be scripted from an external application and easily used in automation. 19 | 20 | !!! note 21 | 22 | If you wish to disable cmd2's consumption of command-line arguments, you can do so by setting the `allow_cli_args` argument of your `cmd2.Cmd` class instance to `False`. This would be useful, for example, if you wish to use something like [Argparse](https://docs.python.org/3/library/argparse.html) to parse the overall command line arguments for your application: 23 | 24 | ```py 25 | from cmd2 import Cmd 26 | class App(Cmd): 27 | def __init__(self): 28 | super().__init__(allow_cli_args=False) 29 | ``` 30 | 31 | ## Startup Script 32 | 33 | You can execute commands from an initialization script by passing a file path to the `startup_script` argument to the `cmd2.Cmd.__init__()` method like so: 34 | 35 | ```py 36 | class StartupApp(cmd2.Cmd): 37 | def __init__(self): 38 | cmd2.Cmd.__init__(self, startup_script='.cmd2rc') 39 | ``` 40 | 41 | This text file should contain a [Command Script](./scripting.md#command-scripts). See the [AliasStartup](https://github.com/python-cmd2/cmd2/blob/master/examples/alias_startup.py) example for a demonstration. 42 | 43 | You can silence a startup script's output by setting `silence_startup_script` to True: 44 | 45 | ```py 46 | cmd2.Cmd.__init__(self, startup_script='.cmd2rc', silence_startup_script=True) 47 | ``` 48 | 49 | Anything written to stderr will still print. Additionally, a startup script cannot be silenced if `allow_redirection` is False since silencing works by redirecting a script's output to `os.devnull`. 50 | -------------------------------------------------------------------------------- /docs/features/table_creation.md: -------------------------------------------------------------------------------- 1 | # Table Creation 2 | 3 | `cmd2` provides a table creation class called `cmd2.table_creator.TableCreator`. This class handles ANSI style sequences and characters with display widths greater than 1 when performing width calculations. It was designed with the ability to build tables one row at a time. This helps when you have large data sets that you don't want to hold in memory or when you receive portions of the data set incrementally. 4 | 5 | `TableCreator` has one public method: `cmd2.table_creator.TableCreator.generate_row()`. 6 | 7 | This function and the `cmd2.table_creator.Column` class provide all features needed to build tables with headers, borders, colors, horizontal and vertical alignment, and wrapped text. However, it's generally easier to inherit from this class and implement a more granular API rather than use `TableCreator` directly. 8 | 9 | The following table classes build upon `TableCreator` and are provided in the [cmd2.table_creater](../api/table_creator.md) module. They can be used as is or as examples for how to build your own table classes. 10 | 11 | `cmd2.table_creator.SimpleTable` - Implementation of TableCreator which generates a borderless table with an optional divider row after the header. This class can be used to create the whole table at once or one row at a time. 12 | 13 | `cmd2.table_creator.BorderedTable` - Implementation of TableCreator which generates a table with borders around the table and between rows. Borders between columns can also be toggled. This class can be used to create the whole table at once or one row at a time. 14 | 15 | `cmd2.table_creator.AlternatingTable` - Implementation of BorderedTable which uses background colors to distinguish between rows instead of row border lines. This class can be used to create the whole table at once or one row at a time. 16 | 17 | See the [table_creation](https://github.com/python-cmd2/cmd2/blob/master/examples/table_creation.py) example to see these classes in use 18 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # cmd2 2 | 3 | A python package for building powerful command-line interpreter (CLI) programs. Extends the Python Standard Library's [cmd](https://docs.python.org/3/library/cmd.html) package. 4 | 5 | The basic use of `cmd2` is identical to that of [cmd](https://docs.python.org/3/library/cmd.html). 6 | 7 | 1. Create a subclass of [cmd2.Cmd][]. Define attributes and `do_*` methods to control its behavior. Throughout this documentation, we will assume that you are naming your subclass `App`: 8 | 9 | ```py title="Creating a class inherited from cmd2.Cmd" linenums="1" 10 | from cmd2 import Cmd 11 | class App(Cmd): 12 | # customized attributes and methods here 13 | ``` 14 | 15 | 2. Instantiate `App` and start the command loop: 16 | 17 | ```py title="Instatiating and starting a cmd2 app" linenums="1" hl_lines="5-6" 18 | from cmd2 import Cmd 19 | class App(Cmd): 20 | # customized attributes and methods here 21 | 22 | app = App() 23 | app.cmdloop() 24 | ``` 25 | 26 | ## Getting Started 27 | 28 | {% 29 | include-markdown "./overview/index.md" 30 | %} 31 | 32 | ## Migrating from cmd 33 | 34 | {% 35 | include-markdown "./migrating/index.md" 36 | %} 37 | 38 | ## Features 39 | 40 | {% 41 | include-markdown "./features/index.md" 42 | start="" 43 | end="" 44 | %} 45 | 46 | ## Examples 47 | 48 | {% 49 | include-markdown "./examples/index.md" 50 | start="" 51 | end="" 52 | %} 53 | 54 | ## Plugins 55 | 56 | {% 57 | include-markdown "./plugins/index.md" 58 | start="" 59 | end="" 60 | %} 61 | 62 | ## [Testing](testing.md) 63 | 64 | ## [API Reference](api/index.md) 65 | 66 | ## Meta 67 | 68 | [Documentation Conventions](doc_conventions.md) 69 | -------------------------------------------------------------------------------- /docs/javascripts/readthedocs.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded", function (event) { 2 | // Trigger Read the Docs' search addon instead of Material MkDocs default 3 | document.querySelector(".md-search__input").addEventListener("focus", (e) => { 4 | const event = new CustomEvent("readthedocs-search-show"); 5 | document.dispatchEvent(event); 6 | }); 7 | }); 8 | 9 | // Use CustomEvent to generate the version selector 10 | document.addEventListener("readthedocs-addons-data-ready", function (event) { 11 | const config = event.detail.data(); 12 | const versioning = ` 13 |
14 | 17 | 18 |
    19 | ${config.versions.active 20 | .map( 21 | (version) => ` 22 |
  • 23 | 24 | ${version.slug} 25 | 26 |
  • `, 27 | ) 28 | .join("\n")} 29 |
30 |
`; 31 | 32 | document 33 | .querySelector(".md-header__topic") 34 | .insertAdjacentHTML("beforeend", versioning); 35 | }); 36 | -------------------------------------------------------------------------------- /docs/migrating/incompatibilities.md: -------------------------------------------------------------------------------- 1 | # Incompatibilities 2 | 3 | `cmd2` strives to be drop-in compatible with [cmd](https://docs.python.org/3/library/cmd.html), however there are a few incompatibilities. 4 | 5 | ## Cmd.emptyline() 6 | 7 | The [Cmd.emptyline()](https://docs.python.org/3/library/cmd.html#cmd.Cmd.emptyline) function is called when an empty line is entered in response to the prompt. By default, in [cmd](https://docs.python.org/3/library/cmd.html) if this method is not overridden, it repeats and executes the last nonempty command entered. However, no end user we have encountered views this as expected or desirable default behavior. `cmd2` completely ignores empty lines and the base class `cmd.emptyline()` method never gets called and thus the empty line behavior cannot be overridden. 8 | 9 | ## Cmd.identchars 10 | 11 | In [cmd](https://docs.python.org/3/library/cmd.html), the [Cmd.identchars](https://docs.python.org/3/library/cmd.html#cmd.Cmd.identchars) attribute contains the string of characters accepted for command names. [cmd](https://docs.python.org/3/library/cmd.html) uses those characters to split the first "word" of the input, without requiring the user to type a space. For example, if `identchars` contained a string of all alphabetic characters, the user could enter a command like `L20` and it would be interpreted as the command `L` with the first argument of `20`. 12 | 13 | Since version 0.9.0, `cmd2` has ignored `identchars`; the parsing logic in `cmd2` splits the command and arguments on whitespace. We opted for this breaking change because while [cmd](https://docs.python.org/3/library/cmd.html) supports unicode, using non-ascii unicode characters in command names while simultaneously using `identchars` functionality can be somewhat painful. Requiring white space to delimit arguments also ensures reliable operation of many other useful `cmd2` features, including [Tab Completion](../features/completion.md) and [Shortcuts, Aliases, and Macros](../features/shortcuts_aliases_macros.md). 14 | 15 | If you really need this functionality in your app, you can add it back in by writing a [Postparsing Hook](../features/hooks.md#postparsing-hooks). 16 | 17 | ## Cmd.cmdqueue 18 | 19 | In [cmd](https://docs.python.org/3/library/cmd.html), the [Cmd.cmdqueue](https://docs.python.org/3/library/cmd.html#cmd.Cmd.cmdqueue) attribute contains a list of queued input lines. The cmdqueue list is checked in `cmdloop()` when new input is needed; if it is nonempty, its elements will be processed in order, as if entered at the prompt. 20 | 21 | Since version 0.9.13 `cmd2` has removed support for `Cmd.cmdqueue`. Because `cmd2` supports running commands via the main `cmdloop()`, text scripts, Python scripts, transcripts, and history replays, the only way to preserve consistent behavior across these methods was to eliminate the command queue. Additionally, reasoning about application behavior is much easier without this queue present. 22 | -------------------------------------------------------------------------------- /docs/migrating/index.md: -------------------------------------------------------------------------------- 1 | # Migrating From cmd 2 | 3 | If you're thinking of migrating your [cmd](https://docs.python.org/3/library/cmd.html) app to `cmd2`, this section will help you decide if it's right for your app, and show you how to do it. 4 | 5 | - [Why cmd2](why.md) - we try and convince you to use `cmd2` instead of [cmd](https://docs.python.org/3/library/cmd.html) 6 | - [Incompatibilities](incompatibilities.md) - `cmd2` is not quite 100% compatible with [cmd](https://docs.python.org/3/library/cmd.html). 7 | - [Minimum Required Changes](minimum.md) - the minimum changes required to move from [cmd](https://docs.python.org/3/library/cmd.html) to `cmd2`. Start your migration here. 8 | - [Next Steps](next_steps.md) - Once you've migrated, here a list of things you can do next to add even more functionality to your app. 9 | -------------------------------------------------------------------------------- /docs/migrating/minimum.md: -------------------------------------------------------------------------------- 1 | # Minimum Required Changes 2 | 3 | `cmd2.Cmd` subclasses `Cmd.cmd` from the standard library, and overrides most of the methods. Most apps based on the standard library can be migrated to `cmd2` in just a couple of minutes. 4 | 5 | ## Import and Inheritance 6 | 7 | You need to change your import from this: 8 | 9 | ```py 10 | import cmd 11 | ``` 12 | 13 | to this: 14 | 15 | ```py 16 | import cmd2 17 | ``` 18 | 19 | Then you need to change your class definition from: 20 | 21 | ```py 22 | class CmdLineApp(cmd.Cmd): 23 | ``` 24 | 25 | to: 26 | 27 | ```py 28 | class CmdLineApp(cmd2.Cmd): 29 | ``` 30 | 31 | ## Exiting 32 | 33 | Have a look at the commands you created to exit your application. You probably have one called `exit` and maybe a similar one called `quit`. You also might have implemented a `do_EOF()` method so your program exits like many operating system shells. If all these commands do is quit the application, you may be able to remove them. See [Exiting](../features/misc.md#exiting). 34 | 35 | ## Distribution 36 | 37 | If you are distributing your application, you'll also need to ensure that `cmd2` is properly installed. You will need to add the following dependency to your `pyproject.toml` or `setup.py`: 38 | 39 | 'cmd2>=2,<3' 40 | 41 | See [Integrate cmd2 Into Your Project](../overview/integrating.md) for more details. 42 | -------------------------------------------------------------------------------- /docs/migrating/next_steps.md: -------------------------------------------------------------------------------- 1 | # Next Steps 2 | 3 | Once your current application is using `cmd2`, you can start to expand the functionality by levering other `cmd2` features. The three ideas here will get you started. Browse the rest of the [Features](../features/index.md) to see what else `cmd2` can help you do. 4 | 5 | ## Argument Parsing 6 | 7 | For all but the simplest of commands, it's probably easier to use [argparse](https://docs.python.org/3/library/argparse.html) to parse user input. `cmd2` provides a `@with_argparser()` decorator which associates an `ArgumentParser` object with one of your commands. Using this method will: 8 | 9 | 1. Pass your command a [Namespace](https://docs.python.org/3/library/argparse.html#argparse.Namespace) containing the arguments instead of a string of text. 10 | 2. Properly handle quoted string input from your users. 11 | 3. Create a help message for you based on the `ArgumentParser`. 12 | 4. Give you a big headstart adding [Tab Completion](../features/completion.md) to your application. 13 | 5. Make it much easier to implement subcommands (i.e. `git` has a bunch of subcommands such as `git pull`, `git diff`, etc). 14 | 15 | There's a lot more about [Argument Processing](../features/argument_processing.md) if you want to dig in further. 16 | 17 | ## Help 18 | 19 | If you have lot of commands in your application, `cmd2` can categorize those commands using a one line decorator `@with_category()`. When a user types `help` the available commands will be organized by the category you specified. 20 | 21 | If you were already using `argparse` or decided to switch to it, you can easily [standardize all of your help messages](../features/argument_processing.md#help-messages) to be generated by your argument parsers and displayed by `cmd2`. No more help messages that don't match what the code actually does. 22 | 23 | ## Generating Output 24 | 25 | If your program generates output by printing directly to `sys.stdout`, you should consider switching to `cmd2.Cmd.poutput`, `cmd2.Cmd.perror`, and `cmd2.Cmd.pfeedback`. These methods work with several of the built in [Settings](../features/settings.md) to allow the user to view or suppress feedback (i.e. progress or status output). They also properly handle ansi colored output according to user preference. Speaking of colored output, you can use any color library you want, or use the included `cmd2.ansi.style` function. These and other related topics are covered in [Generating Output](../features/generating_output.md). 26 | -------------------------------------------------------------------------------- /docs/migrating/why.md: -------------------------------------------------------------------------------- 1 | # Why cmd2 2 | 3 | ## cmd 4 | 5 | [cmd](#cmd) is the Python Standard Library's module for creating simple interactive command-line applications. [cmd](#cmd) is an extremely bare-bones framework which leaves a lot to be desired. It doesn't even include a built-in way to exit from an application! 6 | 7 | Since the API provided by [cmd](#cmd) provides the foundation on which `cmd2` is based, understanding the use of [cmd](#cmd) is the first step in learning the use of `cmd2`. Once you have read the [cmd](#cmd) docs, return here to learn the ways that `cmd2` differs from [cmd](#cmd). 8 | 9 | ## cmd2 10 | 11 | `cmd2` is a batteries-included extension of [cmd](#cmd), which provides a wealth of functionality to make it quicker and easier for developers to create feature-rich interactive command-line applications which delight customers. 12 | 13 | `cmd2` can be used as a drop-in replacement for [cmd](#cmd) with a few minor discrepancies as discussed in the [Incompatibilities](incompatibilities.md) section. Simply importing `cmd2` in place of [cmd](#cmd) will add many features to an application without any further modifications. Migrating to `cmd2` will also open many additional doors for making it possible for developers to provide a top-notch interactive command-line experience for their users. 14 | 15 | ## Free Features 16 | 17 | After switching from [cmd](#cmd) to `cmd2`, your application will have the following new features and capabilities, without you having to do anything: 18 | 19 | - More robust [History](../features/history.md). Both [cmd](#cmd) and `cmd2` have readline history, but `cmd2` also has a robust `history` command which allows you to edit prior commands in a text editor of your choosing, re-run multiple commands at a time, and save prior commands as a script to be executed later. 20 | - Users can redirect output to a file or pipe it to some other operating system command. You did remember to use `self.stdout` instead of `sys.stdout` in all of your print functions, right? If you did, then this will work out of the box. If you didn't, you'll have to go back and fix them. Before you do, you might consider the various ways `cmd2` has of [Generatoring Output](../features/generating_output.md). 21 | - Users can load script files, which contain a series of commands to be executed. 22 | - Users can create [Shortcuts, Aliases, and Macros](../features/shortcuts_aliases_macros.md) to reduce the typing required for repetitive commands. 23 | - Embedded python shell allows a user to execute python code from within your `cmd2` app. How meta. 24 | - [Clipboard Integration](../features/clipboard.md) allows you to save command output to the operating system clipboard. 25 | - A built-in [Timer](../features/misc.md#Timer) can show how long it takes a command to execute 26 | - A [Transcript](../features/transcripts.md) is a file which contains both the input and output of a successful session of a `cmd2`-based app. The transcript can be played back into the app as a unit test. 27 | 28 | ## Next Steps 29 | 30 | In addition to the features you get with no additional work, `cmd2` offers a broad range of additional capabilities which can be easily added to your application. [Next Steps](next_steps.md) has some ideas of where you can start, or you can dig in to all the [Features](../features/index.md). 31 | -------------------------------------------------------------------------------- /docs/overrides/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} {% block site_meta %} {{ super() }} 2 | 3 | {% endblock %} 4 | -------------------------------------------------------------------------------- /docs/overview/alternatives.md: -------------------------------------------------------------------------------- 1 | # Alternatives 2 | 3 | For programs that do not interact with the user in a continuous loop - programs that simply accept a set of arguments from the command line, return results, and do not keep the user within the program's environment - all you need are [sys](https://docs.python.org/3/library/sys.html).argv (the command-line arguments) and [argparse](https://docs.python.org/3/library/argparse.html) (for parsing UNIX-style options and flags). Though some people may prefer [docopt](https://pypi.org/project/docopt) or [click](https://click.palletsprojects.com) to [argparse](https://docs.python.org/3/library/argparse.html). 4 | 5 | The [textual](https://textual.textualize.io/) module is capable of building sophisticated full-screen terminal user interfaces that are not limited to simple text input and output; they can paint the screen with options that are selected from using the cursor keys and even mouse clicks. However, programming a `textual` application is not as straightforward as using `cmd2`. 6 | 7 | Several Python packages exist for building interactive command-line applications approximately similar in concept to [cmd](https://docs.python.org/3/library/cmd.html) applications. None of them share `cmd2`'s close ties to [cmd](https://docs.python.org/3/library/cmd.html), but they may be worth investigating nonetheless. Two of the most mature and full featured are: 8 | 9 | - [Python Prompt Toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) 10 | - [Click](https://click.palletsprojects.com) 11 | 12 | [Python Prompt Toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) is a library for building powerful interactive command lines and terminal applications in Python. It provides a lot of advanced visual features like syntax highlighting, bottom bars, and the ability to create fullscreen apps. 13 | 14 | [Click](https://click.palletsprojects.com) is a Python package for creating beautiful command line interfaces in a composable way with as little code as necessary. It is more geared towards command line utilities instead of command line interpreters, but it can be used for either. 15 | 16 | Getting a working command-interpreter application based on either [Python Prompt Toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) or [Click](https://click.palletsprojects.com) requires a good deal more effort and boilerplate code than `cmd2`. `cmd2` focuses on providing an excellent out-of-the-box experience with as many useful features as possible built in for free with as little work required on the developer's part as possible. We believe that `cmd2` provides developers the easiest way to write a command-line interpreter, while allowing a good experience for end users. If you are seeking a visually richer end-user experience and don't mind investing more development time, we would recommend checking out [Python Prompt Toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit). 17 | -------------------------------------------------------------------------------- /docs/overview/index.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | Building a new [REPL](https://en.wikipedia.org/wiki/Read–eval–print_loop) or [Command Line Interface](https://en.wikipedia.org/wiki/Command-line_interface) application? 4 | 5 | Already built an application that uses [cmd](https://docs.python.org/3/library/cmd.html) from the python standard library and want to add more functionality with very little work? 6 | 7 | `cmd2` is a powerful python library for building command line applications. Start here to find out if this library is a good fit for your needs. 8 | 9 | - [Installation Instructions](installation.md) - how to install `cmd2` and associated optional dependencies 10 | - [First Application](../examples/first_app.md) - a sample application showing 8 key features of `cmd2` 11 | - [Integrate cmd2 Into Your Project](integrating.md) - adding `cmd2` to your project 12 | - [Alternatives](alternatives.md) - other python packages that might meet your needs 13 | - [Resources](resources.md) - related links and other materials 14 | -------------------------------------------------------------------------------- /docs/overview/integrating.md: -------------------------------------------------------------------------------- 1 | # Integrate cmd2 Into Your Project 2 | 3 | Once installed, you will want to ensure that your project's dependencies include `cmd2`. Make sure your `pyproject.toml` or `setup.py` includes the following dependency 4 | 5 | 'cmd2>=2.4' 6 | 7 | The `cmd2` project uses [Semantic Versioning](https://semver.org), which means that any incompatible API changes will be release with a new major version number. The public API is documented in the [API Reference](../api/index.md). 8 | 9 | We recommend that you follow the advice given by the Python Packaging User Guide related to [install_requires](https://packaging.python.org/discussions/install-requires-vs-requirements/). By setting an upper bound on the allowed version, you can ensure that your project does not inadvertently get installed with an incompatible future version of `cmd2`. 10 | 11 | ## OS Considerations 12 | 13 | If you would like to use [Tab Completion](../features/completion.md), then you need a compatible version of [readline](https://tiswww.case.edu/php/chet/readline/rltop.html) installed on your operating system (OS). `cmd2` forces a sane install of `readline` on both `Windows` and `MacOS`, but does not do so on `Linux`. If for some reason, you have a Linux OS that has the [Editline Library (libedit)](https://www.thrysoee.dk/editline/) installed instead of `readline`, you will need to manually add a dependency on `gnureadline`. Make sure to include the following dependency in your `pyproject.toml` or `setup.py`: 14 | 15 | 'gnureadline' 16 | -------------------------------------------------------------------------------- /docs/overview/resources.md: -------------------------------------------------------------------------------- 1 | # Resources 2 | 3 | Project related links and other resources: 4 | 5 | - [cmd](https://docs.python.org/3/library/cmd.html) 6 | - [cmd2 project page](https://github.com/python-cmd2/cmd2) 7 | - [project bug tracker](https://github.com/python-cmd2/cmd2/issues) 8 | - PyOhio 2019: [slides](https://github.com/python-cmd2/talks/blob/master/PyOhio_2019/cmd2-PyOhio_2019.pdf), [video](https://www.youtube.com/watch?v=pebeWrTqIIw), [examples](https://github.com/python-cmd2/talks/tree/master/PyOhio_2019/examples) 9 | -------------------------------------------------------------------------------- /docs/plugins/external_test.md: -------------------------------------------------------------------------------- 1 | # External Test Plugin 2 | 3 | ## Overview 4 | 5 | The [External Test Plugin](https://github.com/python-cmd2/cmd2/tree/master/plugins/ext_test) supports testing of a cmd2 application by exposing access to cmd2 commands with the same context as from within a cmd2 [Python Script](../features/scripting.md#python-scripts). This interface captures `stdout`, `stderr`, as well as any application-specific data returned by the command. This also allows for verification of an application's support for [Python Scripts](../features/scripting.md#python-scripts) and enables the cmd2 application to be tested as part of a larger system integration test. 6 | 7 | ## Example cmd2 Application 8 | 9 | The following short example shows how to mix in the external test plugin to create a fixture for testing your cmd2 application. 10 | 11 | Define your cmd2 application 12 | 13 | ```py 14 | import cmd2 15 | class ExampleApp(cmd2.Cmd): 16 | """An class to show how to use a plugin""" 17 | def __init__(self, *args, **kwargs): 18 | # gotta have this or neither the plugin or cmd2 will initialize 19 | super().__init__(*args, **kwargs) 20 | 21 | def do_something(self, arg): 22 | self.last_result = 5 23 | self.poutput('this is the something command') 24 | ``` 25 | 26 | ## Defining the test fixture 27 | 28 | In your test, define a fixture for your cmd2 application 29 | 30 | ```py 31 | import cmd2_ext_test 32 | import pytest 33 | 34 | class ExampleAppTester(cmd2_ext_test.ExternalTestMixin, ExampleApp): 35 | def __init__(self, *args, **kwargs): 36 | # gotta have this or neither the plugin or cmd2 will initialize 37 | super().__init__(*args, **kwargs) 38 | 39 | @pytest.fixture 40 | def example_app(): 41 | app = ExampleAppTester() 42 | app.fixture_setup() 43 | yield app 44 | app.fixture_teardown() 45 | ``` 46 | 47 | ## Writing Tests 48 | 49 | Now write your tests that validate your application using the `cmd2_ext_test.ExternalTestMixin.app_cmd` function to access the cmd2 application's commands. This allows invocation of the application's commands in the same format as a user would type. The results from calling a command matches what is returned from running an python script with cmd2's [run_pyscript](../features/builtin_commands.md#run_pyscript) command, which provides `stdout`, `stderr`, and the command's result data. 50 | 51 | ```py 52 | from cmd2 import CommandResult 53 | 54 | def test_something(example_app): 55 | # execute a command 56 | out = example_app.app_cmd("something") 57 | 58 | # validate the command output and result data 59 | assert isinstance(out, CommandResult) 60 | assert str(out.stdout).strip() == 'this is the something command' 61 | assert out.data == 5 62 | ``` 63 | -------------------------------------------------------------------------------- /docs/plugins/index.md: -------------------------------------------------------------------------------- 1 | # Plugins 2 | 3 | 4 | 5 | - [External Test Plugin](external_test.md) 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs-include-markdown-plugin 2 | mkdocs-macros-plugin 3 | mkdocs-material 4 | pyperclip 5 | setuptools 6 | setuptools-scm 7 | wcwidth 8 | -------------------------------------------------------------------------------- /docs/stylesheets/readthedocs.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Reduce Read the Docs' flyout font a little bit */ 3 | --readthedocs-flyout-font-size: 0.7rem; 4 | 5 | /* Reduce Read the Docs' notification font a little bit */ 6 | --readthedocs-notification-font-size: 0.8rem; 7 | 8 | /* This customization is not yet perfect because we can't change the `line-height` yet. */ 9 | /* See https://github.com/readthedocs/addons/issues/197 */ 10 | --readthedocs-search-font-size: 0.7rem; 11 | } 12 | -------------------------------------------------------------------------------- /docs/testing.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | ## Overview 4 | 5 | This covers special considerations when writing unit tests for a cmd2 application. 6 | 7 | ## Testing Commands 8 | 9 | The [External Test Plugin](plugins/external_test.md) provides a mixin class with an function that allows external calls to application commands. The `cmd2_ext_test.ExternalTestMixin.app_cmd` function captures and returns stdout, stderr, and the command-specific result data. 10 | 11 | ## Mocking 12 | 13 | If you need to mock anything in your cmd2 application, and most specifically in sub-classes of `cmd2.Cmd` or `cmd2.command_definition.CommandSet`, you must use [Autospeccing](https://docs.python.org/3/library/unittest.mock.html#autospeccing), [spec=True](https://docs.python.org/3/library/unittest.mock.html#patch), or whatever equivalent is provided in the mocking library you're using. 14 | 15 | In order to automatically load functions as commands cmd2 performs a number of reflection calls to look up attributes of classes defined in your cmd2 application. Many mocking libraries will automatically create mock objects to match any attribute being requested, regardless of whether they're present in the object being mocked. This behavior can incorrectly instruct cmd2 to treat a function or attribute as something it needs to recognize and process. To prevent this, you should always mock with [Autospeccing](https://docs.python.org/3/library/unittest.mock.html#autospeccing) or [spec=True](https://docs.python.org/3/library/unittest.mock.html#patch enabled. If you don't have autospeccing on, your unit tests will fail with an error message like: 16 | 17 | ```sh 18 | cmd2.exceptions.CommandSetRegistrationError: Subcommand 19 | is not valid: must be a string. 20 | Received instead 21 | ``` 22 | 23 | ## Examples 24 | 25 | ```py 26 | def test*mocked_methods(): 27 | with mock.patch.object(MockMethodApp, 'foo', spec=True): 28 | cli = MockMethodApp() 29 | ``` 30 | 31 | Another one using [pytest-mock](https://pypi.org/project/pytest-mock) to provide a `mocker` fixture: 32 | 33 | ```py 34 | def test_mocked_methods2(mocker): 35 | mock_cmdloop = mocker.patch("cmd2.Cmd.cmdloop", autospec=True) 36 | cli = cmd2.Cmd() 37 | cli.cmdloop() 38 | assert mock_cmdloop.call_count == 1 39 | ``` 40 | -------------------------------------------------------------------------------- /examples/.cmd2rc: -------------------------------------------------------------------------------- 1 | alias create ls !ls -hal 2 | alias create pwd !pwd 3 | -------------------------------------------------------------------------------- /examples/alias_startup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A simple example demonstrating the following: 3 | 1) How to add custom command aliases using the alias command 4 | 2) How to run an initialization script at startup. 5 | """ 6 | 7 | import os 8 | 9 | import cmd2 10 | 11 | 12 | class AliasAndStartup(cmd2.Cmd): 13 | """Example cmd2 application where we create commands that just print the arguments they are called with.""" 14 | 15 | def __init__(self) -> None: 16 | alias_script = os.path.join(os.path.dirname(__file__), '.cmd2rc') 17 | super().__init__(startup_script=alias_script) 18 | 19 | def do_nothing(self, args) -> None: 20 | """This command does nothing and produces no output.""" 21 | 22 | 23 | if __name__ == '__main__': 24 | import sys 25 | 26 | app = AliasAndStartup() 27 | sys.exit(app.cmdloop()) 28 | -------------------------------------------------------------------------------- /examples/arg_decorators.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """An example demonstrating how use one of cmd2's argument parsing decorators.""" 3 | 4 | import argparse 5 | import os 6 | 7 | import cmd2 8 | 9 | 10 | class ArgparsingApp(cmd2.Cmd): 11 | def __init__(self) -> None: 12 | super().__init__(include_ipy=True) 13 | self.intro = 'cmd2 has awesome decorators to make it easy to use Argparse to parse command arguments' 14 | 15 | # do_fsize parser 16 | fsize_parser = cmd2.Cmd2ArgumentParser(description='Obtain the size of a file') 17 | fsize_parser.add_argument('-c', '--comma', action='store_true', help='add comma for thousands separator') 18 | fsize_parser.add_argument('-u', '--unit', choices=['MB', 'KB'], help='unit to display size in') 19 | fsize_parser.add_argument('file_path', help='path of file', completer=cmd2.Cmd.path_complete) 20 | 21 | @cmd2.with_argparser(fsize_parser) 22 | def do_fsize(self, args: argparse.Namespace) -> None: 23 | """Obtain the size of a file.""" 24 | expanded_path = os.path.expanduser(args.file_path) 25 | 26 | try: 27 | size = os.path.getsize(expanded_path) 28 | except OSError as ex: 29 | self.perror(f"Error retrieving size: {ex}") 30 | return 31 | 32 | if args.unit == 'KB': 33 | size /= 1024 34 | elif args.unit == 'MB': 35 | size /= 1024 * 1024 36 | else: 37 | args.unit = 'bytes' 38 | size = round(size, 2) 39 | 40 | if args.comma: 41 | size = f'{size:,}' 42 | self.poutput(f'{size} {args.unit}') 43 | 44 | # do_pow parser 45 | pow_parser = cmd2.Cmd2ArgumentParser() 46 | pow_parser.add_argument('base', type=int) 47 | pow_parser.add_argument('exponent', type=int, choices=range(-5, 6)) 48 | 49 | @cmd2.with_argparser(pow_parser) 50 | def do_pow(self, args: argparse.Namespace) -> None: 51 | """Raise an integer to a small integer exponent, either positive or negative. 52 | 53 | :param args: argparse arguments 54 | """ 55 | self.poutput(f'{args.base} ** {args.exponent} == {args.base**args.exponent}') 56 | 57 | 58 | if __name__ == '__main__': 59 | app = ArgparsingApp() 60 | app.cmdloop() 61 | -------------------------------------------------------------------------------- /examples/arg_print.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A simple example demonstrating the following: 3 | 1) How arguments and options get parsed and passed to commands 4 | 2) How to change what syntax gets parsed as a comment and stripped from the arguments. 5 | 6 | This is intended to serve as a live demonstration so that developers can 7 | experiment with and understand how command and argument parsing work. 8 | 9 | It also serves as an example of how to create shortcuts. 10 | """ 11 | 12 | import cmd2 13 | 14 | 15 | class ArgumentAndOptionPrinter(cmd2.Cmd): 16 | """Example cmd2 application where we create commands that just print the arguments they are called with.""" 17 | 18 | def __init__(self) -> None: 19 | # Create command shortcuts which are typically 1 character abbreviations which can be used in place of a command 20 | shortcuts = dict(cmd2.DEFAULT_SHORTCUTS) 21 | shortcuts.update({'$': 'aprint', '%': 'oprint'}) 22 | super().__init__(shortcuts=shortcuts) 23 | 24 | def do_aprint(self, statement) -> None: 25 | """Print the argument string this basic command is called with.""" 26 | self.poutput(f'aprint was called with argument: {statement!r}') 27 | self.poutput(f'statement.raw = {statement.raw!r}') 28 | self.poutput(f'statement.argv = {statement.argv!r}') 29 | self.poutput(f'statement.command = {statement.command!r}') 30 | 31 | @cmd2.with_argument_list 32 | def do_lprint(self, arglist) -> None: 33 | """Print the argument list this basic command is called with.""" 34 | self.poutput(f'lprint was called with the following list of arguments: {arglist!r}') 35 | 36 | @cmd2.with_argument_list(preserve_quotes=True) 37 | def do_rprint(self, arglist) -> None: 38 | """Print the argument list this basic command is called with (with quotes preserved).""" 39 | self.poutput(f'rprint was called with the following list of arguments: {arglist!r}') 40 | 41 | oprint_parser = cmd2.Cmd2ArgumentParser() 42 | oprint_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') 43 | oprint_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE') 44 | oprint_parser.add_argument('-r', '--repeat', type=int, help='output [n] times') 45 | oprint_parser.add_argument('words', nargs='+', help='words to print') 46 | 47 | @cmd2.with_argparser(oprint_parser) 48 | def do_oprint(self, args) -> None: 49 | """Print the options and argument list this options command was called with.""" 50 | self.poutput(f'oprint was called with the following\n\toptions: {args!r}') 51 | 52 | pprint_parser = cmd2.Cmd2ArgumentParser() 53 | pprint_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') 54 | pprint_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE') 55 | pprint_parser.add_argument('-r', '--repeat', type=int, help='output [n] times') 56 | 57 | @cmd2.with_argparser(pprint_parser, with_unknown_args=True) 58 | def do_pprint(self, args, unknown) -> None: 59 | """Print the options and argument list this options command was called with.""" 60 | self.poutput(f'oprint was called with the following\n\toptions: {args!r}\n\targuments: {unknown}') 61 | 62 | 63 | if __name__ == '__main__': 64 | import sys 65 | 66 | app = ArgumentAndOptionPrinter() 67 | sys.exit(app.cmdloop()) 68 | -------------------------------------------------------------------------------- /examples/basic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """A simple example demonstrating the following: 3 | 1) How to add a command 4 | 2) How to add help for that command 5 | 3) Persistent history 6 | 4) How to run an initialization script at startup 7 | 5) How to add custom command aliases using the alias command 8 | 6) Shell-like capabilities. 9 | """ 10 | 11 | import cmd2 12 | from cmd2 import ( 13 | Bg, 14 | Fg, 15 | style, 16 | ) 17 | 18 | 19 | class BasicApp(cmd2.Cmd): 20 | CUSTOM_CATEGORY = 'My Custom Commands' 21 | 22 | def __init__(self) -> None: 23 | super().__init__( 24 | multiline_commands=['echo'], 25 | persistent_history_file='cmd2_history.dat', 26 | startup_script='scripts/startup.txt', 27 | include_ipy=True, 28 | ) 29 | 30 | self.intro = style('Welcome to PyOhio 2019 and cmd2!', fg=Fg.RED, bg=Bg.WHITE, bold=True) + ' 😀' 31 | 32 | # Allow access to your application in py and ipy via self 33 | self.self_in_py = True 34 | 35 | # Set the default category name 36 | self.default_category = 'cmd2 Built-in Commands' 37 | 38 | @cmd2.with_category(CUSTOM_CATEGORY) 39 | def do_intro(self, _) -> None: 40 | """Display the intro banner.""" 41 | self.poutput(self.intro) 42 | 43 | @cmd2.with_category(CUSTOM_CATEGORY) 44 | def do_echo(self, arg) -> None: 45 | """Example of a multiline command.""" 46 | self.poutput(arg) 47 | 48 | 49 | if __name__ == '__main__': 50 | app = BasicApp() 51 | app.cmdloop() 52 | -------------------------------------------------------------------------------- /examples/colors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A sample application for cmd2. Demonstrating colorized output. 3 | 4 | Experiment with the command line options on the `speak` command to see how 5 | different output colors ca 6 | 7 | The allow_style setting has three possible values: 8 | 9 | Never 10 | poutput(), pfeedback(), and ppaged() strip all ANSI style sequences 11 | which instruct the terminal to colorize output 12 | 13 | Terminal 14 | (the default value) poutput(), pfeedback(), and ppaged() do not strip any 15 | ANSI style sequences when the output is a terminal, but if the output is 16 | a pipe or a file the style sequences are stripped. If you want colorized 17 | output, add ANSI style sequences using cmd2's internal ansi module. 18 | 19 | Always 20 | poutput(), pfeedback(), and ppaged() never strip ANSI style sequences, 21 | regardless of the output destination 22 | """ 23 | 24 | import cmd2 25 | from cmd2 import ( 26 | Bg, 27 | Fg, 28 | ansi, 29 | ) 30 | 31 | fg_choices = [c.name.lower() for c in Fg] 32 | bg_choices = [c.name.lower() for c in Bg] 33 | 34 | 35 | class CmdLineApp(cmd2.Cmd): 36 | """Example cmd2 application demonstrating colorized output.""" 37 | 38 | def __init__(self) -> None: 39 | # Set include_ipy to True to enable the "ipy" command which runs an interactive IPython shell 40 | super().__init__(include_ipy=True) 41 | 42 | self.maxrepeats = 3 43 | # Make maxrepeats settable at runtime 44 | self.add_settable(cmd2.Settable('maxrepeats', int, 'max repetitions for speak command', self)) 45 | 46 | # Should ANSI color output be allowed 47 | self.allow_style = ansi.AllowStyle.TERMINAL 48 | 49 | speak_parser = cmd2.Cmd2ArgumentParser() 50 | speak_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') 51 | speak_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE') 52 | speak_parser.add_argument('-r', '--repeat', type=int, help='output [n] times') 53 | speak_parser.add_argument('-f', '--fg', choices=fg_choices, help='foreground color to apply to output') 54 | speak_parser.add_argument('-b', '--bg', choices=bg_choices, help='background color to apply to output') 55 | speak_parser.add_argument('-l', '--bold', action='store_true', help='bold the output') 56 | speak_parser.add_argument('-u', '--underline', action='store_true', help='underline the output') 57 | speak_parser.add_argument('words', nargs='+', help='words to say') 58 | 59 | @cmd2.with_argparser(speak_parser) 60 | def do_speak(self, args) -> None: 61 | """Repeats what you tell me to.""" 62 | words = [] 63 | for word in args.words: 64 | if args.piglatin: 65 | word = f'{word[1:]}{word[0]}ay' 66 | if args.shout: 67 | word = word.upper() 68 | words.append(word) 69 | 70 | repetitions = args.repeat or 1 71 | 72 | fg_color = Fg[args.fg.upper()] if args.fg else None 73 | bg_color = Bg[args.bg.upper()] if args.bg else None 74 | output_str = ansi.style(' '.join(words), fg=fg_color, bg=bg_color, bold=args.bold, underline=args.underline) 75 | 76 | for _ in range(min(repetitions, self.maxrepeats)): 77 | # .poutput handles newlines, and accommodates output redirection too 78 | self.poutput(output_str) 79 | 80 | def do_timetravel(self, _) -> None: 81 | """A command which always generates an error message, to demonstrate custom error colors.""" 82 | self.perror('Mr. Fusion failed to start. Could not energize flux capacitor.') 83 | 84 | 85 | if __name__ == '__main__': 86 | import sys 87 | 88 | c = CmdLineApp() 89 | sys.exit(c.cmdloop()) 90 | -------------------------------------------------------------------------------- /examples/custom_parser.py: -------------------------------------------------------------------------------- 1 | """Defines the CustomParser used with override_parser.py example.""" 2 | 3 | import sys 4 | 5 | from cmd2 import ( 6 | Cmd2ArgumentParser, 7 | ansi, 8 | set_default_argument_parser_type, 9 | ) 10 | 11 | 12 | # First define the parser 13 | class CustomParser(Cmd2ArgumentParser): 14 | """Overrides error class.""" 15 | 16 | def __init__(self, *args, **kwargs) -> None: 17 | super().__init__(*args, **kwargs) 18 | 19 | def error(self, message: str) -> None: 20 | """Custom override that applies custom formatting to the error message.""" 21 | lines = message.split('\n') 22 | formatted_message = '' 23 | for linum, line in enumerate(lines): 24 | if linum == 0: 25 | formatted_message = 'Error: ' + line 26 | else: 27 | formatted_message += '\n ' + line 28 | 29 | self.print_usage(sys.stderr) 30 | 31 | # Format errors with style_warning() 32 | formatted_message = ansi.style_warning(formatted_message) 33 | self.exit(2, f'{formatted_message}\n\n') 34 | 35 | 36 | # Now set the default parser for a cmd2 app 37 | set_default_argument_parser_type(CustomParser) 38 | -------------------------------------------------------------------------------- /examples/default_categories.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Simple example demonstrating basic CommandSet usage.""" 3 | 4 | import cmd2 5 | from cmd2 import ( 6 | CommandSet, 7 | with_default_category, 8 | ) 9 | 10 | 11 | @with_default_category('Default Category') 12 | class MyBaseCommandSet(CommandSet): 13 | """Defines a default category for all sub-class CommandSets.""" 14 | 15 | 16 | class ChildInheritsParentCategories(MyBaseCommandSet): 17 | """This subclass doesn't declare any categories so all commands here are also categorized under 'Default Category'.""" 18 | 19 | def do_hello(self, _: cmd2.Statement) -> None: 20 | self._cmd.poutput('Hello') 21 | 22 | def do_world(self, _: cmd2.Statement) -> None: 23 | self._cmd.poutput('World') 24 | 25 | 26 | @with_default_category('Non-Heritable Category', heritable=False) 27 | class ChildOverridesParentCategoriesNonHeritable(MyBaseCommandSet): 28 | """This subclass overrides the 'Default Category' from the parent, but in a non-heritable fashion. Sub-classes of this 29 | CommandSet will not inherit this category and will, instead, inherit 'Default Category'. 30 | """ 31 | 32 | def do_goodbye(self, _: cmd2.Statement) -> None: 33 | self._cmd.poutput('Goodbye') 34 | 35 | 36 | class GrandchildInheritsGrandparentCategory(ChildOverridesParentCategoriesNonHeritable): 37 | """This subclass's parent class declared its default category non-heritable. Instead, it inherits the category defined 38 | by the grandparent class. 39 | """ 40 | 41 | def do_aloha(self, _: cmd2.Statement) -> None: 42 | self._cmd.poutput('Aloha') 43 | 44 | 45 | @with_default_category('Heritable Category') 46 | class ChildOverridesParentCategories(MyBaseCommandSet): 47 | """This subclass is decorated with a default category that is heritable. This overrides the parent class's default 48 | category declaration. 49 | """ 50 | 51 | def do_bonjour(self, _: cmd2.Statement) -> None: 52 | self._cmd.poutput('Bonjour') 53 | 54 | 55 | class GrandchildInheritsHeritable(ChildOverridesParentCategories): 56 | """This subclass's parent declares a default category that overrides its parent. As a result, commands in this 57 | CommandSet will be categorized under 'Heritable Category'. 58 | """ 59 | 60 | def do_monde(self, _: cmd2.Statement) -> None: 61 | self._cmd.poutput('Monde') 62 | 63 | 64 | class ExampleApp(cmd2.Cmd): 65 | """Example to demonstrate heritable default categories.""" 66 | 67 | def __init__(self) -> None: 68 | super().__init__() 69 | 70 | def do_something(self, _arg) -> None: 71 | self.poutput('this is the something command') 72 | 73 | 74 | if __name__ == '__main__': 75 | app = ExampleApp() 76 | app.cmdloop() 77 | -------------------------------------------------------------------------------- /examples/dynamic_commands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """A simple example demonstrating how do_* commands can be created in a loop.""" 3 | 4 | import functools 5 | 6 | import cmd2 7 | from cmd2.constants import ( 8 | COMMAND_FUNC_PREFIX, 9 | HELP_FUNC_PREFIX, 10 | ) 11 | 12 | COMMAND_LIST = ['foo', 'bar'] 13 | CATEGORY = 'Dynamic Commands' 14 | 15 | 16 | class CommandsInLoop(cmd2.Cmd): 17 | """Example of dynamically adding do_* commands.""" 18 | 19 | def __init__(self) -> None: 20 | # Add dynamic commands before calling cmd2.Cmd's init since it validates command names 21 | for command in COMMAND_LIST: 22 | # Create command function and add help category to it 23 | cmd_func = functools.partial(self.send_text, text=command) 24 | cmd2.categorize(cmd_func, CATEGORY) 25 | 26 | # Add command function to CLI object 27 | cmd_func_name = COMMAND_FUNC_PREFIX + command 28 | setattr(self, cmd_func_name, cmd_func) 29 | 30 | # Add help function to CLI object 31 | help_func = functools.partial(self.text_help, text=command) 32 | help_func_name = HELP_FUNC_PREFIX + command 33 | setattr(self, help_func_name, help_func) 34 | 35 | super().__init__(include_ipy=True) 36 | 37 | def send_text(self, _args: cmd2.Statement, *, text: str) -> None: 38 | """Simulate sending text to a server and printing the response.""" 39 | self.poutput(text.capitalize()) 40 | 41 | def text_help(self, *, text: str) -> None: 42 | """Deal with printing help for the dynamically added commands.""" 43 | self.poutput(f"Simulate sending {text!r} to a server and printing the response") 44 | 45 | 46 | if __name__ == '__main__': 47 | app = CommandsInLoop() 48 | app.cmdloop() 49 | -------------------------------------------------------------------------------- /examples/environment.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A sample application for cmd2 demonstrating customized environment parameters.""" 3 | 4 | import cmd2 5 | 6 | 7 | class EnvironmentApp(cmd2.Cmd): 8 | """Example cmd2 application.""" 9 | 10 | def __init__(self) -> None: 11 | super().__init__() 12 | self.degrees_c = 22 13 | self.sunny = False 14 | self.add_settable( 15 | cmd2.Settable('degrees_c', int, 'Temperature in Celsius', self, onchange_cb=self._onchange_degrees_c) 16 | ) 17 | self.add_settable(cmd2.Settable('sunny', bool, 'Is it sunny outside?', self)) 18 | 19 | def do_sunbathe(self, _arg) -> None: 20 | """Attempt to sunbathe.""" 21 | if self.degrees_c < 20: 22 | result = f"It's {self.degrees_c} C - are you a penguin?" 23 | elif not self.sunny: 24 | result = 'Too dim.' 25 | else: 26 | result = 'UV is bad for your skin.' 27 | self.poutput(result) 28 | 29 | def _onchange_degrees_c(self, _param_name, _old, new) -> None: 30 | # if it's over 40C, it's gotta be sunny, right? 31 | if new > 40: 32 | self.sunny = True 33 | 34 | 35 | if __name__ == '__main__': 36 | import sys 37 | 38 | c = EnvironmentApp() 39 | sys.exit(c.cmdloop()) 40 | -------------------------------------------------------------------------------- /examples/event_loops.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A sample application for integrating cmd2 with external event loops. 3 | 4 | This is an example of how to use cmd2 in a way so that cmd2 doesn't own the inner event loop of your application. 5 | 6 | This opens up the possibility of registering cmd2 input with event loops, like asyncio, without occupying the main loop. 7 | """ 8 | 9 | import cmd2 10 | 11 | 12 | class Cmd2EventBased(cmd2.Cmd): 13 | """Basic example of how to run cmd2 without it controlling the main loop.""" 14 | 15 | def __init__(self) -> None: 16 | super().__init__() 17 | 18 | # ... your class code here ... 19 | 20 | 21 | if __name__ == '__main__': 22 | app = Cmd2EventBased() 23 | app.preloop() 24 | 25 | # Do this within whatever event loop mechanism you wish to run a single command. 26 | # In this case, no prompt is generated, so you need to provide one and read the user's input. 27 | app.onecmd_plus_hooks("help history") 28 | 29 | app.postloop() 30 | -------------------------------------------------------------------------------- /examples/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A sample application for cmd2. 3 | 4 | Thanks to cmd2's built-in transcript testing capability, it also serves as a 5 | test suite for example.py when used with the transcript_regex.txt transcript. 6 | 7 | Running `python example.py -t transcript_regex.txt` will run all the commands in 8 | the transcript against example.py, verifying that the output produced matches 9 | the transcript. 10 | """ 11 | 12 | import random 13 | 14 | import cmd2 15 | 16 | 17 | class CmdLineApp(cmd2.Cmd): 18 | """Example cmd2 application.""" 19 | 20 | # Setting this true makes it run a shell command if a cmd2/cmd command doesn't exist 21 | # default_to_shell = True # noqa: ERA001 22 | MUMBLES = ('like', '...', 'um', 'er', 'hmmm', 'ahh') 23 | MUMBLE_FIRST = ('so', 'like', 'well') 24 | MUMBLE_LAST = ('right?',) 25 | 26 | def __init__(self) -> None: 27 | shortcuts = cmd2.DEFAULT_SHORTCUTS 28 | shortcuts.update({'&': 'speak'}) 29 | super().__init__(multiline_commands=['orate'], shortcuts=shortcuts) 30 | 31 | # Make maxrepeats settable at runtime 32 | self.maxrepeats = 3 33 | self.add_settable(cmd2.Settable('maxrepeats', int, 'max repetitions for speak command', self)) 34 | 35 | speak_parser = cmd2.Cmd2ArgumentParser() 36 | speak_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') 37 | speak_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE') 38 | speak_parser.add_argument('-r', '--repeat', type=int, help='output [n] times') 39 | speak_parser.add_argument('words', nargs='+', help='words to say') 40 | 41 | @cmd2.with_argparser(speak_parser) 42 | def do_speak(self, args) -> None: 43 | """Repeats what you tell me to.""" 44 | words = [] 45 | for word in args.words: 46 | if args.piglatin: 47 | word = f'{word[1:]}{word[0]}ay' 48 | if args.shout: 49 | word = word.upper() 50 | words.append(word) 51 | repetitions = args.repeat or 1 52 | for _ in range(min(repetitions, self.maxrepeats)): 53 | # .poutput handles newlines, and accommodates output redirection too 54 | self.poutput(' '.join(words)) 55 | 56 | do_say = do_speak # now "say" is a synonym for "speak" 57 | do_orate = do_speak # another synonym, but this one takes multi-line input 58 | 59 | mumble_parser = cmd2.Cmd2ArgumentParser() 60 | mumble_parser.add_argument('-r', '--repeat', type=int, help='how many times to repeat') 61 | mumble_parser.add_argument('words', nargs='+', help='words to say') 62 | 63 | @cmd2.with_argparser(mumble_parser) 64 | def do_mumble(self, args) -> None: 65 | """Mumbles what you tell me to.""" 66 | repetitions = args.repeat or 1 67 | for _ in range(min(repetitions, self.maxrepeats)): 68 | output = [] 69 | if random.random() < 0.33: 70 | output.append(random.choice(self.MUMBLE_FIRST)) 71 | for word in args.words: 72 | if random.random() < 0.40: 73 | output.append(random.choice(self.MUMBLES)) 74 | output.append(word) 75 | if random.random() < 0.25: 76 | output.append(random.choice(self.MUMBLE_LAST)) 77 | self.poutput(' '.join(output)) 78 | 79 | 80 | if __name__ == '__main__': 81 | import sys 82 | 83 | c = CmdLineApp() 84 | sys.exit(c.cmdloop()) 85 | -------------------------------------------------------------------------------- /examples/exit_code.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A simple example demonstrating the following how to emit a non-zero exit code in your cmd2 application.""" 3 | 4 | import cmd2 5 | 6 | 7 | class ReplWithExitCode(cmd2.Cmd): 8 | """Example cmd2 application where we can specify an exit code when existing.""" 9 | 10 | def __init__(self) -> None: 11 | super().__init__() 12 | 13 | @cmd2.with_argument_list 14 | def do_exit(self, arg_list: list[str]) -> bool: 15 | """Exit the application with an optional exit code. 16 | 17 | Usage: exit [exit_code] 18 | Where: 19 | * exit_code - integer exit code to return to the shell 20 | """ 21 | # If an argument was provided 22 | if arg_list: 23 | try: 24 | self.exit_code = int(arg_list[0]) 25 | except ValueError: 26 | self.perror(f"{arg_list[0]} isn't a valid integer exit code") 27 | self.exit_code = 1 28 | 29 | return True 30 | 31 | 32 | if __name__ == '__main__': 33 | import sys 34 | 35 | app = ReplWithExitCode() 36 | sys_exit_code = app.cmdloop() 37 | app.poutput(f'{sys.argv[0]!r} exiting with code: {sys_exit_code}') 38 | sys.exit(sys_exit_code) 39 | -------------------------------------------------------------------------------- /examples/first_app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A simple application using cmd2 which demonstrates 8 key features: 3 | 4 | * Settings 5 | * Commands 6 | * Argument Parsing 7 | * Generating Output 8 | * Help 9 | * Shortcuts 10 | * Multiline Commands 11 | * History 12 | """ 13 | 14 | import cmd2 15 | 16 | 17 | class FirstApp(cmd2.Cmd): 18 | """A simple cmd2 application.""" 19 | 20 | def __init__(self) -> None: 21 | shortcuts = cmd2.DEFAULT_SHORTCUTS 22 | shortcuts.update({'&': 'speak'}) 23 | super().__init__(multiline_commands=['orate'], shortcuts=shortcuts) 24 | 25 | # Make maxrepeats settable at runtime 26 | self.maxrepeats = 3 27 | self.add_settable(cmd2.Settable('maxrepeats', int, 'max repetitions for speak command', self)) 28 | 29 | speak_parser = cmd2.Cmd2ArgumentParser() 30 | speak_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') 31 | speak_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE') 32 | speak_parser.add_argument('-r', '--repeat', type=int, help='output [n] times') 33 | speak_parser.add_argument('words', nargs='+', help='words to say') 34 | 35 | @cmd2.with_argparser(speak_parser) 36 | def do_speak(self, args) -> None: 37 | """Repeats what you tell me to.""" 38 | words = [] 39 | for word in args.words: 40 | if args.piglatin: 41 | word = f'{word[1:]}{word[0]}ay' 42 | if args.shout: 43 | word = word.upper() 44 | words.append(word) 45 | repetitions = args.repeat or 1 46 | for _ in range(min(repetitions, self.maxrepeats)): 47 | # .poutput handles newlines, and accommodates output redirection too 48 | self.poutput(' '.join(words)) 49 | 50 | # orate is a synonym for speak which takes multiline input 51 | do_orate = do_speak 52 | 53 | 54 | if __name__ == '__main__': 55 | import sys 56 | 57 | c = FirstApp() 58 | sys.exit(c.cmdloop()) 59 | -------------------------------------------------------------------------------- /examples/hello_cmd2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """This is intended to be a completely bare-bones cmd2 application suitable for rapid testing and debugging.""" 3 | 4 | from cmd2 import ( 5 | cmd2, 6 | ) 7 | 8 | if __name__ == '__main__': 9 | import sys 10 | 11 | # If run as the main application, simply start a bare-bones cmd2 application with only built-in functionality. 12 | # Enable commands to support interactive Python and IPython shells. 13 | app = cmd2.Cmd(include_py=True, include_ipy=True, persistent_history_file='cmd2_history.dat') 14 | app.self_in_py = True # Enable access to "self" within the py command 15 | app.debug = True # Show traceback if/when an exception occurs 16 | sys.exit(app.cmdloop()) 17 | -------------------------------------------------------------------------------- /examples/initialization.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """A simple example cmd2 application demonstrating the following: 3 | 1) Colorizing/stylizing output 4 | 2) Using multiline commands 5 | 3) Persistent history 6 | 4) How to run an initialization script at startup 7 | 5) How to group and categorize commands when displaying them in help 8 | 6) Opting-in to using the ipy command to run an IPython shell 9 | 7) Allowing access to your application in py and ipy 10 | 8) Displaying an intro banner upon starting your application 11 | 9) Using a custom prompt 12 | 10) How to make custom attributes settable at runtime. 13 | """ 14 | 15 | import cmd2 16 | from cmd2 import ( 17 | Bg, 18 | Fg, 19 | style, 20 | ) 21 | 22 | 23 | class BasicApp(cmd2.Cmd): 24 | CUSTOM_CATEGORY = 'My Custom Commands' 25 | 26 | def __init__(self) -> None: 27 | super().__init__( 28 | multiline_commands=['echo'], 29 | persistent_history_file='cmd2_history.dat', 30 | startup_script='scripts/startup.txt', 31 | include_ipy=True, 32 | ) 33 | 34 | # Prints an intro banner once upon application startup 35 | self.intro = style('Welcome to cmd2!', fg=Fg.RED, bg=Bg.WHITE, bold=True) 36 | 37 | # Show this as the prompt when asking for input 38 | self.prompt = 'myapp> ' 39 | 40 | # Used as prompt for multiline commands after the first line 41 | self.continuation_prompt = '... ' 42 | 43 | # Allow access to your application in py and ipy via self 44 | self.self_in_py = True 45 | 46 | # Set the default category name 47 | self.default_category = 'cmd2 Built-in Commands' 48 | 49 | # Color to output text in with echo command 50 | self.foreground_color = Fg.CYAN.name.lower() 51 | 52 | # Make echo_fg settable at runtime 53 | fg_colors = [c.name.lower() for c in Fg] 54 | self.add_settable( 55 | cmd2.Settable('foreground_color', str, 'Foreground color to use with echo command', self, choices=fg_colors) 56 | ) 57 | 58 | @cmd2.with_category(CUSTOM_CATEGORY) 59 | def do_intro(self, _) -> None: 60 | """Display the intro banner.""" 61 | self.poutput(self.intro) 62 | 63 | @cmd2.with_category(CUSTOM_CATEGORY) 64 | def do_echo(self, arg) -> None: 65 | """Example of a multiline command.""" 66 | fg_color = Fg[self.foreground_color.upper()] 67 | self.poutput(style(arg, fg=fg_color)) 68 | 69 | 70 | if __name__ == '__main__': 71 | app = BasicApp() 72 | app.cmdloop() 73 | -------------------------------------------------------------------------------- /examples/migrating.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A sample cmd application that shows how to trivially migrate a cmd application to use cmd2.""" 3 | 4 | # import cmd2 as cmd # noqa: ERA001 5 | import cmd # Comment this line and uncomment the one above to migrate to cmd2 6 | import random 7 | 8 | 9 | class CmdLineApp(cmd.Cmd): 10 | """Example cmd application.""" 11 | 12 | MUMBLES = ('like', '...', 'um', 'er', 'hmmm', 'ahh') 13 | MUMBLE_FIRST = ('so', 'like', 'well') 14 | MUMBLE_LAST = ('right?',) 15 | 16 | def do_exit(self, _line) -> bool: 17 | """Exit the application.""" 18 | return True 19 | 20 | do_EOF = do_exit # noqa: N815 21 | do_quit = do_exit 22 | 23 | def do_speak(self, line) -> None: 24 | """Repeats what you tell me to.""" 25 | print(line, file=self.stdout) 26 | 27 | do_say = do_speak 28 | 29 | def do_mumble(self, line) -> None: 30 | """Mumbles what you tell me to.""" 31 | words = line.split(' ') 32 | output = [] 33 | if random.random() < 0.33: 34 | output.append(random.choice(self.MUMBLE_FIRST)) 35 | for word in words: 36 | if random.random() < 0.40: 37 | output.append(random.choice(self.MUMBLES)) 38 | output.append(word) 39 | if random.random() < 0.25: 40 | output.append(random.choice(self.MUMBLE_LAST)) 41 | print(' '.join(output), file=self.stdout) 42 | 43 | 44 | if __name__ == '__main__': 45 | import sys 46 | 47 | c = CmdLineApp() 48 | sys.exit(c.cmdloop()) 49 | -------------------------------------------------------------------------------- /examples/modular_commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cmd2/cmd2/28f226acbea4c1043449b0de72ca2c8bdebf3a56/examples/modular_commands/__init__.py -------------------------------------------------------------------------------- /examples/modular_commands/commandset_complex.py: -------------------------------------------------------------------------------- 1 | """Test CommandSet.""" 2 | 3 | import argparse 4 | 5 | import cmd2 6 | 7 | 8 | @cmd2.with_default_category('Fruits') 9 | class CommandSetA(cmd2.CommandSet): 10 | def do_apple(self, _statement: cmd2.Statement) -> None: 11 | self._cmd.poutput('Apple!') 12 | 13 | def do_banana(self, _statement: cmd2.Statement) -> None: 14 | """Banana Command.""" 15 | self._cmd.poutput('Banana!!') 16 | 17 | cranberry_parser = cmd2.Cmd2ArgumentParser() 18 | cranberry_parser.add_argument('arg1', choices=['lemonade', 'juice', 'sauce']) 19 | 20 | @cmd2.with_argparser(cranberry_parser, with_unknown_args=True) 21 | def do_cranberry(self, ns: argparse.Namespace, unknown: list[str]) -> None: 22 | self._cmd.poutput(f'Cranberry {ns.arg1}!!') 23 | if unknown and len(unknown): 24 | self._cmd.poutput('Unknown: ' + ', '.join(['{}'] * len(unknown)).format(*unknown)) 25 | self._cmd.last_result = {'arg1': ns.arg1, 'unknown': unknown} 26 | 27 | def help_cranberry(self) -> None: 28 | self._cmd.stdout.write('This command does diddly squat...\n') 29 | 30 | @cmd2.with_argument_list 31 | @cmd2.with_category('Also Alone') 32 | def do_durian(self, args: list[str]) -> None: 33 | """Durian Command.""" 34 | self._cmd.poutput(f'{len(args)} Arguments: ') 35 | self._cmd.poutput(', '.join(['{}'] * len(args)).format(*args)) 36 | 37 | def complete_durian(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: 38 | return self._cmd.basic_complete(text, line, begidx, endidx, ['stinks', 'smells', 'disgusting']) 39 | 40 | elderberry_parser = cmd2.Cmd2ArgumentParser() 41 | elderberry_parser.add_argument('arg1') 42 | 43 | @cmd2.with_category('Alone') 44 | @cmd2.with_argparser(elderberry_parser) 45 | def do_elderberry(self, ns: argparse.Namespace) -> None: 46 | self._cmd.poutput(f'Elderberry {ns.arg1}!!') 47 | -------------------------------------------------------------------------------- /examples/modular_commands/commandset_custominit.py: -------------------------------------------------------------------------------- 1 | """A simple example demonstrating a loadable command set.""" 2 | 3 | from cmd2 import ( 4 | Cmd, 5 | CommandSet, 6 | Statement, 7 | with_default_category, 8 | ) 9 | 10 | 11 | @with_default_category('Custom Init') 12 | class CustomInitCommandSet(CommandSet): 13 | def __init__(self, arg1, arg2) -> None: 14 | super().__init__() 15 | 16 | self._arg1 = arg1 17 | self._arg2 = arg2 18 | 19 | def do_show_arg1(self, _cmd: Cmd, _: Statement) -> None: 20 | self._cmd.poutput('Arg1: ' + self._arg1) 21 | 22 | def do_show_arg2(self, _cmd: Cmd, _: Statement) -> None: 23 | self._cmd.poutput('Arg2: ' + self._arg2) 24 | -------------------------------------------------------------------------------- /examples/modular_commands_basic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Simple example demonstrating basic CommandSet usage.""" 3 | 4 | import cmd2 5 | from cmd2 import ( 6 | CommandSet, 7 | with_default_category, 8 | ) 9 | 10 | 11 | @with_default_category('My Category') 12 | class AutoLoadCommandSet(CommandSet): 13 | def __init__(self) -> None: 14 | super().__init__() 15 | 16 | def do_hello(self, _: cmd2.Statement) -> None: 17 | self._cmd.poutput('Hello') 18 | 19 | def do_world(self, _: cmd2.Statement) -> None: 20 | self._cmd.poutput('World') 21 | 22 | 23 | class ExampleApp(cmd2.Cmd): 24 | """CommandSets are automatically loaded. Nothing needs to be done.""" 25 | 26 | def __init__(self) -> None: 27 | super().__init__() 28 | 29 | def do_something(self, _arg) -> None: 30 | self.poutput('this is the something command') 31 | 32 | 33 | if __name__ == '__main__': 34 | app = ExampleApp() 35 | app.cmdloop() 36 | -------------------------------------------------------------------------------- /examples/modular_commands_dynamic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Simple example demonstrating dynamic CommandSet loading and unloading. 3 | 4 | There are 2 CommandSets defined. ExampleApp sets the `auto_load_commands` flag to false. 5 | 6 | The `load` and `unload` commands will load and unload the CommandSets. The available commands will change depending 7 | on which CommandSets are loaded 8 | """ 9 | 10 | import argparse 11 | 12 | import cmd2 13 | from cmd2 import ( 14 | CommandSet, 15 | with_argparser, 16 | with_category, 17 | with_default_category, 18 | ) 19 | 20 | 21 | @with_default_category('Fruits') 22 | class LoadableFruits(CommandSet): 23 | def __init__(self) -> None: 24 | super().__init__() 25 | 26 | def do_apple(self, _: cmd2.Statement) -> None: 27 | self._cmd.poutput('Apple') 28 | 29 | def do_banana(self, _: cmd2.Statement) -> None: 30 | self._cmd.poutput('Banana') 31 | 32 | 33 | @with_default_category('Vegetables') 34 | class LoadableVegetables(CommandSet): 35 | def __init__(self) -> None: 36 | super().__init__() 37 | 38 | def do_arugula(self, _: cmd2.Statement) -> None: 39 | self._cmd.poutput('Arugula') 40 | 41 | def do_bokchoy(self, _: cmd2.Statement) -> None: 42 | self._cmd.poutput('Bok Choy') 43 | 44 | 45 | class ExampleApp(cmd2.Cmd): 46 | """CommandSets are loaded via the `load` and `unload` commands.""" 47 | 48 | def __init__(self, *args, **kwargs) -> None: 49 | # gotta have this or neither the plugin or cmd2 will initialize 50 | super().__init__(*args, auto_load_commands=False, **kwargs) 51 | 52 | self._fruits = LoadableFruits() 53 | self._vegetables = LoadableVegetables() 54 | 55 | load_parser = cmd2.Cmd2ArgumentParser() 56 | load_parser.add_argument('cmds', choices=['fruits', 'vegetables']) 57 | 58 | @with_argparser(load_parser) 59 | @with_category('Command Loading') 60 | def do_load(self, ns: argparse.Namespace) -> None: 61 | if ns.cmds == 'fruits': 62 | try: 63 | self.register_command_set(self._fruits) 64 | self.poutput('Fruits loaded') 65 | except ValueError: 66 | self.poutput('Fruits already loaded') 67 | 68 | if ns.cmds == 'vegetables': 69 | try: 70 | self.register_command_set(self._vegetables) 71 | self.poutput('Vegetables loaded') 72 | except ValueError: 73 | self.poutput('Vegetables already loaded') 74 | 75 | @with_argparser(load_parser) 76 | def do_unload(self, ns: argparse.Namespace) -> None: 77 | if ns.cmds == 'fruits': 78 | self.unregister_command_set(self._fruits) 79 | self.poutput('Fruits unloaded') 80 | 81 | if ns.cmds == 'vegetables': 82 | self.unregister_command_set(self._vegetables) 83 | self.poutput('Vegetables unloaded') 84 | 85 | 86 | if __name__ == '__main__': 87 | app = ExampleApp() 88 | app.cmdloop() 89 | -------------------------------------------------------------------------------- /examples/modular_commands_main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A complex example demonstrating a variety of methods to load CommandSets using a mix of command decorators 3 | with examples of how to integrate tab completion with argparse-based commands. 4 | """ 5 | 6 | import argparse 7 | from collections.abc import Iterable 8 | from typing import Optional 9 | 10 | from modular_commands.commandset_basic import ( # noqa: F401 11 | BasicCompletionCommandSet, 12 | ) 13 | from modular_commands.commandset_complex import ( # noqa: F401 14 | CommandSetA, 15 | ) 16 | from modular_commands.commandset_custominit import ( 17 | CustomInitCommandSet, 18 | ) 19 | 20 | from cmd2 import ( 21 | Cmd, 22 | Cmd2ArgumentParser, 23 | CommandSet, 24 | with_argparser, 25 | ) 26 | 27 | 28 | class WithCommandSets(Cmd): 29 | def __init__(self, command_sets: Optional[Iterable[CommandSet]] = None) -> None: 30 | super().__init__(command_sets=command_sets) 31 | self.sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball'] 32 | 33 | def choices_provider(self) -> list[str]: 34 | """A choices provider is useful when the choice list is based on instance data of your application.""" 35 | return self.sport_item_strs 36 | 37 | # Parser for example command 38 | example_parser = Cmd2ArgumentParser( 39 | description="Command demonstrating tab completion with argparse\nNotice even the flags of this command tab complete" 40 | ) 41 | 42 | # Tab complete from a list using argparse choices. Set metavar if you don't 43 | # want the entire choices list showing in the usage text for this command. 44 | example_parser.add_argument( 45 | '--choices', choices=['some', 'choices', 'here'], metavar="CHOICE", help="tab complete using choices" 46 | ) 47 | 48 | # Tab complete from choices provided by a choices provider 49 | example_parser.add_argument( 50 | '--choices_provider', choices_provider=choices_provider, help="tab complete using a choices_provider" 51 | ) 52 | 53 | # Tab complete using a completer 54 | example_parser.add_argument('--completer', completer=Cmd.path_complete, help="tab complete using a completer") 55 | 56 | @with_argparser(example_parser) 57 | def do_example(self, _: argparse.Namespace) -> None: 58 | """The example command.""" 59 | self.poutput("I do nothing") 60 | 61 | 62 | if __name__ == '__main__': 63 | import sys 64 | 65 | print("Starting") 66 | my_sets = [CustomInitCommandSet('First argument', 'Second argument')] 67 | app = WithCommandSets(command_sets=my_sets) 68 | sys.exit(app.cmdloop()) 69 | -------------------------------------------------------------------------------- /examples/override_parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """The standard parser used by cmd2 built-in commands is Cmd2ArgumentParser. 3 | The following code shows how to override it with your own parser class. 4 | """ 5 | 6 | # First set a value called argparse.cmd2_parser_module with the module that defines the custom parser. 7 | # See the code for custom_parser.py. It simply defines a parser and calls cmd2.set_default_argument_parser_type() 8 | # with the custom parser's type. 9 | import argparse 10 | 11 | argparse.cmd2_parser_module = 'custom_parser' 12 | 13 | # Next import from cmd2. It will import your module just before the cmd2.Cmd class file is imported 14 | # and therefore override the parser class it uses on its commands. 15 | from cmd2 import cmd2 # noqa: E402 16 | 17 | if __name__ == '__main__': 18 | import sys 19 | 20 | app = cmd2.Cmd(include_ipy=True, persistent_history_file='cmd2_history.dat') 21 | app.self_in_py = True # Enable access to "self" within the py command 22 | app.debug = True # Show traceback if/when an exception occurs 23 | sys.exit(app.cmdloop()) 24 | -------------------------------------------------------------------------------- /examples/paged_output.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A simple example demonstrating the using paged output via the ppaged() method.""" 3 | 4 | import os 5 | 6 | import cmd2 7 | 8 | 9 | class PagedOutput(cmd2.Cmd): 10 | """Example cmd2 application which shows how to display output using a pager.""" 11 | 12 | def __init__(self) -> None: 13 | super().__init__() 14 | 15 | def page_file(self, file_path: str, chop: bool = False) -> None: 16 | """Helper method to prevent having too much duplicated code.""" 17 | filename = os.path.expanduser(file_path) 18 | try: 19 | with open(filename) as f: 20 | text = f.read() 21 | self.ppaged(text, chop=chop) 22 | except OSError as ex: 23 | self.pexcept(f'Error reading {filename!r}: {ex}') 24 | 25 | @cmd2.with_argument_list 26 | def do_page_wrap(self, args: list[str]) -> None: 27 | """Read in a text file and display its output in a pager, wrapping long lines if they don't fit. 28 | 29 | Usage: page_wrap 30 | """ 31 | if not args: 32 | self.perror('page_wrap requires a path to a file as an argument') 33 | return 34 | self.page_file(args[0], chop=False) 35 | 36 | complete_page_wrap = cmd2.Cmd.path_complete 37 | 38 | @cmd2.with_argument_list 39 | def do_page_truncate(self, args: list[str]) -> None: 40 | """Read in a text file and display its output in a pager, truncating long lines if they don't fit. 41 | 42 | Truncated lines can still be accessed by scrolling to the right using the arrow keys. 43 | 44 | Usage: page_chop 45 | """ 46 | if not args: 47 | self.perror('page_truncate requires a path to a file as an argument') 48 | return 49 | self.page_file(args[0], chop=True) 50 | 51 | complete_page_truncate = cmd2.Cmd.path_complete 52 | 53 | 54 | if __name__ == '__main__': 55 | import sys 56 | 57 | app = PagedOutput() 58 | sys.exit(app.cmdloop()) 59 | -------------------------------------------------------------------------------- /examples/persistent_history.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """This example demonstrates how to enable persistent readline history in your cmd2 application. 3 | 4 | This will allow end users of your cmd2-based application to use the arrow keys and Ctrl+r in a manner which persists 5 | across invocations of your cmd2 application. This can make it much easier for them to use your application. 6 | """ 7 | 8 | import cmd2 9 | 10 | 11 | class Cmd2PersistentHistory(cmd2.Cmd): 12 | """Basic example of how to enable persistent readline history within your cmd2 app.""" 13 | 14 | def __init__(self, hist_file) -> None: 15 | """Configure the app to load persistent history from a file (both readline and cmd2 history command affected). 16 | 17 | :param hist_file: file to load history from at start and write it to at end 18 | """ 19 | super().__init__(persistent_history_file=hist_file, persistent_history_length=500, allow_cli_args=False) 20 | self.prompt = 'ph> ' 21 | 22 | # ... your class code here ... 23 | 24 | 25 | if __name__ == '__main__': 26 | import sys 27 | 28 | history_file = '~/.persistent_history.cmd2' 29 | if len(sys.argv) > 1: 30 | history_file = sys.argv[1] 31 | 32 | app = Cmd2PersistentHistory(hist_file=history_file) 33 | sys.exit(app.cmdloop()) 34 | -------------------------------------------------------------------------------- /examples/pirate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """This example is adapted from the pirate8.py example created by Catherine Devlin and 3 | presented as part of her PyCon 2010 talk. 4 | 5 | It demonstrates many features of cmd2. 6 | """ 7 | 8 | import cmd2 9 | from cmd2 import ( 10 | Fg, 11 | ) 12 | from cmd2.constants import ( 13 | MULTILINE_TERMINATOR, 14 | ) 15 | 16 | color_choices = [c.name.lower() for c in Fg] 17 | 18 | 19 | class Pirate(cmd2.Cmd): 20 | """A piratical example cmd2 application involving looting and drinking.""" 21 | 22 | def __init__(self) -> None: 23 | """Initialize the base class as well as this one.""" 24 | shortcuts = dict(cmd2.DEFAULT_SHORTCUTS) 25 | shortcuts.update({'~': 'sing'}) 26 | super().__init__(multiline_commands=['sing'], terminators=[MULTILINE_TERMINATOR, '...'], shortcuts=shortcuts) 27 | 28 | self.default_to_shell = True 29 | self.songcolor = 'blue' 30 | 31 | # Make songcolor settable at runtime 32 | self.add_settable(cmd2.Settable('songcolor', str, 'Color to ``sing``', self, choices=color_choices)) 33 | 34 | # prompts and defaults 35 | self.gold = 0 36 | self.initial_gold = self.gold 37 | self.prompt = 'arrr> ' 38 | 39 | def precmd(self, line): 40 | """Runs just before a command line is parsed, but after the prompt is presented.""" 41 | self.initial_gold = self.gold 42 | return line 43 | 44 | def postcmd(self, stop, _line): 45 | """Runs right before a command is about to return.""" 46 | if self.gold != self.initial_gold: 47 | self.poutput(f'Now we gots {self.gold} doubloons') 48 | if self.gold < 0: 49 | self.poutput("Off to debtorrr's prison.") 50 | self.exit_code = 1 51 | stop = True 52 | return stop 53 | 54 | def do_loot(self, _arg) -> None: 55 | """Seize booty from a passing ship.""" 56 | self.gold += 1 57 | 58 | def do_drink(self, arg) -> None: 59 | """Drown your sorrrows in rrrum. 60 | 61 | drink [n] - drink [n] barrel[s] o' rum. 62 | """ 63 | try: 64 | self.gold -= int(arg) 65 | except ValueError: 66 | if arg: 67 | self.poutput(f'''What's "{arg}"? I'll take rrrum.''') 68 | self.gold -= 1 69 | 70 | def do_quit(self, _arg) -> bool: 71 | """Quit the application gracefully.""" 72 | self.poutput("Quiterrr!") 73 | return True 74 | 75 | def do_sing(self, arg) -> None: 76 | """Sing a colorful song.""" 77 | self.poutput(cmd2.ansi.style(arg, fg=Fg[self.songcolor.upper()])) 78 | 79 | yo_parser = cmd2.Cmd2ArgumentParser() 80 | yo_parser.add_argument('--ho', type=int, default=2, help="How often to chant 'ho'") 81 | yo_parser.add_argument('-c', '--commas', action='store_true', help='Intersperse commas') 82 | yo_parser.add_argument('beverage', help='beverage to drink with the chant') 83 | 84 | @cmd2.with_argparser(yo_parser) 85 | def do_yo(self, args) -> None: 86 | """Compose a yo-ho-ho type chant with flexible options.""" 87 | chant = ['yo'] + ['ho'] * args.ho 88 | separator = ', ' if args.commas else ' ' 89 | chant = separator.join(chant) 90 | self.poutput(f'{chant} and a bottle of {args.beverage}') 91 | 92 | 93 | if __name__ == '__main__': 94 | import sys 95 | 96 | # Create an instance of the Pirate derived class and enter the REPL with cmdloop(). 97 | pirate = Pirate() 98 | sys_exit_code = pirate.cmdloop() 99 | print(f'Exiting with code: {sys_exit_code!r}') 100 | sys.exit(sys_exit_code) 101 | -------------------------------------------------------------------------------- /examples/pretty_print.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """A simple example demonstrating use of cmd2.Cmd.ppretty().""" 3 | 4 | import cmd2 5 | 6 | data = { 7 | "name": "John Doe", 8 | "age": 30, 9 | "address": {"street": "123 Main St", "city": "Anytown", "state": "CA"}, 10 | "hobbies": ["reading", "hiking", "coding"], 11 | } 12 | 13 | 14 | class Cmd2App(cmd2.Cmd): 15 | def __init__(self) -> None: 16 | super().__init__() 17 | 18 | def do_normal(self, _) -> None: 19 | """Display the data using the normal poutput method.""" 20 | self.poutput(data) 21 | 22 | def do_pretty(self, _) -> None: 23 | """Display the data using the ppretty method.""" 24 | self.ppretty(data) 25 | 26 | 27 | if __name__ == '__main__': 28 | app = Cmd2App() 29 | app.cmdloop() 30 | -------------------------------------------------------------------------------- /examples/remove_builtin_commands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A simple example demonstrating how to remove unused commands. 3 | 4 | Commands can be removed from help menu and tab completion by appending their command name to the hidden_commands list. 5 | These commands will still exist and can be executed and help can be retrieved for them by 6 | name, they just won't clutter the help menu. 7 | 8 | Commands can also be removed entirely by using Python's "del". 9 | """ 10 | 11 | import cmd2 12 | 13 | 14 | class RemoveBuiltinCommands(cmd2.Cmd): 15 | """Example cmd2 application where we remove some unused built-in commands.""" 16 | 17 | def __init__(self) -> None: 18 | super().__init__() 19 | 20 | # To hide commands from displaying in the help menu, add them to the hidden_commands list 21 | self.hidden_commands.append('py') 22 | 23 | # To remove built-in commands entirely, delete their "do_*" function from the cmd2.Cmd class 24 | del cmd2.Cmd.do_edit 25 | 26 | 27 | if __name__ == '__main__': 28 | import sys 29 | 30 | app = RemoveBuiltinCommands() 31 | sys.exit(app.cmdloop()) 32 | -------------------------------------------------------------------------------- /examples/remove_settable.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A sample application for cmd2 demonstrating how to remove one of the built-in runtime settable parameters.""" 3 | 4 | import cmd2 5 | 6 | 7 | class MyApp(cmd2.Cmd): 8 | def __init__(self) -> None: 9 | super().__init__() 10 | self.remove_settable('debug') 11 | 12 | 13 | if __name__ == '__main__': 14 | import sys 15 | 16 | c = MyApp() 17 | sys.exit(c.cmdloop()) 18 | -------------------------------------------------------------------------------- /examples/scripts/arg_printer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | print(f"Running Python script {os.path.basename(sys.argv[0])!r} which was called with {len(sys.argv) - 1} arguments") 6 | for i, arg in enumerate(sys.argv[1:]): 7 | print(f"arg {i + 1}: {arg!r}") 8 | -------------------------------------------------------------------------------- /examples/scripts/conditional.py: -------------------------------------------------------------------------------- 1 | """This is a Python script intended to be used with the "python_scripting.py" cmd2 example application. 2 | 3 | To run it you should do the following: 4 | ./python_scripting.py 5 | run_pyscript scripts/conditional.py directory_path 6 | 7 | Note: The "app" function is defined within the cmd2 embedded Python environment and in there "self" is your cmd2 8 | application instance. Note: self only exists in this environment if self_in_py is True. 9 | """ 10 | 11 | import os 12 | import sys 13 | 14 | if len(sys.argv) > 1: 15 | directory = sys.argv[1] 16 | print(f'Using specified directory: {directory!r}') 17 | else: 18 | directory = 'foobar' 19 | print(f'Using default directory: {directory!r}') 20 | 21 | # Keep track of where we stared 22 | original_dir = os.getcwd() 23 | 24 | # Try to change to the specified directory 25 | result = app(f'cd {directory}') 26 | 27 | # Conditionally do something based on the results of the last command 28 | if result: 29 | print(f"STDOUT: {result.stdout}\n") 30 | print(f"STDERR: {result.stderr}\n") 31 | 32 | print(f'\nContents of directory {directory!r}:') 33 | result = app('dir -l') 34 | 35 | print(f"STDOUT: {result.stdout}\n") 36 | print(f"STDERR: {result.stderr}\n") 37 | 38 | print(f'{result.data}\n') 39 | 40 | # Change back to where we were 41 | print(f'Changing back to original directory: {original_dir!r}') 42 | app(f'cd {original_dir}') 43 | else: 44 | # cd command failed, print a warning 45 | print(f'Failed to change directory to {directory!r}') 46 | 47 | print(f"STDOUT: {result.stdout}\n") 48 | print(f"STDERR: {result.stderr}\n") 49 | -------------------------------------------------------------------------------- /examples/scripts/nested.txt: -------------------------------------------------------------------------------- 1 | !echo "Doing a relative run script" 2 | _relative_run_script script.txt 3 | -------------------------------------------------------------------------------- /examples/scripts/quit.txt: -------------------------------------------------------------------------------- 1 | quit 2 | -------------------------------------------------------------------------------- /examples/scripts/save_help_text.py: -------------------------------------------------------------------------------- 1 | """A cmd2 script that saves the help text for every command, subcommand, and topic to a file. 2 | This is meant to be run within a cmd2 session using run_pyscript. 3 | """ 4 | 5 | import argparse 6 | import os 7 | import sys 8 | from typing import TextIO 9 | 10 | ASTERISKS = "********************************************************" 11 | 12 | 13 | def get_sub_commands(parser: argparse.ArgumentParser) -> list[str]: 14 | """Get a list of subcommands for an ArgumentParser.""" 15 | sub_cmds = [] 16 | 17 | # Check if this is parser has subcommands 18 | if parser is not None and parser._subparsers is not None: 19 | # Find the _SubParsersAction for the subcommands of this parser 20 | for action in parser._subparsers._actions: 21 | if isinstance(action, argparse._SubParsersAction): 22 | for sub_cmd, sub_cmd_parser in action.choices.items(): 23 | sub_cmds.append(sub_cmd) 24 | 25 | # Look for nested subcommands 26 | sub_cmds.extend(f'{sub_cmd} {nested_sub_cmd}' for nested_sub_cmd in get_sub_commands(sub_cmd_parser)) 27 | break 28 | 29 | sub_cmds.sort() 30 | return sub_cmds 31 | 32 | 33 | def add_help_to_file(item: str, outfile: TextIO, is_command: bool) -> None: 34 | """Write help text for commands and topics to the output file 35 | :param item: what is having its help text saved 36 | :param outfile: file being written to 37 | :param is_command: tells if the item is a command and not just a help topic. 38 | """ 39 | label = "COMMAND" if is_command else "TOPIC" 40 | 41 | header = f'{ASTERISKS}\n{label}: {item}\n{ASTERISKS}\n' 42 | outfile.write(header) 43 | 44 | result = app(f'help {item}') 45 | outfile.write(result.stdout) 46 | 47 | 48 | def main() -> None: 49 | """Main function of this script.""" 50 | # Make sure we have access to self 51 | if 'self' not in globals(): 52 | print("Re-run this script from a cmd2 application where self_in_py is True") 53 | return 54 | 55 | # Make sure the user passed in an output file 56 | if len(sys.argv) != 2: 57 | print(f"Usage: {os.path.basename(sys.argv[0])} ") 58 | return 59 | 60 | # Open the output file 61 | outfile_path = os.path.expanduser(sys.argv[1]) 62 | try: 63 | with open(outfile_path, 'w') as outfile: 64 | pass 65 | except OSError as e: 66 | print(f"Error opening {outfile_path} because: {e}") 67 | return 68 | 69 | # Write the help summary 70 | header = f'{ASTERISKS}\nSUMMARY\n{ASTERISKS}\n' 71 | outfile.write(header) 72 | 73 | result = app('help -v') 74 | outfile.write(result.stdout) 75 | 76 | # Get a list of all commands and help topics and then filter out duplicates 77 | all_commands = set(self.get_all_commands()) 78 | all_topics = set(self.get_help_topics()) 79 | to_save = list(all_commands | all_topics) 80 | to_save.sort() 81 | 82 | for item in to_save: 83 | is_command = item in all_commands 84 | add_help_to_file(item, outfile, is_command) 85 | 86 | if is_command: 87 | # Add any subcommands 88 | for subcmd in get_sub_commands(getattr(self.cmd_func(item), 'argparser', None)): 89 | full_cmd = f'{item} {subcmd}' 90 | add_help_to_file(full_cmd, outfile, is_command) 91 | 92 | outfile.close() 93 | print(f"Output written to {outfile_path}") 94 | 95 | 96 | # Run main function 97 | main() 98 | -------------------------------------------------------------------------------- /examples/scripts/script.py: -------------------------------------------------------------------------------- 1 | """Trivial example of a Python script which can be run inside a cmd2 application.""" 2 | 3 | print("This is a python script running ...") 4 | -------------------------------------------------------------------------------- /examples/scripts/script.txt: -------------------------------------------------------------------------------- 1 | # This is a text file script and any line like this beginning with a "#" is a comment 2 | help 3 | help history 4 | -------------------------------------------------------------------------------- /examples/tmux_launch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | # This script launches two applications using tmux in different windows/tabs. 4 | # The user is required to enter the name of at least the first application. 5 | # If the second isn't provided, then the user's default shell is launched for this. 6 | # You must have tmux installed and that can be done using your operating system's package manager. 7 | # 8 | # See the tmux Wiki for info on how to use it: https://github.com/tmux/tmux/wiki. 9 | # To shift focus between different windows in tmux use Ctrl-b followed by l (lowercase "L"). 10 | # 11 | # NOTE: If you have byobu installed, it is a wrapper around tmux and will likely run instead of tmux. 12 | # For info on how to use Byobu, see: https://www.byobu.org/ 13 | # To shift focus between windows/tabs in byobu, simply hit F3. 14 | 15 | # Function to print in red 16 | print_red() { 17 | echo -e "\e[31m$*\e[0m" 18 | } 19 | 20 | if [ $# -eq 0 ]; 21 | then 22 | print_red "No arguments supplied and this script requires at least one" 23 | exit 1 24 | fi 25 | 26 | FIRST_COMMAND=$1 27 | 28 | if [ $# -eq 1 ] 29 | then 30 | SECOND_COMMAND=$SHELL 31 | else 32 | SECOND_COMMAND=$2 33 | fi 34 | 35 | tmux new-session -s "tmux window demo" -n "$FIRST_COMMAND" "$FIRST_COMMAND ;read" \; \ 36 | new-window -n "$SECOND_COMMAND" "$SECOND_COMMAND ; read" \; previous-window 37 | -------------------------------------------------------------------------------- /examples/tmux_split.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | # This script launches two applications using byobu in different tabs. 4 | # The user is required to enter the name of at least the first application. 5 | # If the second isn't provided, then the user's default shell is launched for this. 6 | # 7 | # byobu must be installed for this script to work and you can install it using your 8 | # operating system package manager. For info on how to use Byobu, see: https://www.byobu.org/ 9 | # 10 | # To shift focus between tabs in byobu, just hit F3. 11 | 12 | # Function to print in red 13 | print_red() { 14 | echo -e "\e[31m$*\e[0m" 15 | } 16 | 17 | if [ $# -eq 0 ]; 18 | then 19 | print_red "No arguments supplied and this script requires at least one" 20 | exit 1 21 | fi 22 | 23 | FIRST_COMMAND=$1 24 | 25 | if [ $# -eq 1 ] 26 | then 27 | SECOND_COMMAND=$SHELL 28 | else 29 | SECOND_COMMAND=$2 30 | fi 31 | 32 | tmux new-session -s "tmux split pane demo" "$FIRST_COMMAND ; read" \; \ 33 | split-window "$SECOND_COMMAND ; read" \; \ 34 | select-layout even-vertical 35 | -------------------------------------------------------------------------------- /examples/transcripts/exampleSession.txt: -------------------------------------------------------------------------------- 1 | # Run this transcript with "python decorator_example.py -t exampleSession.txt" 2 | # Anything between two forward slashes, /, is interpreted as a regular expression (regex). 3 | # The regex for editor will match whatever program you use. 4 | # regexes on prompts just make the trailing space obvious 5 | (Cmd) set 6 | allow_style: '/(Terminal|Always|Never)/' 7 | debug: False 8 | echo: False 9 | editor: /.*?/ 10 | feedback_to_output: False 11 | max_completion_items: 50 12 | maxrepeats: 3 13 | quiet: False 14 | timing: False 15 | -------------------------------------------------------------------------------- /examples/transcripts/pirate.transcript: -------------------------------------------------------------------------------- 1 | arrr> loot 2 | Now we gots 1 doubloons 3 | arrr> loot 4 | Now we gots 2 doubloons 5 | arrr> loot 6 | Now we gots 3 doubloons 7 | arrr> drink 3 8 | Now we gots 0 doubloons 9 | arrr> yo --ho 3 rum 10 | yo ho ho ho and a bottle of rum 11 | -------------------------------------------------------------------------------- /examples/transcripts/quit.txt: -------------------------------------------------------------------------------- 1 | (Cmd) quit 2 | -------------------------------------------------------------------------------- /examples/transcripts/transcript_regex.txt: -------------------------------------------------------------------------------- 1 | # Run this transcript with "python example.py -t transcript_regex.txt" 2 | # Anything between two forward slashes, /, is interpreted as a regular expression (regex). 3 | # The regex for editor will match whatever program you use. 4 | # regexes on prompts just make the trailing space obvious 5 | (Cmd) set 6 | allow_style: '/(Terminal|Always|Never)/' 7 | always_show_hint: False 8 | debug: False 9 | echo: False 10 | editor: /.*?/ 11 | feedback_to_output: False 12 | max_completion_items: 50 13 | maxrepeats: 3 14 | quiet: False 15 | timing: False 16 | -------------------------------------------------------------------------------- /examples/unicode_commands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A simple example demonstrating support for unicode command names.""" 3 | 4 | import math 5 | 6 | import cmd2 7 | 8 | 9 | class UnicodeApp(cmd2.Cmd): 10 | """Example cmd2 application with unicode command names.""" 11 | 12 | def __init__(self) -> None: 13 | super().__init__() 14 | self.intro = 'Welcome the Unicode example app. Note the full Unicode support: 😇 💩' 15 | 16 | def do_𝛑print(self, _) -> None: # noqa: PLC2401 17 | """This command prints 𝛑 to 5 decimal places.""" 18 | self.poutput(f"𝛑 = {math.pi:.6}") 19 | 20 | def do_你好(self, arg) -> None: # noqa: N802, PLC2401 21 | """This command says hello in Chinese (Mandarin).""" 22 | self.poutput("你好 " + arg) 23 | 24 | 25 | if __name__ == '__main__': 26 | app = UnicodeApp() 27 | app.cmdloop() 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "prettier": "^3.5.3", 4 | "prettier-plugin-toml": "^2.0.5" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /plugins/README.txt: -------------------------------------------------------------------------------- 1 | For information about creating a cmd2 plugin, see template/README.md 2 | -------------------------------------------------------------------------------- /plugins/ext_test/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | 8 | ## 0.2.0 (2020-09-11) 9 | 10 | - Updated documentation to reflect new home inside of main cmd2 repo. 11 | - Updated python version requirements to match cmd2 12 | 13 | ## 0.1.2 (2020-08-03) 14 | 15 | - Bug Fixes 16 | - Applied fix to match change in cmd2 APIs 17 | 18 | ## 0.1.1 (2020-03-09) 19 | 20 | ### Added 21 | 22 | - Initial contribution 23 | -------------------------------------------------------------------------------- /plugins/ext_test/README.md: -------------------------------------------------------------------------------- 1 | # cmd2 External Test Plugin 2 | 3 | ## Table of Contents 4 | 5 | - [Overview](#overview) 6 | - [Example cmd2 Application](#example-cmd2-application) 7 | - [Defining the test fixture](#defining-the-test-fixture) 8 | - [Writing Tests](#writing-tests) 9 | - [License](#license) 10 | 11 | ## Overview 12 | 13 | This plugin supports testing of a cmd2 application by exposing access cmd2 commands with the same context 14 | as from within a cmd2 pyscript. This allows for verification of an application's support for pyscripts. 15 | 16 | ## Example cmd2 Application 17 | 18 | The following short example shows how to mix in the external test plugin to create a fixture for testing 19 | your cmd2 application. 20 | 21 | Define your cmd2 application 22 | 23 | ```python 24 | import cmd2 25 | class ExampleApp(cmd2.Cmd): 26 | """An class to show how to use a plugin""" 27 | def __init__(self, *args, **kwargs): 28 | # gotta have this or neither the plugin or cmd2 will initialize 29 | super().__init__(*args, **kwargs) 30 | 31 | def do_something(self, arg): 32 | self.last_result = 5 33 | self.poutput('this is the something command') 34 | ``` 35 | 36 | ## Defining the test fixture 37 | 38 | In your test, define a fixture for your cmd2 application 39 | 40 | ```python 41 | import cmd2_ext_test 42 | import pytest 43 | 44 | class ExampleAppTester(cmd2_ext_test.ExternalTestMixin, ExampleApp): 45 | def __init__(self, *args, **kwargs): 46 | # gotta have this or neither the plugin or cmd2 will initialize 47 | super().__init__(*args, **kwargs) 48 | 49 | @pytest.fixture 50 | def example_app(): 51 | app = ExampleAppTester() 52 | app.fixture_setup() 53 | yield app 54 | app.fixture_teardown() 55 | 56 | ``` 57 | 58 | ## Writing Tests 59 | 60 | Now write your tests that validate your application using the `app_cmd` function to access 61 | the cmd2 application's commands. This allows invocation of the application's commands in the 62 | same format as a user would type. The results from calling a command matches what is returned 63 | from running an python script with cmd2's pyscript command, which provides stdout, stderr, and 64 | the command's result data. 65 | 66 | ```python 67 | from cmd2 import CommandResult 68 | 69 | def test_something(example_app): 70 | # execute a command 71 | out = example_app.app_cmd("something") 72 | 73 | # validate the command output and result data 74 | assert isinstance(out, CommandResult) 75 | assert str(out.stdout).strip() == 'this is the something command' 76 | assert out.data == 5 77 | ``` 78 | 79 | ## License 80 | 81 | cmd2 [uses the very liberal MIT license](https://github.com/python-cmd2/cmd2/blob/master/LICENSE). 82 | We invite plugin authors to consider doing the same. 83 | -------------------------------------------------------------------------------- /plugins/ext_test/build-pyenvs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | 4 | # create pyenv environments for each minor version of python 5 | # supported by this project 6 | # 7 | # this script uses terms from Semantic Versioning https://semver.org/ 8 | # version numbers are: major.minor.patch 9 | # 10 | # this script will delete and recreate existing virtualenvs named 11 | # cmd2-3.9, etc. It will also create a .python-version 12 | # 13 | # Prerequisites: 14 | # - *nix-ish environment like macOS or Linux 15 | # - pyenv installed 16 | # - pyenv-virtualenv installed 17 | # - readline and openssl libraries installed so pyenv can 18 | # build pythons 19 | # 20 | 21 | # Make a array of the python minor versions we want to install. 22 | # Order matters in this list, because it's the order that the 23 | # virtualenvs will be added to '.python-version'. Feel free to modify 24 | # this list, but note that this script intentionally won't install 25 | # dev, rc, or beta python releases 26 | declare -a pythons=("3.9", "3.10", "3.11", "3.12", "3.13") 27 | 28 | # function to find the latest patch of a minor version of python 29 | function find_latest_version { 30 | pyenv install -l | \ 31 | sed -En -e "s/^ *//g" -e "/(dev|b|rc)/d" -e "/^$1/p" | \ 32 | tail -1 33 | } 34 | 35 | # empty out '.python-version' 36 | > .python-version 37 | 38 | # loop through the pythons 39 | for minor_version in "${pythons[@]}" 40 | do 41 | patch_version=$( find_latest_version "$minor_version" ) 42 | # use pyenv to install the latest versions of python 43 | # if it's already installed don't install it again 44 | pyenv install -s "$patch_version" 45 | 46 | envname="cmd2-$minor_version" 47 | # remove the associated virtualenv 48 | pyenv uninstall -f "$envname" 49 | # create a new virtualenv 50 | pyenv virtualenv -p "python$minor_version" "$patch_version" "$envname" 51 | # append the virtualenv to .python-version 52 | echo "$envname" >> .python-version 53 | done 54 | -------------------------------------------------------------------------------- /plugins/ext_test/cmd2_ext_test/__init__.py: -------------------------------------------------------------------------------- 1 | """cmd2 External Python Testing Mixin 2 | 3 | Allows developers to exercise their cmd2 application using the PyScript interface 4 | """ 5 | 6 | import importlib.metadata as importlib_metadata 7 | 8 | try: 9 | __version__ = importlib_metadata.version(__name__) 10 | except importlib_metadata.PackageNotFoundError: # pragma: no cover 11 | # package is not installed 12 | __version__ = 'unknown' 13 | 14 | from .cmd2_ext_test import ( 15 | ExternalTestMixin, 16 | ) 17 | 18 | __all__ = ['ExternalTestMixin'] 19 | -------------------------------------------------------------------------------- /plugins/ext_test/cmd2_ext_test/cmd2_ext_test.py: -------------------------------------------------------------------------------- 1 | """External test interface plugin""" 2 | 3 | from typing import ( 4 | TYPE_CHECKING, 5 | Optional, 6 | ) 7 | 8 | import cmd2 9 | 10 | if TYPE_CHECKING: # pragma: no cover 11 | _Base = cmd2.Cmd 12 | else: 13 | _Base = object 14 | 15 | 16 | class ExternalTestMixin(_Base): 17 | """A cmd2 plugin (mixin class) that exposes an interface to execute application commands from python""" 18 | 19 | def __init__(self, *args, **kwargs): 20 | """ 21 | 22 | :type self: cmd2.Cmd 23 | :param args: 24 | :param kwargs: 25 | """ 26 | # code placed here runs before cmd2 initializes 27 | super().__init__(*args, **kwargs) 28 | assert isinstance(self, cmd2.Cmd) 29 | # code placed here runs after cmd2 initializes 30 | self._pybridge = cmd2.py_bridge.PyBridge(self) 31 | 32 | def app_cmd(self, command: str, echo: Optional[bool] = None) -> cmd2.CommandResult: 33 | """ 34 | Run the application command 35 | 36 | :param command: The application command as it would be written on the cmd2 application prompt 37 | :param echo: Flag whether the command's output should be echoed to stdout/stderr 38 | :return: A CommandResult object that captures stdout, stderr, and the command's result object 39 | """ 40 | assert isinstance(self, cmd2.Cmd) 41 | assert isinstance(self, ExternalTestMixin) 42 | try: 43 | self._in_py = True 44 | 45 | return self._pybridge(command, echo=echo) 46 | 47 | finally: 48 | self._in_py = False 49 | 50 | def fixture_setup(self): 51 | """ 52 | Replicates the behavior of `cmdloop()` preparing the state of the application 53 | :type self: cmd2.Cmd 54 | """ 55 | 56 | for func in self._preloop_hooks: 57 | func() 58 | self.preloop() 59 | 60 | def fixture_teardown(self): 61 | """ 62 | Replicates the behavior of `cmdloop()` tearing down the application 63 | 64 | :type self: cmd2.Cmd 65 | """ 66 | for func in self._postloop_hooks: 67 | func() 68 | self.postloop() 69 | -------------------------------------------------------------------------------- /plugins/ext_test/cmd2_ext_test/py.typed: -------------------------------------------------------------------------------- 1 | # PEP 561 2 | -------------------------------------------------------------------------------- /plugins/ext_test/cmd2_ext_test/pylintrc: -------------------------------------------------------------------------------- 1 | # 2 | # pylint configuration 3 | # 4 | # $ pylint --rcfile=cmd2_myplugin/pylintrc cmd2_myplugin 5 | # 6 | 7 | [messages control] 8 | # too-few-public-methods pylint expects a class to have at 9 | # least two public methods 10 | disable=too-few-public-methods 11 | -------------------------------------------------------------------------------- /plugins/ext_test/examples/example.py: -------------------------------------------------------------------------------- 1 | import cmd2_ext_test 2 | 3 | import cmd2 4 | import cmd2.py_bridge 5 | 6 | 7 | class Example(cmd2.Cmd): 8 | """An class to show how to use a plugin""" 9 | 10 | def __init__(self, *args, **kwargs): 11 | # gotta have this or neither the plugin or cmd2 will initialize 12 | super().__init__(*args, **kwargs) 13 | 14 | def do_something(self, _arg): 15 | self.last_result = 5 16 | self.poutput('this is the something command') 17 | 18 | 19 | class ExampleTester(cmd2_ext_test.ExternalTestMixin, Example): 20 | def __init__(self, *args, **kwargs): 21 | # gotta have this or neither the plugin or cmd2 will initialize 22 | super().__init__(*args, **kwargs) 23 | 24 | 25 | if __name__ == '__main__': 26 | app = ExampleTester() 27 | 28 | try: 29 | app.fixture_setup() 30 | 31 | out = app.app_cmd("something") 32 | assert isinstance(out, cmd2.CommandResult) 33 | 34 | assert out.data == 5 35 | 36 | finally: 37 | app.fixture_teardown() 38 | -------------------------------------------------------------------------------- /plugins/ext_test/noxfile.py: -------------------------------------------------------------------------------- 1 | import nox 2 | 3 | 4 | @nox.session(python=['3.9', '3.10', '3.11', '3.12', '3.13']) 5 | def tests(session): 6 | session.install('invoke', './[test]') 7 | session.run('invoke', 'pytest', '--junit', '--no-pty') 8 | -------------------------------------------------------------------------------- /plugins/ext_test/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import setuptools 4 | 5 | # get the long description from the README file 6 | here = os.path.abspath(os.path.dirname(__file__)) 7 | with open(os.path.join(here, 'README.md'), encoding='utf-8') as f: 8 | long_description = f.read() 9 | 10 | PACKAGE_DATA = { 11 | 'cmd2_ext_test': ['py.typed'], 12 | } 13 | 14 | setuptools.setup( 15 | name='cmd2-ext-test', 16 | version='2.0.0', 17 | description='External test plugin for cmd2. Allows for external invocation of commands as if from a cmd2 pyscript', 18 | long_description=long_description, 19 | long_description_content_type='text/markdown', 20 | keywords='cmd2 test plugin', 21 | author='Eric Lin', 22 | author_email='anselor@gmail.com', 23 | url='https://github.com/python-cmd2/cmd2/tree/master/plugins/ext_test', 24 | license='MIT', 25 | package_data=PACKAGE_DATA, 26 | packages=['cmd2_ext_test'], 27 | python_requires='>=3.9', 28 | install_requires=['cmd2 >= 2, <3'], 29 | setup_requires=['setuptools >= 42', 'setuptools_scm >= 3.4'], 30 | classifiers=[ 31 | 'Development Status :: 5 - Production/Stable', 32 | 'Environment :: Console', 33 | 'Operating System :: OS Independent', 34 | 'Topic :: Software Development :: Libraries :: Python Modules', 35 | 'Intended Audience :: Developers', 36 | 'License :: OSI Approved :: MIT License', 37 | 'Programming Language :: Python :: 3.9', 38 | 'Programming Language :: Python :: 3.10', 39 | 'Programming Language :: Python :: 3.11', 40 | 'Programming Language :: Python :: 3.12', 41 | 'Programming Language :: Python :: 3.13', 42 | 'Programming Language :: Python :: 3.14', 43 | ], 44 | # dependencies for development and testing 45 | # $ pip install -e .[dev] 46 | extras_require={ 47 | 'test': ['codecov', 'coverage', 'pytest', 'pytest-cov'], 48 | 'dev': ['setuptools_scm', 'pytest', 'codecov', 'pytest-cov', 'pylint', 'invoke', 'wheel', 'twine'], 49 | }, 50 | ) 51 | -------------------------------------------------------------------------------- /plugins/ext_test/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # empty file to create a package 3 | -------------------------------------------------------------------------------- /plugins/ext_test/tests/pylintrc: -------------------------------------------------------------------------------- 1 | # 2 | # pylint configuration for tests package 3 | # 4 | # $ pylint --rcfile=tests/pylintrc tests 5 | # 6 | 7 | [basic] 8 | # allow for longer method and function names 9 | method-rgx=(([a-z][a-z0-9_]{2,50})|(_[a-z0-9_]*))$ 10 | function-rgx=(([a-z][a-z0-9_]{2,50})|(_[a-z0-9_]*))$ 11 | 12 | [messages control] 13 | # too-many-public-methods -> test classes can have lots of methods, so let's ignore those 14 | # missing-docstring -> prefer method names instead of docstrings 15 | # no-self-use -> test methods part of a class hardly ever use self 16 | # unused-variable -> sometimes we are expecting exceptions 17 | # redefined-outer-name -> pylint fixtures cause these 18 | # protected-access -> we want to test private methods 19 | disable=too-many-public-methods,missing-docstring,no-self-use,unused-variable,redefined-outer-name,protected-access 20 | -------------------------------------------------------------------------------- /plugins/ext_test/tests/test_ext_test.py: -------------------------------------------------------------------------------- 1 | import cmd2_ext_test 2 | import pytest 3 | 4 | from cmd2 import ( 5 | CommandResult, 6 | cmd2, 7 | ) 8 | 9 | ###### 10 | # 11 | # define a class which implements a simple cmd2 application 12 | # 13 | ###### 14 | 15 | OUT_MSG = 'this is the something command' 16 | 17 | 18 | class ExampleApp(cmd2.Cmd): 19 | """An class to show how to use a plugin""" 20 | 21 | def __init__(self, *args, **kwargs): 22 | # gotta have this or neither the plugin or cmd2 will initialize 23 | super().__init__(*args, **kwargs) 24 | 25 | def do_something(self, _): 26 | self.last_result = 5 27 | self.poutput(OUT_MSG) 28 | 29 | 30 | # Define a tester class that brings in the external test mixin 31 | 32 | 33 | class ExampleTester(cmd2_ext_test.ExternalTestMixin, ExampleApp): 34 | def __init__(self, *args, **kwargs): 35 | # gotta have this or neither the plugin or cmd2 will initialize 36 | super().__init__(*args, **kwargs) 37 | 38 | 39 | # 40 | # You can't use a fixture to instantiate your app if you want to use 41 | # to use the capsys fixture to capture the output. cmd2.Cmd sets 42 | # internal variables to sys.stdout and sys.stderr on initialization 43 | # and then uses those internal variables instead of sys.stdout. It does 44 | # this so you can redirect output from within the app. The capsys fixture 45 | # can't capture the output properly in this scenario. 46 | # 47 | # If you have extensive initialization needs, create a function 48 | # to initialize your cmd2 application. 49 | 50 | 51 | @pytest.fixture 52 | def example_app(): 53 | app = ExampleTester() 54 | app.fixture_setup() 55 | yield app 56 | app.fixture_teardown() 57 | 58 | 59 | ##### 60 | # 61 | # unit tests 62 | # 63 | ##### 64 | 65 | 66 | def test_something(example_app): 67 | # load our fixture 68 | # execute a command 69 | out = example_app.app_cmd("something") 70 | 71 | # validate the command output and result data 72 | assert isinstance(out, CommandResult) 73 | assert str(out.stdout).strip() == OUT_MSG 74 | assert out.data == 5 75 | -------------------------------------------------------------------------------- /plugins/tasks.py: -------------------------------------------------------------------------------- 1 | """Development related tasks to be run with 'invoke'. 2 | 3 | Make sure you satisfy the following Python module requirements if you are trying to publish a release to PyPI: 4 | - twine >= 1.11.0 5 | - wheel >= 0.31.0 6 | - setuptools >= 39.1.0 7 | """ 8 | 9 | import pathlib 10 | 11 | import invoke 12 | 13 | from plugins.ext_test import ( 14 | tasks as ext_test_tasks, 15 | ) 16 | from plugins.template import ( 17 | tasks as template_tasks, 18 | ) 19 | 20 | # create namespaces 21 | namespace = invoke.Collection( 22 | ext_test=ext_test_tasks, 23 | template=template_tasks, 24 | ) 25 | namespace_clean = invoke.Collection('clean') 26 | namespace.add_collection(namespace_clean, 'clean') 27 | 28 | ##### 29 | # 30 | # pytest, pylint, and codecov 31 | # 32 | ##### 33 | 34 | TASK_ROOT = pathlib.Path(__file__).resolve().parent 35 | TASK_ROOT_STR = str(TASK_ROOT) 36 | 37 | 38 | @invoke.task(pre=[ext_test_tasks.pytest]) 39 | @invoke.task() 40 | def pytest(_) -> None: 41 | """Run tests and code coverage using pytest.""" 42 | 43 | 44 | namespace.add_task(pytest) 45 | 46 | 47 | @invoke.task(pre=[ext_test_tasks.pytest_clean]) 48 | def pytest_clean(_) -> None: 49 | """Remove pytest cache and code coverage files and directories.""" 50 | 51 | 52 | namespace_clean.add_task(pytest_clean, 'pytest') 53 | 54 | 55 | @invoke.task(pre=[ext_test_tasks.mypy]) 56 | def mypy(_) -> None: 57 | """Run mypy optional static type checker.""" 58 | 59 | 60 | namespace.add_task(mypy) 61 | 62 | 63 | @invoke.task(pre=[ext_test_tasks.mypy_clean]) 64 | def mypy_clean(_) -> None: 65 | """Remove mypy cache directory.""" 66 | # pylint: disable=unused-argument 67 | 68 | 69 | namespace_clean.add_task(mypy_clean, 'mypy') 70 | 71 | 72 | ##### 73 | # 74 | # build and distribute 75 | # 76 | ##### 77 | BUILDDIR = 'build' 78 | DISTDIR = 'dist' 79 | 80 | 81 | @invoke.task(pre=[ext_test_tasks.build_clean]) 82 | def build_clean(_) -> None: 83 | """Remove the build directory.""" 84 | 85 | 86 | namespace_clean.add_task(build_clean, 'build') 87 | 88 | 89 | @invoke.task(pre=[ext_test_tasks.dist_clean]) 90 | def dist_clean(_) -> None: 91 | """Remove the dist directory.""" 92 | 93 | 94 | namespace_clean.add_task(dist_clean, 'dist') 95 | 96 | 97 | # make a dummy clean task which runs all the tasks in the clean namespace 98 | clean_tasks = list(namespace_clean.tasks.values()) 99 | 100 | 101 | @invoke.task(pre=list(namespace_clean.tasks.values()), default=True) 102 | def clean_all(_) -> None: 103 | """Run all clean tasks.""" 104 | # pylint: disable=unused-argument 105 | 106 | 107 | namespace_clean.add_task(clean_all, 'all') 108 | 109 | 110 | @invoke.task(pre=[clean_all], post=[ext_test_tasks.sdist]) 111 | def sdist(_) -> None: 112 | """Create a source distribution.""" 113 | 114 | 115 | namespace.add_task(sdist) 116 | 117 | 118 | @invoke.task(pre=[clean_all], post=[ext_test_tasks.wheel]) 119 | def wheel(_) -> None: 120 | """Build a wheel distribution.""" 121 | 122 | 123 | namespace.add_task(wheel) 124 | 125 | 126 | # ruff linter 127 | @invoke.task(pre=[ext_test_tasks.lint]) 128 | def lint(context) -> None: 129 | with context.cd(TASK_ROOT_STR): 130 | context.run("ruff check") 131 | 132 | 133 | namespace.add_task(lint) 134 | 135 | 136 | # ruff formatter 137 | @invoke.task(pre=[ext_test_tasks.format]) 138 | def format(context) -> None: # noqa: A001 139 | """Run formatter.""" 140 | with context.cd(TASK_ROOT_STR): 141 | context.run("ruff format --check") 142 | 143 | 144 | namespace.add_task(format) 145 | -------------------------------------------------------------------------------- /plugins/template/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | 8 | ## 1.0.0 (2018-07-24) 9 | 10 | ### Added 11 | 12 | - Created plugin template and initial documentation 13 | -------------------------------------------------------------------------------- /plugins/template/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jared Crapo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /plugins/template/build-pyenvs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | 4 | # create pyenv environments for each minor version of python 5 | # supported by this project 6 | # 7 | # this script uses terms from Semantic Versioning https://semver.org/ 8 | # version numbers are: major.minor.patch 9 | # 10 | # this script will delete and recreate existing virtualenvs named 11 | # cmd2-3.9, etc. It will also create a .python-version 12 | # 13 | # Prerequisites: 14 | # - *nix-ish environment like macOS or Linux 15 | # - pyenv installed 16 | # - pyenv-virtualenv installed 17 | # - readline and openssl libraries installed so pyenv can 18 | # build pythons 19 | # 20 | 21 | # Make a array of the python minor versions we want to install. 22 | # Order matters in this list, because it's the order that the 23 | # virtualenvs will be added to '.python-version'. Feel free to modify 24 | # this list, but note that this script intentionally won't install 25 | # dev, rc, or beta python releases 26 | declare -a pythons=("3.9" "3.10" "3.11", "3.12", "3.13") 27 | 28 | # function to find the latest patch of a minor version of python 29 | function find_latest_version { 30 | pyenv install -l | \ 31 | sed -En -e "s/^ *//g" -e "/(dev|b|rc)/d" -e "/^$1/p" | \ 32 | tail -1 33 | } 34 | 35 | # empty out '.python-version' 36 | > .python-version 37 | 38 | # loop through the pythons 39 | for minor_version in "${pythons[@]}" 40 | do 41 | patch_version=$( find_latest_version "$minor_version" ) 42 | # use pyenv to install the latest versions of python 43 | # if it's already installed don't install it again 44 | pyenv install -s "$patch_version" 45 | 46 | envname="cmd2-$minor_version" 47 | # remove the associated virtualenv 48 | pyenv uninstall -f "$envname" 49 | # create a new virtualenv 50 | pyenv virtualenv -p "python$minor_version" "$patch_version" "$envname" 51 | # append the virtualenv to .python-version 52 | echo "$envname" >> .python-version 53 | done 54 | -------------------------------------------------------------------------------- /plugins/template/cmd2_myplugin/__init__.py: -------------------------------------------------------------------------------- 1 | """Description of myplugin. 2 | 3 | An overview of what myplugin does. 4 | """ 5 | 6 | import importlib.metadata as importlib_metadata 7 | 8 | from .myplugin import ( # noqa: F401 9 | MyPluginMixin, 10 | empty_decorator, 11 | ) 12 | 13 | try: 14 | __version__ = importlib_metadata.version(__name__) 15 | except importlib_metadata.PackageNotFoundError: # pragma: no cover 16 | # package is not installed 17 | __version__ = 'unknown' 18 | -------------------------------------------------------------------------------- /plugins/template/cmd2_myplugin/myplugin.py: -------------------------------------------------------------------------------- 1 | """An example cmd2 plugin.""" 2 | 3 | import functools 4 | from collections.abc import Callable 5 | from typing import TYPE_CHECKING 6 | 7 | import cmd2 8 | 9 | if TYPE_CHECKING: # pragma: no cover 10 | _Base = cmd2.Cmd 11 | else: 12 | _Base = object 13 | 14 | 15 | def empty_decorator(func: Callable) -> Callable: 16 | """An empty decorator for myplugin.""" 17 | 18 | @functools.wraps(func) 19 | def _empty_decorator(self, *args, **kwargs) -> None: 20 | self.poutput("in the empty decorator") 21 | func(self, *args, **kwargs) 22 | 23 | _empty_decorator.__doc__ = func.__doc__ 24 | return _empty_decorator 25 | 26 | 27 | class MyPluginMixin(_Base): 28 | """A mixin class which adds a 'say' command to a cmd2 subclass. 29 | 30 | The order in which you add the mixin matters. Say you want to 31 | use this mixin in a class called MyApp. 32 | 33 | class MyApp(cmd2_myplugin.MyPlugin, cmd2.Cmd): 34 | def __init__(self, *args, **kwargs): 35 | # gotta have this or neither the plugin or cmd2 will initialize 36 | super().__init__(*args, **kwargs) 37 | """ 38 | 39 | def __init__(self, *args, **kwargs) -> None: 40 | # code placed here runs before cmd2 initializes 41 | super().__init__(*args, **kwargs) 42 | # code placed here runs after cmd2 initializes 43 | # this is where you register any hook functions 44 | self.register_preloop_hook(self.cmd2_myplugin_preloop_hook) 45 | self.register_postloop_hook(self.cmd2_myplugin_postloop_hook) 46 | self.register_postparsing_hook(self.cmd2_myplugin_postparsing_hook) 47 | 48 | def do_say(self, statement) -> None: 49 | """Simple say command.""" 50 | self.poutput(statement) 51 | 52 | # 53 | # define hooks as functions, not methods 54 | def cmd2_myplugin_preloop_hook(self) -> None: 55 | """Method to be called before the command loop begins.""" 56 | self.poutput("preloop hook") 57 | 58 | def cmd2_myplugin_postloop_hook(self) -> None: 59 | """Method to be called after the command loop finishes.""" 60 | self.poutput("postloop hook") 61 | 62 | def cmd2_myplugin_postparsing_hook(self, data: cmd2.plugin.PostparsingData) -> cmd2.plugin.PostparsingData: 63 | """Method to be called after parsing user input, but before running the command.""" 64 | self.poutput('in postparsing hook') 65 | return data 66 | -------------------------------------------------------------------------------- /plugins/template/cmd2_myplugin/pylintrc: -------------------------------------------------------------------------------- 1 | # 2 | # pylint configuration 3 | # 4 | # $ pylint --rcfile=cmd2_myplugin/pylintrc cmd2_myplugin 5 | # 6 | 7 | [messages control] 8 | # too-few-public-methods pylint expects a class to have at 9 | # least two public methods 10 | disable=too-few-public-methods 11 | -------------------------------------------------------------------------------- /plugins/template/examples/example.py: -------------------------------------------------------------------------------- 1 | import cmd2_myplugin 2 | 3 | import cmd2 4 | 5 | 6 | class Example(cmd2_myplugin.MyPlugin, cmd2.Cmd): 7 | """An class to show how to use a plugin.""" 8 | 9 | def __init__(self, *args, **kwargs) -> None: 10 | # gotta have this or neither the plugin or cmd2 will initialize 11 | super().__init__(*args, **kwargs) 12 | 13 | @cmd2_myplugin.empty_decorator 14 | def do_something(self, _arg) -> None: 15 | self.poutput('this is the something command') 16 | 17 | 18 | if __name__ == '__main__': 19 | app = Example() 20 | app.cmdloop() 21 | -------------------------------------------------------------------------------- /plugins/template/noxfile.py: -------------------------------------------------------------------------------- 1 | import nox 2 | 3 | 4 | @nox.session(python=['3.9', '3.10', '3.11', '3.12', '3.13']) 5 | def tests(session) -> None: 6 | session.install('invoke', './[test]') 7 | session.run('invoke', 'pytest', '--junit', '--no-pty') 8 | -------------------------------------------------------------------------------- /plugins/template/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import setuptools 4 | 5 | # get the long description from the README file 6 | here = os.path.abspath(os.path.dirname(__file__)) 7 | with open(os.path.join(here, 'README.md'), encoding='utf-8') as f: 8 | long_description = f.read() 9 | 10 | setuptools.setup( 11 | name='cmd2-myplugin', 12 | # use_scm_version=True, # use_scm_version doesn't work if setup.py isn't in the repository root # noqa: ERA001 13 | version='2.0.0', 14 | description='A template used to build plugins for cmd2', 15 | long_description=long_description, 16 | long_description_content_type='text/markdown', 17 | keywords='cmd2 plugin', 18 | author='Kotfu', 19 | author_email='kotfu@kotfu.net', 20 | url='https://github.com/python-cmd2/cmd2-plugin-template', 21 | license='MIT', 22 | packages=['cmd2_myplugin'], 23 | python_requires='>=3.9', 24 | install_requires=['cmd2 >= 2, <3'], 25 | setup_requires=['setuptools_scm'], 26 | classifiers=[ 27 | 'Development Status :: 4 - Beta', 28 | 'Environment :: Console', 29 | 'Operating System :: OS Independent', 30 | 'Topic :: Software Development :: Libraries :: Python Modules', 31 | 'Intended Audience :: Developers', 32 | 'License :: OSI Approved :: MIT License', 33 | 'Programming Language :: Python :: 3.9', 34 | 'Programming Language :: Python :: 3.10', 35 | 'Programming Language :: Python :: 3.11', 36 | 'Programming Language :: Python :: 3.12', 37 | 'Programming Language :: Python :: 3.13', 38 | 'Programming Language :: Python :: 3.14', 39 | ], 40 | # dependencies for development and testing 41 | # $ pip install -e .[dev] 42 | extras_require={ 43 | 'test': [ 44 | 'codecov', 45 | 'coverage', 46 | 'pytest', 47 | 'pytest-cov', 48 | ], 49 | 'dev': ['setuptools_scm', 'pytest', 'codecov', 'pytest-cov', 'pylint', 'invoke', 'wheel', 'twine'], 50 | }, 51 | ) 52 | -------------------------------------------------------------------------------- /plugins/template/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # empty file to create a package 3 | -------------------------------------------------------------------------------- /plugins/template/tests/pylintrc: -------------------------------------------------------------------------------- 1 | # 2 | # pylint configuration for tests package 3 | # 4 | # $ pylint --rcfile=tests/pylintrc tests 5 | # 6 | 7 | [basic] 8 | # allow for longer method and function names 9 | method-rgx=(([a-z][a-z0-9_]{2,50})|(_[a-z0-9_]*))$ 10 | function-rgx=(([a-z][a-z0-9_]{2,50})|(_[a-z0-9_]*))$ 11 | 12 | [messages control] 13 | # too-many-public-methods -> test classes can have lots of methods, so let's ignore those 14 | # missing-docstring -> prefer method names instead of docstrings 15 | # no-self-use -> test methods part of a class hardly ever use self 16 | # unused-variable -> sometimes we are expecting exceptions 17 | # redefined-outer-name -> pylint fixtures cause these 18 | # protected-access -> we want to test private methods 19 | disable=too-many-public-methods,missing-docstring,no-self-use,unused-variable,redefined-outer-name,protected-access 20 | -------------------------------------------------------------------------------- /plugins/template/tests/test_myplugin.py: -------------------------------------------------------------------------------- 1 | import cmd2_myplugin 2 | 3 | from cmd2 import ( 4 | cmd2, 5 | ) 6 | 7 | ###### 8 | # 9 | # define a class which uses our plugin and some convenience functions 10 | # 11 | ###### 12 | 13 | 14 | class MyApp(cmd2_myplugin.MyPluginMixin, cmd2.Cmd): 15 | """Simple subclass of cmd2.Cmd with our SayMixin plugin included.""" 16 | 17 | def __init__(self, *args, **kwargs) -> None: 18 | super().__init__(*args, **kwargs) 19 | 20 | @cmd2_myplugin.empty_decorator 21 | def do_empty(self, _args) -> None: 22 | self.poutput("running the empty command") 23 | 24 | 25 | # 26 | # You can't use a fixture to instantiate your app if you want to use 27 | # to use the capsys fixture to capture the output. cmd2.Cmd sets 28 | # internal variables to sys.stdout and sys.stderr on initialization 29 | # and then uses those internal variables instead of sys.stdout. It does 30 | # this so you can redirect output from within the app. The capsys fixture 31 | # can't capture the output properly in this scenario. 32 | # 33 | # If you have extensive initialization needs, create a function 34 | # to initialize your cmd2 application. 35 | 36 | 37 | def init_app(): 38 | return MyApp() 39 | 40 | 41 | ##### 42 | # 43 | # unit tests 44 | # 45 | ##### 46 | 47 | 48 | def test_say(capsys) -> None: 49 | # call our initialization function instead of using a fixture 50 | app = init_app() 51 | # run our mixed in command 52 | app.onecmd_plus_hooks('say hello') 53 | # use the capsys fixture to retrieve the output on stdout and stderr 54 | out, err = capsys.readouterr() 55 | # make our assertions 56 | assert out == 'in postparsing hook\nhello\n' 57 | assert not err 58 | 59 | 60 | def test_decorator(capsys) -> None: 61 | # call our initialization function instead of using a fixture 62 | app = init_app() 63 | # run one command in the app 64 | app.onecmd_plus_hooks('empty') 65 | # use the capsys fixture to retrieve the output on stdout and stderr 66 | out, err = capsys.readouterr() 67 | # make our assertions 68 | assert out == 'in postparsing hook\nin the empty decorator\nrunning the empty command\n' 69 | assert not err 70 | -------------------------------------------------------------------------------- /readme_files/shout_out.csv: -------------------------------------------------------------------------------- 1 | Application Name, Description 2 | [Jok3r](http://www.jok3r-framework.com),Network & Web Pentest Automation Framework 3 | [CephFS Shell](https://github.com/ceph/ceph),'[Ceph](https://ceph.com/) is a distributed object, block, and file storage platform' 4 | [psiTurk](https://psiturk.org),An open platform for science on Amazon Mechanical Turk 5 | [Poseidon](https://github.com/CyberReboot/poseidon),Leverages software-defined networks (SDNs) to acquire and then feed network traffic to a number of machine learning techniques. 6 | [Unipacker](https://github.com/unipacker/unipacker),Automatic and platform-independent unpacker for Windows binaries based on emulation 7 | [tomcatmanager](https://github.com/tomcatmanager/tomcatmanager),A command line tool and python library for managing a tomcat server 8 | [Expliot](https://gitlab.com/expliot_framework/expliot),Internet of Things (IoT) exploitation framework 9 | [mptcpanalyzer](),Tool to help analyze mptcp pcaps 10 | [clanvas](https://github.com/marklalor/clanvas),Command-line client for Canvas by Instructure 11 | 12 | Oldies but goodie,, 13 | [JSShell](https://github.com/Den1al/JSShell),An interactive multi-user web JavaScript shell. 14 | [FLASHMINGO](https://github.com/fireeye/flashmingo),Automatic analysis of SWF files based on some heuristics. Extensible via plugins. 15 | -------------------------------------------------------------------------------- /readme_files/shoutout.txt: -------------------------------------------------------------------------------- 1 | Name: 2 | Source Code: 3 | Status: 4 | Description: 5 | Genre: 6 | 7 | 8 | 9 | Name: Microsoft/ Azure Counterfit 10 | Source Code: https://github.com/Azure/counterfit 11 | Status: Active 12 | Description: Counterfit is a command-line tool and generic automation layer for assessing the security of machine learning systems. 13 | Genre: Security 14 | 15 | Name: MQTT-Pwn 16 | Source Code: https://github.com/akamai-threat-research/mqtt-pwn 17 | Status: Stale 18 | Description: MQTT-PWN intends to be a one-stop-shop for IoT Broker penetration-testing and security assessment operations 19 | Genre: Security 20 | 21 | Name: OpenBeacon2 22 | Source Code: https://github.com/etherkit/OpenBeacon2 23 | Description: Commandline tool for interfacing with hardware in the [OpenBeacon](https://www.openbeacon.org/) environment. 24 | Genre: Utility 25 | 26 | Name: GreenWaves-Technologies/gap_sdk 27 | Source Code: https://github.com/GreenWaves-Technologies/gap_sdk 28 | Status: Active 29 | Description: GAP SDK allows you to compile and execute applications on the GAP IoT Application Processor. 30 | Genre: Utility, IoT 31 | 32 | Name: JSShell 33 | Source Code: https://github.com/Den1al/JSShell 34 | Status: Unknown 35 | Description: An interactive multi-user web based javascript shell. 36 | Genre: Utility, Web 37 | 38 | 39 | Name: pyOS 40 | Source Code: https://github.com/muhrin/pyos 41 | Status: Active 42 | Description: A fresh way to interact with your python objects as though they were files on your filesystem. 43 | Genre: ???? 44 | 45 | Name: darkcode357/thg-framework 46 | Source Code: https://github.com/darkcode357/thg-framework 47 | Status: Inactive 48 | Description: THG is a framework for security testing and ctf games, but it can be used as a library for exploit development. 49 | Genre: Security 50 | 51 | 52 | Name: qsecure-labs/Overloard 53 | Source Code: https://github.com/qsecure-labs/overlord 54 | Status: Active 55 | Description: Overlord provides a python-based console CLI which is used to build Red Teaming infrastructure in an automated way. 56 | Genre: Security 57 | 58 | 59 | Name: seemoo-lab/internalblue 60 | Source Code: https://github.com/seemoo-lab/internalblue 61 | Status: Active 62 | Description: Bluetooth experimentation framework for Broadcom and Cypress chips. 63 | Genre: utility 64 | 65 | Name: icl-rocketry/Avionics 66 | Source Code: https://github.com/icl-rocketry/Avionics 67 | Status: Active 68 | Description: The main repository for hardware and software associated with the Ricardo Avionics Ecosystem. 69 | Genre: Utility 70 | 71 | 72 | Name: jonny1102/nmap-parse 73 | Source Code: https://github.com/jonny1102/nmap-parse 74 | Status: Inactive 75 | Description: Command line nmap XML parser 76 | Genre: Security 77 | 78 | Name: cybiere/baboossh 79 | Source Code: https://github.com/cybiere/baboossh 80 | Status: Active 81 | Description:SSH spreading made easy for red teams in a hurry 82 | Genre: Security 83 | 84 | Name: qilingframework/qiling 85 | Source Code: https://github.com/qilingframework/qiling 86 | Status: Active 87 | Description: Qiling Advanced Binary Emulation Framework 88 | Genre: Security 89 | 90 | 91 | Name: JohnHammond/katana 92 | Source Code: https://github.com/JohnHammond/katana 93 | Status: Active 94 | Description: Katana - Automatic CTF Challenge Solver in Python3 95 | Genre: Security 96 | -------------------------------------------------------------------------------- /tests/.cmd2rc: -------------------------------------------------------------------------------- 1 | alias create ls !ls -hal 2 | alias create pwd !pwd 3 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cmd2/cmd2/28f226acbea4c1043449b0de72ca2c8bdebf3a56/tests/__init__.py -------------------------------------------------------------------------------- /tests/pyscript/echo.py: -------------------------------------------------------------------------------- 1 | # Tests echo argument to app() 2 | app.cmd_echo = False 3 | 4 | # echo defaults to current setting which is False, so this help text should not be echoed to pytest's stdout 5 | app('help alias') 6 | 7 | # pytest's stdout should have this help text written to it 8 | app('help edit', echo=True) 9 | -------------------------------------------------------------------------------- /tests/pyscript/environment.py: -------------------------------------------------------------------------------- 1 | # Tests that cmd2 populates __name__, __file__, and sets sys.path[0] to our directory 2 | import os 3 | import sys 4 | 5 | app.cmd_echo = True 6 | 7 | if __name__ != '__main__': 8 | print(f"Error: __name__ is: {__name__}") 9 | quit() 10 | 11 | if __file__ != sys.argv[0]: 12 | print(f"Error: __file__ is: {__file__}") 13 | quit() 14 | 15 | our_dir = os.path.dirname(os.path.abspath(__file__)) 16 | if our_dir != sys.path[0]: 17 | print(f"Error: our_dir is: {our_dir}") 18 | quit() 19 | 20 | print("PASSED") 21 | -------------------------------------------------------------------------------- /tests/pyscript/help.py: -------------------------------------------------------------------------------- 1 | app.cmd_echo = True 2 | app('help') 3 | 4 | # Exercise py_quit() in unit test 5 | quit() 6 | -------------------------------------------------------------------------------- /tests/pyscript/py_locals.py: -------------------------------------------------------------------------------- 1 | # Tests how much a pyscript can affect cmd2.Cmd.py_locals 2 | 3 | del [locals()["test_var"]] 4 | my_list.append(2) 5 | -------------------------------------------------------------------------------- /tests/pyscript/pyscript_dir.py: -------------------------------------------------------------------------------- 1 | out = dir(app) 2 | out.sort() 3 | print(out) 4 | -------------------------------------------------------------------------------- /tests/pyscript/raises_exception.py: -------------------------------------------------------------------------------- 1 | """Example demonstrating what happens when a Python script raises an exception""" 2 | 3 | x = 1 + 'blue' 4 | -------------------------------------------------------------------------------- /tests/pyscript/recursive.py: -------------------------------------------------------------------------------- 1 | """Example demonstrating that calling run_pyscript recursively inside another Python script isn't allowed""" 2 | 3 | import os 4 | import sys 5 | 6 | app.cmd_echo = True 7 | my_dir = os.path.dirname(os.path.realpath(sys.argv[0])) 8 | app('run_pyscript {}'.format(os.path.join(my_dir, 'stop.py'))) 9 | -------------------------------------------------------------------------------- /tests/pyscript/self_in_py.py: -------------------------------------------------------------------------------- 1 | # Tests self_in_py in pyscripts 2 | if 'self' in globals(): 3 | print("I see self") 4 | else: 5 | print("I do not see self") 6 | -------------------------------------------------------------------------------- /tests/pyscript/stdout_capture.py: -------------------------------------------------------------------------------- 1 | # This script demonstrates when output of a command finalization hook is captured by a pyscript app() call 2 | import sys 3 | 4 | # The unit test framework passes in the string being printed by the command finalization hook 5 | hook_output = sys.argv[1] 6 | 7 | # Run a help command which results in 1 call to onecmd_plus_hooks 8 | res = app('help') 9 | 10 | # hook_output will not be captured because there are no nested calls to onecmd_plus_hooks 11 | if hook_output not in res.stdout: 12 | print("PASSED") 13 | else: 14 | print("FAILED") 15 | 16 | # Run the last command in the history 17 | res = app('history -r -1') 18 | 19 | # All output of the history command will be captured. This includes all output of the commands 20 | # started in do_history() using onecmd_plus_hooks(), including any output in those commands' hooks. 21 | # Therefore we expect the hook_output to show up this time. 22 | if hook_output in res.stdout: 23 | print("PASSED") 24 | else: 25 | print("FAILED") 26 | -------------------------------------------------------------------------------- /tests/pyscript/stop.py: -------------------------------------------------------------------------------- 1 | app.cmd_echo = True 2 | app('help') 3 | 4 | # This will set stop to True in the PyBridge 5 | app('quit') 6 | 7 | # Exercise py_quit() in unit test 8 | quit() 9 | -------------------------------------------------------------------------------- /tests/relative_multiple.txt: -------------------------------------------------------------------------------- 1 | _relative_run_script scripts/one_down.txt 2 | -------------------------------------------------------------------------------- /tests/script.py: -------------------------------------------------------------------------------- 1 | """Trivial example of a Python script which can be run inside a cmd2 application.""" 2 | 3 | print("This is a python script running ...") 4 | -------------------------------------------------------------------------------- /tests/script.txt: -------------------------------------------------------------------------------- 1 | help history 2 | -------------------------------------------------------------------------------- /tests/scripts/binary.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cmd2/cmd2/28f226acbea4c1043449b0de72ca2c8bdebf3a56/tests/scripts/binary.bin -------------------------------------------------------------------------------- /tests/scripts/empty.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cmd2/cmd2/28f226acbea4c1043449b0de72ca2c8bdebf3a56/tests/scripts/empty.txt -------------------------------------------------------------------------------- /tests/scripts/help.txt: -------------------------------------------------------------------------------- 1 | help -v 2 | -------------------------------------------------------------------------------- /tests/scripts/nested.txt: -------------------------------------------------------------------------------- 1 | _relative_run_script precmds.txt 2 | help 3 | shortcuts 4 | _relative_run_script postcmds.txt 5 | -------------------------------------------------------------------------------- /tests/scripts/one_down.txt: -------------------------------------------------------------------------------- 1 | _relative_run_script ../script.txt 2 | -------------------------------------------------------------------------------- /tests/scripts/postcmds.txt: -------------------------------------------------------------------------------- 1 | set allow_style Never 2 | -------------------------------------------------------------------------------- /tests/scripts/precmds.txt: -------------------------------------------------------------------------------- 1 | set allow_style Always 2 | -------------------------------------------------------------------------------- /tests/scripts/utf8.txt: -------------------------------------------------------------------------------- 1 | !echo γνωρίζω 2 | -------------------------------------------------------------------------------- /tests/test_utils_defining_class.py: -------------------------------------------------------------------------------- 1 | """Unit testing for get_defining_class in cmd2/utils.py module.""" 2 | 3 | import functools 4 | 5 | import cmd2.utils as cu 6 | 7 | 8 | class ParentClass: 9 | def func_with_overrides(self) -> None: 10 | pass 11 | 12 | def parent_only_func(self, param1, param2) -> None: 13 | pass 14 | 15 | 16 | class ChildClass(ParentClass): 17 | def func_with_overrides(self) -> None: 18 | super().func_with_overrides() 19 | 20 | def child_function(self) -> None: 21 | pass 22 | 23 | def lambda1() -> int: 24 | return 1 25 | 26 | def lambda2() -> int: 27 | return 2 28 | 29 | @classmethod 30 | def class_method(cls) -> None: 31 | pass 32 | 33 | @staticmethod 34 | def static_meth() -> None: 35 | pass 36 | 37 | 38 | def func_not_in_class() -> None: 39 | pass 40 | 41 | 42 | def test_get_defining_class() -> None: 43 | parent_instance = ParentClass() 44 | child_instance = ChildClass() 45 | 46 | # validate unbound class functions 47 | assert cu.get_defining_class(ParentClass.func_with_overrides) is ParentClass 48 | assert cu.get_defining_class(ParentClass.parent_only_func) is ParentClass 49 | assert cu.get_defining_class(ChildClass.func_with_overrides) is ChildClass 50 | assert cu.get_defining_class(ChildClass.parent_only_func) is ParentClass 51 | assert cu.get_defining_class(ChildClass.child_function) is ChildClass 52 | assert cu.get_defining_class(ChildClass.class_method) is ChildClass 53 | assert cu.get_defining_class(ChildClass.static_meth) is ChildClass 54 | 55 | # validate bound class methods 56 | assert cu.get_defining_class(parent_instance.func_with_overrides) is ParentClass 57 | assert cu.get_defining_class(parent_instance.parent_only_func) is ParentClass 58 | assert cu.get_defining_class(child_instance.func_with_overrides) is ChildClass 59 | assert cu.get_defining_class(child_instance.parent_only_func) is ParentClass 60 | assert cu.get_defining_class(child_instance.child_function) is ChildClass 61 | assert cu.get_defining_class(child_instance.class_method) is ChildClass 62 | assert cu.get_defining_class(child_instance.static_meth) is ChildClass 63 | 64 | # bare functions resolve to nothing 65 | assert cu.get_defining_class(func_not_in_class) is None 66 | 67 | # lambdas and nested lambdas 68 | assert cu.get_defining_class(ChildClass.lambda1) is ChildClass 69 | assert cu.get_defining_class(ChildClass.lambda2) is ChildClass 70 | assert cu.get_defining_class(ChildClass().lambda1) is ChildClass 71 | assert cu.get_defining_class(ChildClass().lambda2) is ChildClass 72 | 73 | # partials 74 | partial_unbound = functools.partial(ParentClass.parent_only_func, 1) 75 | nested_partial_unbound = functools.partial(partial_unbound, 2) 76 | assert cu.get_defining_class(partial_unbound) is ParentClass 77 | assert cu.get_defining_class(nested_partial_unbound) is ParentClass 78 | 79 | partial_bound = functools.partial(parent_instance.parent_only_func, 1) 80 | nested_partial_bound = functools.partial(partial_bound, 2) 81 | assert cu.get_defining_class(partial_bound) is ParentClass 82 | assert cu.get_defining_class(nested_partial_bound) is ParentClass 83 | -------------------------------------------------------------------------------- /tests/transcripts/bol_eol.txt: -------------------------------------------------------------------------------- 1 | # match the text with regular expressions and the newlines as literal text 2 | 3 | (Cmd) say -r 3 -s yabba dabba do 4 | /^Y.*?$/ 5 | /^Y.*?$/ 6 | /^Y.*?$/ 7 | -------------------------------------------------------------------------------- /tests/transcripts/characterclass.txt: -------------------------------------------------------------------------------- 1 | # match using character classes and special sequence for digits (\d) 2 | 3 | (Cmd) say 555-1212 4 | /[0-9]{3}-[0-9]{4}/ 5 | (Cmd) say 555-1212 6 | /\d{3}-\d{4}/ 7 | -------------------------------------------------------------------------------- /tests/transcripts/dotstar.txt: -------------------------------------------------------------------------------- 1 | # ensure the old standby .* works. We use the non-greedy flavor 2 | 3 | (Cmd) say Adopt the pace of nature: her secret is patience. 4 | Adopt the pace of /.*?/ is patience. 5 | -------------------------------------------------------------------------------- /tests/transcripts/extension_notation.txt: -------------------------------------------------------------------------------- 1 | # inception: a regular expression that matches itself 2 | 3 | (Cmd) say (?:fred) 4 | /(?:\(\?:fred\))/ 5 | -------------------------------------------------------------------------------- /tests/transcripts/failure.txt: -------------------------------------------------------------------------------- 1 | # This is an example of a transcript test which will fail 2 | 3 | (Cmd) say -r 3 -s yabba dabba do 4 | foo bar baz 5 | -------------------------------------------------------------------------------- /tests/transcripts/from_cmdloop.txt: -------------------------------------------------------------------------------- 1 | # responses with trailing spaces have been matched with a regex 2 | # so you can see where they are. 3 | 4 | (Cmd) help say 5 | Usage: speak [-h] [-p] [-s] [-r REPEAT]/ */ 6 | 7 | Repeats what you tell me to./ */ 8 | 9 | optional arguments:/ */ 10 | -h, --help show this help message and exit/ */ 11 | -p, --piglatin atinLay/ */ 12 | -s, --shout N00B EMULATION MODE/ */ 13 | -r, --repeat REPEAT output [n] times/ */ 14 | 15 | (Cmd) say goodnight, Gracie 16 | goodnight, Gracie 17 | (Cmd) say -ps --repeat=5 goodnight, Gracie 18 | OODNIGHT, GRACIEGAY 19 | OODNIGHT, GRACIEGAY 20 | OODNIGHT, GRACIEGAY 21 | (Cmd) set maxrepeats 5 22 | maxrepeats - was: 3 23 | now: 5 24 | (Cmd) say -ps --repeat=5 goodnight, Gracie 25 | OODNIGHT, GRACIEGAY 26 | OODNIGHT, GRACIEGAY 27 | OODNIGHT, GRACIEGAY 28 | OODNIGHT, GRACIEGAY 29 | OODNIGHT, GRACIEGAY 30 | (Cmd) history 31 | 1 help say 32 | 2 say goodnight, Gracie 33 | 3 say -ps --repeat=5 goodnight, Gracie 34 | 4 set maxrepeats 5 35 | 5 say -ps --repeat=5 goodnight, Gracie 36 | (Cmd) history -r 3 37 | OODNIGHT, GRACIEGAY 38 | OODNIGHT, GRACIEGAY 39 | OODNIGHT, GRACIEGAY 40 | OODNIGHT, GRACIEGAY 41 | OODNIGHT, GRACIEGAY 42 | (Cmd) set debug True 43 | debug - was: False/ */ 44 | now: True/ */ 45 | -------------------------------------------------------------------------------- /tests/transcripts/multiline_no_regex.txt: -------------------------------------------------------------------------------- 1 | # test a multi-line command 2 | 3 | (Cmd) orate This is a test 4 | > of the 5 | > emergency broadcast system 6 | This is a test of the emergency broadcast system 7 | -------------------------------------------------------------------------------- /tests/transcripts/multiline_regex.txt: -------------------------------------------------------------------------------- 1 | # these regular expressions match multiple lines of text 2 | 3 | (Cmd) say -r 3 -s yabba dabba do 4 | /\A(YA.*?DO\n?){3}/ 5 | (Cmd) say -r 5 -s yabba dabba do 6 | /\A([A-Z\s]*$){3}/ 7 | -------------------------------------------------------------------------------- /tests/transcripts/no_output.txt: -------------------------------------------------------------------------------- 1 | # ensure the transcript can play a command with no output from a command somewhere in the middle 2 | 3 | (Cmd) say something 4 | something 5 | (Cmd) nothing 6 | (Cmd) say something else 7 | something else 8 | -------------------------------------------------------------------------------- /tests/transcripts/no_output_last.txt: -------------------------------------------------------------------------------- 1 | # ensure the transcript can play a command with no output from the last command 2 | 3 | (Cmd) say something 4 | something 5 | (Cmd) say something else 6 | something else 7 | (Cmd) nothing 8 | -------------------------------------------------------------------------------- /tests/transcripts/regex_set.txt: -------------------------------------------------------------------------------- 1 | # Run this transcript with "python example.py -t transcript_regex.txt" 2 | # The regex for colors shows all possible settings for colors 3 | # The regex for editor will match whatever program you use. 4 | # Regexes on prompts just make the trailing space obvious 5 | 6 | (Cmd) set allow_style Terminal 7 | allow_style - was: '/.*/' 8 | now: 'Terminal' 9 | (Cmd) set editor vim 10 | editor - was: '/.*/' 11 | now: 'vim' 12 | (Cmd) set 13 | Name Value Description/ +/ 14 | ==================================================================================================================== 15 | allow_style Terminal Allow ANSI text style sequences in output (valid values:/ +/ 16 | Always, Never, Terminal)/ +/ 17 | always_show_hint False Display tab completion hint even when completion suggestions 18 | print/ +/ 19 | debug False Show full traceback on exception/ +/ 20 | echo False Echo command issued into output/ +/ 21 | editor vim Program used by 'edit'/ +/ 22 | feedback_to_output False Include nonessentials in '|', '>' results/ +/ 23 | max_completion_items 50 Maximum number of CompletionItems to display during tab/ +/ 24 | completion/ +/ 25 | maxrepeats 3 Max number of `--repeat`s allowed/ +/ 26 | quiet False Don't print nonessential feedback/ +/ 27 | scripts_add_to_history True Scripts and pyscripts add commands to history/ +/ 28 | timing False Report execution times/ +/ 29 | -------------------------------------------------------------------------------- /tests/transcripts/singleslash.txt: -------------------------------------------------------------------------------- 1 | # even if you only have a single slash, you have 2 | # to escape it 3 | 4 | (Cmd) say use 2/3 cup of sugar 5 | use 2\/3 cup of sugar 6 | -------------------------------------------------------------------------------- /tests/transcripts/slashes_escaped.txt: -------------------------------------------------------------------------------- 1 | # escape those slashes 2 | 3 | (Cmd) say /some/unix/path 4 | \/some\/unix\/path 5 | (Cmd) say mix 2/3 c. sugar, 1/2 c. butter, and 1/2 tsp. salt 6 | mix 2\/3 c. sugar, 1\/2 c. butter, and 1\/2 tsp. salt 7 | -------------------------------------------------------------------------------- /tests/transcripts/slashslash.txt: -------------------------------------------------------------------------------- 1 | # ensure consecutive slashes are parsed correctly 2 | 3 | (Cmd) say // 4 | \/\/ 5 | -------------------------------------------------------------------------------- /tests/transcripts/spaces.txt: -------------------------------------------------------------------------------- 1 | # check spaces in all their forms 2 | 3 | (Cmd) say how many spaces 4 | how many spaces 5 | (Cmd) say how many spaces 6 | how/\s{1}/many/\s{1}/spaces 7 | (Cmd) say "how many spaces" 8 | how/\s+/many/\s+/spaces 9 | -------------------------------------------------------------------------------- /tests/transcripts/word_boundaries.txt: -------------------------------------------------------------------------------- 1 | # use word boundaries to check for key words in the output 2 | 3 | (Cmd) mumble maybe we could go to lunch 4 | /.*\bmaybe\b.*\bcould\b.*\blunch\b.*/ 5 | (Cmd) mumble maybe we could go to lunch 6 | /.*\bmaybe\b.*\bcould\b.*\blunch\b.*/ 7 | -------------------------------------------------------------------------------- /tests_isolated/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cmd2/cmd2/28f226acbea4c1043449b0de72ca2c8bdebf3a56/tests_isolated/__init__.py -------------------------------------------------------------------------------- /tests_isolated/test_commandset/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-cmd2/cmd2/28f226acbea4c1043449b0de72ca2c8bdebf3a56/tests_isolated/test_commandset/__init__.py --------------------------------------------------------------------------------