diff options
42 files changed, 143 insertions, 63 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 06d4299b..1175618a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -65,7 +65,7 @@ The tables below list all prerequisites along with the minimum required version #### Additional prerequisites to build cmd2 documentation | Prerequisite | Minimum Version | | ------------------------------------------- | --------------- | -| [sphinx](http://www.sphinx-doc.org) | `1.4.9` | +| [sphinx](http://www.sphinx-doc.org) | `2.0.0` | | [sphinx-rtd-theme](https://github.com/snide/sphinx_rtd_theme) | `0.1.9` | #### Optional prerequisites for enhanced unit test features @@ -211,7 +211,7 @@ pipenv install --dev To create a new virtualenv, using a specific version of Python you have installed (and on your PATH), use the --python VERSION flag, like so: ```sh -pipenv install --dev --python 3.7 +pipenv install --dev --python 3.8 ``` Then you can enter that virtual environment with: @@ -221,8 +221,8 @@ pipenv shell #### Create a new environment for cmd2 using Conda ```sh -$ conda create -n cmd2_py36 python=3.6 -$ conda activate cmd2_py36 +$ conda create -n cmd2_py37 python=3.7 +$ conda activate cmd2_py37 ``` #### Create a new environment for cmd using Virtualenv @@ -233,13 +233,13 @@ We recommend that you use [pyenv](https://github.com/pyenv/pyenv) to manage your pyenv versions # Install python version defined -pyenv install 3.6.3 +pyenv install 3.8.2 ``` With the Python version installed, you can set the virtualenv properly. ```sh $ cd ~/src/cmd2 -$ virtualenv -p $(pyenv root)/versions/3.6.3/ cmd_py36 +$ virtualenv -p $(pyenv root)/versions/3.8.2/ cmd_py38 $ source ~/src/cmd2/bin/activate ``` @@ -544,6 +544,33 @@ excellent support for debugging console applications. [PyCharm](https://www.jetbrains.com/pycharm/) is also quite good and has very nice [code inspection](https://www.jetbrains.com/help/pycharm/code-inspection.html) capabilities. +## Branching Strategy and Semantic Versioning + +Starting with version 1.0.0, `cmd2` has adopted [Semantic Versioning](https://semver.org). + +### Semantic Versioning Summary +Given a version number `MAJOR`.`MINOR`.`PATCH`, increment the: + +- `MAJOR` version when you make incompatible API changes, +- `MINOR` version when you add functionality in a backwards compatible manner, and +- `PATCH` version when you make backwards compatible bug fixes. + +### Branching Strategy + +We use the **master** branch for the upcoming `PATCH` release - i.e. if the current version +of `cmd2` is 1.0.2, then the **master** branch contains code which is planned for release +in 1.0.3. + +If work needs to be done for a `MAJOR` or `MINOR` release when we anticipate there will be +a `PATCH` release in-between, then a branch should be created named for the appropriate version +number for the work, e.g. if the current release of `cmd2` is 1.0.2 and a backwards-incompatible +change needs to be committed for an upcoming `MAJOR` release, then this work should be committed +to a **2.0.0** branch until such a time as we are ready to release version 2.0.0. + +Following this strategy, releases are always done from the **master** branch and `MAJOR` or `MINOR` +branches are merged to **master** immediately prior to doing a release. Once merged to **master**, the +other branches can be deleted. All releases are tagged so that they can be reproduced if necessary. + ## Publishing a new release Since 0.9.2, the process of publishing a new release of `cmd2` to [PyPi](https://pypi.org/) has been @@ -18,6 +18,7 @@ flake8 = "*" gnureadline = {version = "*",sys_platform = "== 'darwin'"} invoke = "*" ipython = "*" +isort = "*" mock = {version = "*",markers = "python_version < '3.6'"} plumbum = "*" pyreadline = {version = "*",sys_platform = "== 'win32'"} diff --git a/cmd2/ansi.py b/cmd2/ansi.py index a6c09413..f172b87f 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -6,10 +6,10 @@ setting the window title, and asynchronous alerts. import functools import re from enum import Enum -from typing import Any, IO, List, Union +from typing import IO, Any, List, Union import colorama -from colorama import Fore, Back, Style +from colorama import Back, Fore, Style from wcwidth import wcswidth # On Windows, filter ANSI escape codes out of text sent to stdout/stderr, and replace them with equivalent Win32 calls diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index f61f5fd8..61f173cc 100644 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -14,10 +14,16 @@ from collections import deque from typing import Dict, List, Optional, Union from . import ansi, cmd2, constants -from .argparse_custom import ATTR_CHOICES_CALLABLE, generate_range_error -from .argparse_custom import ATTR_SUPPRESS_TAB_HINT, ATTR_DESCRIPTIVE_COMPLETION_HEADER, ATTR_NARGS_RANGE -from .argparse_custom import ChoicesCallable, CompletionItem -from .utils import basic_complete, CompletionError +from .argparse_custom import ( + ATTR_CHOICES_CALLABLE, + ATTR_DESCRIPTIVE_COMPLETION_HEADER, + ATTR_NARGS_RANGE, + ATTR_SUPPRESS_TAB_HINT, + ChoicesCallable, + CompletionItem, + generate_range_error, +) +from .utils import CompletionError, basic_complete # If no descriptive header is supplied, then this will be used instead DEFAULT_DESCRIPTIVE_HEADER = 'Description' diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index f018f33f..485f65c2 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -203,7 +203,7 @@ import argparse import re import sys # noinspection PyUnresolvedReferences,PyProtectedMember -from argparse import ZERO_OR_MORE, ONE_OR_MORE, ArgumentError, _ +from argparse import ONE_OR_MORE, ZERO_OR_MORE, ArgumentError, _ from typing import Callable, Optional, Tuple, Type, Union from . import ansi, constants diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index d14b4d99..49c181f1 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -42,17 +42,14 @@ from collections import namedtuple from contextlib import redirect_stdout from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, Tuple, Type, Union -from . import ansi -from . import constants -from . import plugin -from . import utils -from .argparse_custom import CompletionItem, DEFAULT_ARGUMENT_PARSER +from . import ansi, constants, plugin, utils +from .argparse_custom import DEFAULT_ARGUMENT_PARSER, CompletionItem from .clipboard import can_clip, get_paste_buffer, write_to_paste_buffer from .decorators import with_argparser from .exceptions import Cmd2ArgparseError, Cmd2ShlexError, EmbeddedConsoleExit, EmptyStatement, RedirectionError from .history import History, HistoryItem -from .parsing import StatementParser, Statement, Macro, MacroArg, shlex_split -from .rl_utils import rl_type, RlType, rl_get_point, rl_set_prompt, vt100_support, rl_make_safe_prompt, rl_warning +from .parsing import Macro, MacroArg, Statement, StatementParser, shlex_split +from .rl_utils import RlType, rl_get_point, rl_make_safe_prompt, rl_set_prompt, rl_type, rl_warning, vt100_support from .utils import CompletionError, Settable # Set up readline diff --git a/cmd2/history.py b/cmd2/history.py index 7b52aa16..60a071fb 100644 --- a/cmd2/history.py +++ b/cmd2/history.py @@ -4,7 +4,6 @@ History management classes """ import re - from typing import List, Union import attr diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 71582f1a..a7ee74a1 100755 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -8,8 +8,7 @@ from typing import Dict, Iterable, List, Optional, Tuple, Union import attr -from . import constants -from . import utils +from . import constants, utils from .exceptions import Cmd2ShlexError diff --git a/cmd2/py_bridge.py b/cmd2/py_bridge.py index 0dc04ca6..38fef142 100644 --- a/cmd2/py_bridge.py +++ b/cmd2/py_bridge.py @@ -5,10 +5,10 @@ while maintaining a reasonable degree of isolation between the two. """ import sys -from contextlib import redirect_stdout, redirect_stderr +from contextlib import redirect_stderr, redirect_stdout from typing import Optional -from .utils import namedtuple_with_defaults, StdSim +from .utils import StdSim, namedtuple_with_defaults class CommandResult(namedtuple_with_defaults('CommandResult', ['stdout', 'stderr', 'stop', 'data'])): diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index 4df733db..099d76b7 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -2,8 +2,8 @@ """ Imports the proper readline for the platform and provides utility functions for it """ -from enum import Enum import sys +from enum import Enum # Prefer statically linked gnureadline if available (for macOS compatibility due to issues with libedit) try: diff --git a/docs/conf.py b/docs/conf.py index 9bb2e855..6c6faf0d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,10 +17,9 @@ If extensions (or modules to document with autodoc) are in another directory, add these directories to sys.path here. If the directory is relative to the documentation root, use os.path.abspath to make it absolute, like shown here. """ -from pkg_resources import get_distribution - # Import for custom theme from Read the Docs import sphinx_rtd_theme +from pkg_resources import get_distribution # -- General configuration ----------------------------------------------------- diff --git a/examples/alias_startup.py b/examples/alias_startup.py index 052d1367..3fa9ec5a 100755 --- a/examples/alias_startup.py +++ b/examples/alias_startup.py @@ -5,6 +5,7 @@ 2) How to run an initialization script at startup """ import os + import cmd2 diff --git a/examples/argparse_completion.py b/examples/argparse_completion.py index bf2b2723..e44533b3 100644 --- a/examples/argparse_completion.py +++ b/examples/argparse_completion.py @@ -6,8 +6,8 @@ A simple example demonstrating how to integrate tab completion with argparse-bas import argparse from typing import Dict, List -from cmd2 import Cmd, Cmd2ArgumentParser, with_argparser, CompletionItem -from cmd2.utils import basic_complete, CompletionError +from cmd2 import Cmd, Cmd2ArgumentParser, CompletionItem, with_argparser +from cmd2.utils import CompletionError, basic_complete # Data source for argparse.choices food_item_strs = ['Pizza', 'Ham', 'Ham Sandwich', 'Potato'] diff --git a/examples/async_printing.py b/examples/async_printing.py index 692ce769..a136d8e2 100755 --- a/examples/async_printing.py +++ b/examples/async_printing.py @@ -10,7 +10,7 @@ import time from typing import List import cmd2 -from cmd2 import style, fg +from cmd2 import fg, style ALERTS = ["Watch as this application prints alerts and updates the prompt", "This will only happen when the prompt is present", diff --git a/examples/basic.py b/examples/basic.py index 5df3d1b5..2a1e9a12 100755 --- a/examples/basic.py +++ b/examples/basic.py @@ -9,7 +9,7 @@ 6) Shell-like capabilities """ import cmd2 -from cmd2 import style, fg, bg +from cmd2 import bg, fg, style class BasicApp(cmd2.Cmd): diff --git a/examples/colors.py b/examples/colors.py index 1466471b..8f8e3c6e 100755 --- a/examples/colors.py +++ b/examples/colors.py @@ -26,9 +26,10 @@ Always import argparse from typing import Any +from colorama import Back, Fore, Style + import cmd2 from cmd2 import ansi -from colorama import Fore, Back, Style class CmdLineApp(cmd2.Cmd): diff --git a/examples/custom_parser.py b/examples/custom_parser.py index 7c154176..34c7bee2 100644 --- a/examples/custom_parser.py +++ b/examples/custom_parser.py @@ -4,8 +4,7 @@ Defines the CustomParser used with override_parser.py example """ import sys -from cmd2 import Cmd2ArgumentParser, set_default_argument_parser -from cmd2 import ansi +from cmd2 import Cmd2ArgumentParser, ansi, set_default_argument_parser # First define the parser diff --git a/examples/exit_code.py b/examples/exit_code.py index f4b19091..89ed86cd 100755 --- a/examples/exit_code.py +++ b/examples/exit_code.py @@ -2,9 +2,10 @@ # coding=utf-8 """A simple example demonstrating the following how to emit a non-zero exit code in your cmd2 application. """ -import cmd2 from typing import List +import cmd2 + class ReplWithExitCode(cmd2.Cmd): """ Example cmd2 application where we can specify an exit code when existing.""" diff --git a/examples/hooks.py b/examples/hooks.py index 409c8979..f8079e58 100755 --- a/examples/hooks.py +++ b/examples/hooks.py @@ -10,7 +10,6 @@ follow a command without any intervening whitespace. """ import re - from typing import List import cmd2 diff --git a/examples/initialization.py b/examples/initialization.py index 609255f1..50bb73aa 100755 --- a/examples/initialization.py +++ b/examples/initialization.py @@ -13,7 +13,7 @@ 10) How to make custom attributes settable at runtime """ import cmd2 -from cmd2 import style, fg, bg +from cmd2 import bg, fg, style class BasicApp(cmd2.Cmd): diff --git a/examples/migrating.py b/examples/migrating.py index 3a25b8c8..86a59ed6 100755 --- a/examples/migrating.py +++ b/examples/migrating.py @@ -3,9 +3,8 @@ """ A sample application for cmd which can be used to show how to migrate to cmd2. """ -import random - import cmd +import random class CmdLineApp(cmd.Cmd): diff --git a/examples/override_parser.py b/examples/override_parser.py index eecb0e88..b6548388 100755 --- a/examples/override_parser.py +++ b/examples/override_parser.py @@ -10,12 +10,14 @@ The following code shows how to override it with your own parser class. # See the code for custom_parser.py. It simply defines a parser and calls cmd2.set_default_argument_parser() # with the custom parser's type. import argparse -argparse.cmd2_parser_module = 'examples.custom_parser' # Next import stuff from cmd2. It will import your module just before the cmd2.Cmd class file is imported # and therefore override the parser class it uses on its commands. from cmd2 import cmd2 +argparse.cmd2_parser_module = 'examples.custom_parser' + + if __name__ == '__main__': import sys app = cmd2.Cmd(use_ipython=True, persistent_history_file='cmd2_history.dat') diff --git a/examples/plumbum_colors.py b/examples/plumbum_colors.py index 2887ef1f..ed65f245 100755 --- a/examples/plumbum_colors.py +++ b/examples/plumbum_colors.py @@ -27,9 +27,10 @@ WARNING: This example requires the plumbum package, which isn't normally require """ import argparse +from plumbum.colors import bg, fg + import cmd2 from cmd2 import ansi -from plumbum.colors import fg, bg class FgColors(ansi.ColorBase): diff --git a/examples/scripts/arg_printer.py b/examples/scripts/arg_printer.py index 19f6dd3f..f5f30c6d 100755 --- a/examples/scripts/arg_printer.py +++ b/examples/scripts/arg_printer.py @@ -2,6 +2,7 @@ # coding=utf-8 import os import sys + print("Running Python script {!r} which was called with {} arguments".format(os.path.basename(sys.argv[0]), len(sys.argv) - 1)) for i, arg in enumerate(sys.argv[1:]): diff --git a/examples/subcommands.py b/examples/subcommands.py index 4f569b1e..dd69d97e 100755 --- a/examples/subcommands.py +++ b/examples/subcommands.py @@ -7,6 +7,7 @@ This example shows an easy way for a single command to have many subcommands, ea and provides separate contextual help. """ import argparse + import cmd2 sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball'] diff --git a/examples/unicode_commands.py b/examples/unicode_commands.py index f8381e50..0a7c5ac7 100755 --- a/examples/unicode_commands.py +++ b/examples/unicode_commands.py @@ -3,6 +3,7 @@ """A simple example demonstrating support for unicode command names. """ import math + import cmd2 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..f60e3aed --- /dev/null +++ b/setup.cfg @@ -0,0 +1,17 @@ +[flake8] +exclude = .git,.idea,.pytest_cache,.tox,.venv,.vscode,build,cmd2.egg-info,dist,htmlcov,__pycache__,*.egg +max-line-length = 127 +max-complexity = 26 + +[isort] +line_length=127 +skip=cmd2/__init__.py +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true + +[doc8] +ignore-path=docs/_build,.git,.idea,.pytest_cache,.tox,.venv,.vscode,build,cmd2,examples,tests,cmd2.egg-info,dist,htmlcov,__pycache__,*.egg +;max-line-length=99 +verbose=0 @@ -4,6 +4,7 @@ Setuptools setup file, used to install or test 'cmd2' """ import codecs + from setuptools import setup DESCRIPTION = "cmd2 - quickly build feature-rich and user-friendly interactive command line applications in Python" @@ -15,6 +15,7 @@ import sys import invoke + # shared function def rmrf(items, verbose=True): "Silently remove a list of directories or files" diff --git a/tests/conftest.py b/tests/conftest.py index 9ee8da19..60074f5c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,7 @@ Cmd2 unit/functional testing """ import sys -from contextlib import redirect_stdout, redirect_stderr +from contextlib import redirect_stderr, redirect_stdout from typing import List, Optional, Union from unittest import mock @@ -12,7 +12,6 @@ from pytest import fixture import cmd2 from cmd2.utils import StdSim - # Prefer statically linked gnureadline if available (for macOS compatibility due to issues with libedit) try: import gnureadline as readline diff --git a/tests/pyscript/environment.py b/tests/pyscript/environment.py index 5e4d93d6..f1182c82 100644 --- a/tests/pyscript/environment.py +++ b/tests/pyscript/environment.py @@ -2,6 +2,7 @@ # Tests that cmd2 populates __name__, __file__, and sets sys.path[0] to our directory import os import sys + app.cmd_echo = True if __name__ != '__main__': diff --git a/tests/test_argparse.py b/tests/test_argparse.py index db6dea21..daf43497 100644 --- a/tests/test_argparse.py +++ b/tests/test_argparse.py @@ -9,6 +9,7 @@ from typing import Optional import pytest import cmd2 + from .conftest import run_cmd # Prefer statically linked gnureadline if available (for macOS compatibility due to issues with libedit) diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py index 9e635a42..4313647b 100644 --- a/tests/test_argparse_completer.py +++ b/tests/test_argparse_completer.py @@ -9,9 +9,10 @@ from typing import List import pytest import cmd2 -from cmd2 import with_argparser, Cmd2ArgumentParser, CompletionItem +from cmd2 import Cmd2ArgumentParser, CompletionItem, with_argparser from cmd2.utils import CompletionError, StdSim, basic_complete -from .conftest import run_cmd, complete_tester + +from .conftest import complete_tester, run_cmd # Lists used in our tests (there is a mix of sorted and unsorted on purpose) static_int_choices_list = [-1, 1, -2, 2, 0, -12] diff --git a/tests/test_argparse_custom.py b/tests/test_argparse_custom.py index 92b2ecb4..f4db12b6 100644 --- a/tests/test_argparse_custom.py +++ b/tests/test_argparse_custom.py @@ -9,6 +9,7 @@ import pytest import cmd2 from cmd2 import Cmd2ArgumentParser, constants from cmd2.argparse_custom import generate_range_error + from .conftest import run_cmd diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index fdeea9aa..33f75c9e 100755 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -13,16 +13,27 @@ from code import InteractiveConsole import pytest +import cmd2 +from cmd2 import COMMAND_NAME, ansi, clipboard, constants, exceptions, plugin, utils + +from .conftest import ( + HELP_HISTORY, + SHORTCUTS_TXT, + SHOW_LONG, + SHOW_TXT, + complete_tester, + normalize, + odd_file_names, + run_cmd, + verify_help_text, +) + # Python 3.5 had some regressions in the unitest.mock module, so use 3rd party mock if available try: import mock except ImportError: from unittest import mock -import cmd2 -from cmd2 import ansi, clipboard, constants, exceptions, plugin, utils, COMMAND_NAME -from .conftest import (run_cmd, normalize, verify_help_text, HELP_HISTORY, SHORTCUTS_TXT, SHOW_TXT, - SHOW_LONG, complete_tester, odd_file_names) def CreateOutsimApp(): c = cmd2.Cmd() diff --git a/tests/test_completion.py b/tests/test_completion.py index e13d87de..a380d43a 100755 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -22,6 +22,7 @@ import pytest import cmd2 from cmd2 import utils from examples.subcommands import SubcommandsExample + from .conftest import complete_tester, normalize, run_cmd # List of strings used with completion functions diff --git a/tests/test_history.py b/tests/test_history.py index 11f189f6..6fa16ad8 100755 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -3,22 +3,23 @@ """ Test history functions of cmd2 """ -import tempfile import os +import tempfile import pytest +import cmd2 # Python 3.5 had some regressions in the unitest.mock module, so use # 3rd party mock if available from cmd2.parsing import StatementParser +from .conftest import HELP_HISTORY, normalize, run_cmd + try: import mock except ImportError: from unittest import mock -import cmd2 -from .conftest import run_cmd, normalize, HELP_HISTORY # diff --git a/tests/test_parsing.py b/tests/test_parsing.py index 5f363320..c2c242fe 100755 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -10,6 +10,7 @@ import cmd2 from cmd2 import constants, exceptions, utils from cmd2.parsing import StatementParser, shlex_split + @pytest.fixture def parser(): parser = StatementParser( diff --git a/tests/test_plugin.py b/tests/test_plugin.py index bb7753f0..132361a6 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -8,14 +8,15 @@ import sys import pytest +import cmd2 +from cmd2 import Cmd2ArgumentParser, exceptions, plugin, with_argparser + # Python 3.5 had some regressions in the unitest.mock module, so use 3rd party mock if available try: import mock except ImportError: from unittest import mock -import cmd2 -from cmd2 import exceptions, plugin, Cmd2ArgumentParser, with_argparser class Plugin: diff --git a/tests/test_run_pyscript.py b/tests/test_run_pyscript.py index 6110e3ac..8cfd8578 100644 --- a/tests/test_run_pyscript.py +++ b/tests/test_run_pyscript.py @@ -9,7 +9,8 @@ import os import pytest from cmd2 import plugin, utils -from .conftest import run_cmd, odd_file_names + +from .conftest import odd_file_names, run_cmd # Python 3.5 had some regressions in the unitest.mock module, so use 3rd party mock if available try: diff --git a/tests/test_table_creator.py b/tests/test_table_creator.py index 917ba5cc..ed53efa6 100644 --- a/tests/test_table_creator.py +++ b/tests/test_table_creator.py @@ -6,8 +6,15 @@ Unit testing for cmd2/table_creator.py module import pytest from cmd2 import ansi -from cmd2.table_creator import (AlternatingTable, BorderedTable, Column, HorizontalAlignment, - SimpleTable, TableCreator, VerticalAlignment) +from cmd2.table_creator import ( + AlternatingTable, + BorderedTable, + Column, + HorizontalAlignment, + SimpleTable, + TableCreator, + VerticalAlignment, +) def test_column_creation(): diff --git a/tests/test_transcript.py b/tests/test_transcript.py index aa6f8c4e..69389b7f 100644 --- a/tests/test_transcript.py +++ b/tests/test_transcript.py @@ -5,18 +5,19 @@ Cmd2 functional testing based on transcript """ import argparse import os -import sys -import re import random +import re +import sys import tempfile - from unittest import mock + import pytest import cmd2 -from .conftest import run_cmd, verify_help_text from cmd2 import transcript -from cmd2.utils import StdSim, Settable +from cmd2.utils import Settable, StdSim + +from .conftest import run_cmd, verify_help_text class CmdLineApp(cmd2.Cmd): |