Skip to content

Commit 0b64604

Browse files
committed
Added type annotations for fields in cmd2.Cmd.
Cleaned up docstring in external test plugin Updated some initialization to match new approach for version info discovery. Tagged some IDE-only lines as no-cover Adds plugin coverage reporting.
1 parent 2df28cb commit 0b64604

File tree

9 files changed

+66
-45
lines changed

9 files changed

+66
-45
lines changed

Diff for: .coveragerc

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# .coveragerc to control coverage.py
22
[run]
33
# Source
4-
source = cmd2/
4+
source = plugins/*/cmd2_*/
5+
cmd2/
56
# (boolean, default False): whether to measure branch coverage in addition to statement coverage.
67
branch = False
78

Diff for: cmd2/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import importlib_metadata
1212
try:
1313
__version__ = importlib_metadata.version(__name__)
14-
except importlib_metadata.PackageNotFoundError:
14+
except importlib_metadata.PackageNotFoundError: # pragma: no cover
1515
# package is not installed
1616
pass
1717

@@ -31,6 +31,7 @@
3131
from .constants import COMMAND_NAME, DEFAULT_SHORTCUTS
3232
from .decorators import with_argument_list, with_argparser, with_argparser_and_unknown_args, with_category
3333
from .exceptions import Cmd2ArgparseError, SkipPostcommandHooks
34+
from . import plugin
3435
from .parsing import Statement
3536
from .py_bridge import CommandResult
3637
from .utils import categorize, CompletionError, Settable

Diff for: cmd2/cmd2.py

+27-26
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, *,
200200
self.max_completion_items = 50
201201

202202
# A dictionary mapping settable names to their Settable instance
203-
self.settables = dict()
203+
self.settables = dict() # type: Dict[str, Settable]
204204
self.build_settables()
205205

206206
# Use as prompt for multiline commands on the 2nd+ line of input
@@ -220,7 +220,7 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, *,
220220
self.exclude_from_history = ['eof', 'history']
221221

222222
# Dictionary of macro names and their values
223-
self.macros = dict()
223+
self.macros = dict() # type: Dict[str, Macro]
224224

225225
# Keeps track of typed command history in the Python shell
226226
self._py_history = []
@@ -249,14 +249,14 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, *,
249249
self.last_result = None
250250

251251
# Used by run_script command to store current script dir as a LIFO queue to support _relative_run_script command
252-
self._script_dir = []
252+
self._script_dir = [] # type: List[str]
253253

254254
# Context manager used to protect critical sections in the main thread from stopping due to a KeyboardInterrupt
255255
self.sigint_protection = utils.ContextFlag()
256256

257257
# If the current command created a process to pipe to, then this will be a ProcReader object.
258258
# Otherwise it will be None. It's used to know when a pipe process can be killed and/or waited upon.
259-
self._cur_pipe_proc_reader = None
259+
self._cur_pipe_proc_reader = None # type: Optional[utils.ProcReader]
260260

261261
# Used to keep track of whether we are redirecting or piping output
262262
self._redirecting = False
@@ -280,7 +280,7 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, *,
280280
self.broken_pipe_warning = ''
281281

282282
# Commands that will run at the beginning of the command loop
283-
self._startup_commands = []
283+
self._startup_commands = [] # type: List[str]
284284

285285
# If a startup script is provided and exists, then execute it in the startup commands
286286
if startup_script:
@@ -289,7 +289,7 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, *,
289289
self._startup_commands.append("run_script {}".format(utils.quote_string(startup_script)))
290290

291291
# Transcript files to run instead of interactive command loop
292-
self._transcript_files = None
292+
self._transcript_files = None # type: Optional[List[str]]
293293

294294
# Check for command line args
295295
if allow_cli_args:
@@ -333,7 +333,7 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, *,
333333
# Commands that have been disabled from use. This is to support commands that are only available
334334
# during specific states of the application. This dictionary's keys are the command names and its
335335
# values are DisabledCommand objects.
336-
self.disabled_commands = dict()
336+
self.disabled_commands = dict() # type: Dict[str, DisabledCommand]
337337

338338
# If any command has been categorized, then all other commands that haven't been categorized
339339
# will display under this section in the help output.
@@ -1910,7 +1910,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState:
19101910
self._cur_pipe_proc_reader, self._redirecting)
19111911

19121912
# The ProcReader for this command
1913-
cmd_pipe_proc_reader = None
1913+
cmd_pipe_proc_reader = None # type: Optional[utils.ProcReader]
19141914

19151915
if not self.allow_redirection:
19161916
# Don't return since we set some state variables at the end of the function
@@ -2694,16 +2694,31 @@ def do_help(self, args: argparse.Namespace) -> None:
26942694

26952695
def _help_menu(self, verbose: bool = False) -> None:
26962696
"""Show a list of commands which help can be displayed for"""
2697+
cmds_cats, cmds_doc, cmds_undoc, help_topics = self._build_command_info()
2698+
2699+
if len(cmds_cats) == 0:
2700+
# No categories found, fall back to standard behavior
2701+
self.poutput("{}".format(str(self.doc_leader)))
2702+
self._print_topics(self.doc_header, cmds_doc, verbose)
2703+
else:
2704+
# Categories found, Organize all commands by category
2705+
self.poutput('{}'.format(str(self.doc_leader)))
2706+
self.poutput('{}'.format(str(self.doc_header)), end="\n\n")
2707+
for category in sorted(cmds_cats.keys(), key=self.default_sort_key):
2708+
self._print_topics(category, cmds_cats[category], verbose)
2709+
self._print_topics(self.default_category, cmds_doc, verbose)
2710+
2711+
self.print_topics(self.misc_header, help_topics, 15, 80)
2712+
self.print_topics(self.undoc_header, cmds_undoc, 15, 80)
2713+
2714+
def _build_command_info(self):
26972715
# Get a sorted list of help topics
26982716
help_topics = sorted(self.get_help_topics(), key=self.default_sort_key)
2699-
27002717
# Get a sorted list of visible command names
27012718
visible_commands = sorted(self.get_visible_commands(), key=self.default_sort_key)
2702-
27032719
cmds_doc = []
27042720
cmds_undoc = []
27052721
cmds_cats = {}
2706-
27072722
for command in visible_commands:
27082723
func = self.cmd_func(command)
27092724
has_help_func = False
@@ -2724,21 +2739,7 @@ def _help_menu(self, verbose: bool = False) -> None:
27242739
cmds_doc.append(command)
27252740
else:
27262741
cmds_undoc.append(command)
2727-
2728-
if len(cmds_cats) == 0:
2729-
# No categories found, fall back to standard behavior
2730-
self.poutput("{}".format(str(self.doc_leader)))
2731-
self._print_topics(self.doc_header, cmds_doc, verbose)
2732-
else:
2733-
# Categories found, Organize all commands by category
2734-
self.poutput('{}'.format(str(self.doc_leader)))
2735-
self.poutput('{}'.format(str(self.doc_header)), end="\n\n")
2736-
for category in sorted(cmds_cats.keys(), key=self.default_sort_key):
2737-
self._print_topics(category, cmds_cats[category], verbose)
2738-
self._print_topics(self.default_category, cmds_doc, verbose)
2739-
2740-
self.print_topics(self.misc_header, help_topics, 15, 80)
2741-
self.print_topics(self.undoc_header, cmds_undoc, 15, 80)
2742+
return cmds_cats, cmds_doc, cmds_undoc, help_topics
27422743

27432744
def _print_topics(self, header: str, cmds: List[str], verbose: bool) -> None:
27442745
"""Customized version of print_topics that can switch between verbose or traditional output"""

Diff for: noxfile.py

+17-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33

44
@nox.session(python=['3.7'])
55
def docs(session):
6-
session.install('sphinx', 'sphinx-rtd-theme', '.')
6+
session.install('sphinx',
7+
'sphinx-rtd-theme',
8+
'.',
9+
)
710
session.chdir('docs')
811
tmpdir = session.create_tmp()
912

@@ -12,16 +15,25 @@ def docs(session):
1215

1316

1417
@nox.session(python=['3.5', '3.6', '3.7', '3.8', '3.9'])
15-
@nox.parametrize('plugin', [None, 'ext_test', 'template'])
18+
@nox.parametrize('plugin', [None,
19+
'ext_test',
20+
'template',
21+
'coverage'])
1622
def tests(session, plugin):
1723
if plugin is None:
1824
session.install('invoke', './[test]')
1925
session.run('invoke', 'pytest', '--junit', '--no-pty')
26+
elif plugin == 'coverage':
27+
session.install('invoke', 'codecov', 'coverage')
28+
session.run('codecov')
2029
else:
2130
session.install('invoke', '.')
2231

2332
# cd into test directory to run other unit test
2433
session.install('plugins/{}[test]'.format(plugin))
25-
session.run('invoke', 'plugin.{}.pytest'.format(plugin.replace('_', '-')), '--junit', '--no-pty')
26-
27-
session.run('codecov')
34+
session.run('invoke',
35+
'plugin.{}.pytest'.format(plugin.replace('_', '-')),
36+
'--junit',
37+
'--no-pty',
38+
'--append-cov',
39+
)

Diff for: plugins/ext_test/cmd2_ext_test/__init__.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
#
22
# coding=utf-8
3-
"""Description of myplugin
3+
"""cmd2 External Python Testing Mixin
44
5-
An overview of what myplugin does.
5+
Allows developers to exercise their cmd2 application using the PyScript interface
66
"""
77

88
try:
99
# For python 3.8 and later
1010
import importlib.metadata as importlib_metadata
11-
except ImportError:
11+
except ImportError: # pragma: no cover
1212
# For everyone else
1313
import importlib_metadata
1414
try:
1515
__version__ = importlib_metadata.version(__name__)
16-
except importlib_metadata.PackageNotFoundError:
16+
except importlib_metadata.PackageNotFoundError: # pragma: no cover
1717
# package is not installed
1818
__version__ = 'unknown'
1919

Diff for: plugins/ext_test/cmd2_ext_test/cmd2_ext_test.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import cmd2
88

9-
if TYPE_CHECKING:
9+
if TYPE_CHECKING: # pragma: no cover
1010
_Base = cmd2.Cmd
1111
else:
1212
_Base = object

Diff for: plugins/tasks.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
88
- wheel >= 0.31.0
99
- setuptools >= 39.1.0
1010
"""
11-
import os
1211
import invoke
1312

1413
from plugins.ext_test import tasks as ext_test_tasks
1514
from plugins.template import tasks as template_tasks
1615

1716
# create namespaces
18-
namespace = invoke.Collection(ext_test=ext_test_tasks, template=template_tasks)
17+
namespace = invoke.Collection(ext_test=ext_test_tasks,
18+
template=template_tasks,
19+
)
1920
namespace_clean = invoke.Collection('clean')
2021
namespace.add_collection(namespace_clean, 'clean')
2122

Diff for: plugins/template/cmd2_myplugin/__init__.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@
55
An overview of what myplugin does.
66
"""
77

8-
from pkg_resources import get_distribution, DistributionNotFound
9-
108
from .myplugin import empty_decorator, MyPluginMixin # noqa: F401
119

1210
try:
13-
__version__ = get_distribution(__name__).version
14-
except DistributionNotFound:
11+
# For python 3.8 and later
12+
import importlib.metadata as importlib_metadata
13+
except ImportError: # pragma: no cover
14+
# For everyone else
15+
import importlib_metadata
16+
try:
17+
__version__ = importlib_metadata.version(__name__)
18+
except importlib_metadata.PackageNotFoundError: # pragma: no cover
19+
# package is not installed
1520
__version__ = 'unknown'

Diff for: plugins/template/cmd2_myplugin/myplugin.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import cmd2
99

10-
if TYPE_CHECKING:
10+
if TYPE_CHECKING: # pragma: no cover
1111
_Base = cmd2.Cmd
1212
else:
1313
_Base = object

0 commit comments

Comments
 (0)