diff options
-rwxr-xr-x | cmd2.py | 34 | ||||
-rwxr-xr-x | setup.py | 6 | ||||
-rw-r--r-- | tests/test_argparse.py | 40 | ||||
-rw-r--r-- | tests/test_cmd2.py | 30 | ||||
-rw-r--r-- | tests/test_completion.py | 6 |
5 files changed, 58 insertions, 58 deletions
@@ -92,8 +92,12 @@ except ImportError: # BrokenPipeError is only in Python 3. Use IOError for Python 2. if six.PY3: BROKEN_PIPE_ERROR = BrokenPipeError + + # redirect_stdout and redirect_stderr weren't added to contextlib until Python 3.4 + from contextlib import redirect_stdout, redirect_stderr else: BROKEN_PIPE_ERROR = IOError + from contextlib2 import redirect_stdout, redirect_stderr # On some systems, pyperclip will import gtk for its clipboard functionality. # The following code is a workaround for gtk interfering with printing from a background @@ -668,6 +672,9 @@ class Cmd(cmd.Cmd): # Used when piping command output to a shell command self.pipe_proc = None + # Used by complete() for readline tab completion + self.completion_matches = [] + # ----- Methods related to presenting output to the user ----- @property @@ -771,13 +778,14 @@ class Cmd(cmd.Cmd): return cmd_completion + # noinspection PyUnusedLocal def complete_subcommand(self, text, line, begidx, endidx): """Readline tab-completion method for completing argparse sub-command names.""" - cmd, args, foo = self.parseline(line) + command, args, foo = self.parseline(line) arglist = args.split() - if len(arglist) <= 1 and cmd + ' ' + args == line: - funcname = self._func_named(cmd) + if len(arglist) <= 1 and command + ' ' + args == line: + funcname = self._func_named(command) if funcname: # Check to see if this function was decorated with an argparse ArgumentParser func = getattr(self, funcname) @@ -799,7 +807,7 @@ class Cmd(cmd.Cmd): return [] def complete(self, text, state): - """Override of cmd method which returns the next possible completion for 'text'. + """Override of command method which returns the next possible completion for 'text'. If a command has not been entered, then complete against command list. Otherwise try to call complete_<command> to get list of completions. @@ -819,17 +827,17 @@ class Cmd(cmd.Cmd): stripped = len(origline) - len(line) begidx = readline.get_begidx() - stripped endidx = readline.get_endidx() - stripped - if begidx>0: - cmd, args, foo = self.parseline(line) - if cmd == '': + if begidx > 0: + command, args, foo = self.parseline(line) + if command == '': compfunc = self.completedefault else: arglist = args.split() compfunc = None # If the user has entered no more than a single argument after the command name - if len(arglist) <= 1 and cmd + ' ' + args == line: - funcname = self._func_named(cmd) + if len(arglist) <= 1 and command + ' ' + args == line: + funcname = self._func_named(command) if funcname: # Check to see if this function was decorated with an argparse ArgumentParser func = getattr(self, funcname) @@ -842,7 +850,7 @@ class Cmd(cmd.Cmd): if compfunc is None: # This command either doesn't have sub-commands or the user is past the point of entering one try: - compfunc = getattr(self, 'complete_' + cmd) + compfunc = getattr(self, 'complete_' + command) except AttributeError: compfunc = self.completedefault else: @@ -1319,7 +1327,11 @@ class Cmd(cmd.Cmd): # Function has an argparser, so get help based on all the arguments in case there are sub-commands new_arglist = arglist[1:] new_arglist.append('-h') - func(new_arglist) + + # Temporarily redirect all argparse output to both sys.stdout and sys.stderr to self.stdout + with redirect_stdout(self.stdout): + with redirect_stderr(self.stdout): + func(new_arglist) else: # No special behavior needed, delegate to cmd base class do_help() cmd.Cmd.do_help(self, funcname[3:]) @@ -62,9 +62,15 @@ Topic :: Software Development :: Libraries :: Python Modules """.splitlines()))) INSTALL_REQUIRES = ['pyparsing >= 2.0.1', 'pyperclip', 'six'] + +# Windows also requires pyreadline to ensure tab completion works if sys.platform.startswith('win'): INSTALL_REQUIRES += ['pyreadline'] +# Python 2.7 also requires contextlib2 for temporarily redirecting stdout and stderr and subprocess32 +if sys.version_info < (3, 0): + INSTALL_REQUIRES += ['contextlib2', 'subprocess32'] + # unittest.mock was added in Python 3.3. mock is a backport of unittest.mock to all versions of Python TESTS_REQUIRE = ['mock', 'pytest'] DOCS_REQUIRE = ['sphinx', 'sphinx_rtd_theme', 'pyparsing', 'pyperclip', 'six'] diff --git a/tests/test_argparse.py b/tests/test_argparse.py index ecaa1049..d3646046 100644 --- a/tests/test_argparse.py +++ b/tests/test_argparse.py @@ -139,26 +139,20 @@ def test_argparse_quoted_arguments_posix_multiple(argparse_app): out = run_cmd(argparse_app, 'tag strong this "should be" loud') assert out == ['<strong>this should be loud</strong>'] -def test_argparse_help_docstring(argparse_app, capsys): - run_cmd(argparse_app, 'help say') - out, err = capsys.readouterr() - out = out.splitlines() +def test_argparse_help_docstring(argparse_app): + out = run_cmd(argparse_app, 'help say') assert out[0].startswith('usage: say') assert out[1] == '' assert out[2] == 'Repeat what you tell me to.' -def test_argparse_help_description(argparse_app, capsys): - run_cmd(argparse_app, 'help tag') - out, err = capsys.readouterr() - out = out.splitlines() +def test_argparse_help_description(argparse_app): + out = run_cmd(argparse_app, 'help tag') assert out[0].startswith('usage: tag') assert out[1] == '' assert out[2] == 'create a html tag' -def test_argparse_prog(argparse_app, capsys): - run_cmd(argparse_app, 'help tag') - out, err = capsys.readouterr() - out = out.splitlines() +def test_argparse_prog(argparse_app): + out = run_cmd(argparse_app, 'help tag') progname = out[0].split(' ')[1] assert progname == 'tag' @@ -237,26 +231,20 @@ def test_subcommand_invalid(subcommand_app, capsys): assert err[0].startswith('usage: base') assert err[1].startswith("base: error: invalid choice: 'baz'") -def test_subcommand_base_help(subcommand_app, capsys): - run_cmd(subcommand_app, 'help base') - out, err = capsys.readouterr() - out = out.splitlines() +def test_subcommand_base_help(subcommand_app): + out = run_cmd(subcommand_app, 'help base') assert out[0].startswith('usage: base') assert out[1] == '' assert out[2] == 'Base command help' -def test_subcommand_help(subcommand_app, capsys): - run_cmd(subcommand_app, 'help base foo') - out, err = capsys.readouterr() - out = out.splitlines() +def test_subcommand_help(subcommand_app): + out = run_cmd(subcommand_app, 'help base foo') assert out[0].startswith('usage: base foo') assert out[1] == '' assert out[2] == 'positional arguments:' -def test_subcommand_invalid_help(subcommand_app, capsys): - run_cmd(subcommand_app, 'help base baz') - out, err = capsys.readouterr() - err = err.splitlines() - assert err[0].startswith('usage: base') - assert err[1].startswith("base: error: invalid choice: 'baz'") +def test_subcommand_invalid_help(subcommand_app): + out = run_cmd(subcommand_app, 'help base baz') + assert out[0].startswith('usage: base') + assert out[1].startswith("base: error: invalid choice: 'baz'") diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 9fb9f2d9..186def65 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -39,26 +39,22 @@ def test_base_help(base_app): assert out == expected -def test_base_help_history(base_app, capsys): - run_cmd(base_app, 'help history') - out, err = capsys.readouterr() - assert out == HELP_HISTORY - assert err == '' +def test_base_help_history(base_app): + out = run_cmd(base_app, 'help history') + assert out == normalize(HELP_HISTORY) def test_base_argparse_help(base_app, capsys): # Verify that "set -h" gives the same output as "help set" and that it starts in a way that makes sense run_cmd(base_app, 'set -h') - out1, err1 = capsys.readouterr() + out, err = capsys.readouterr() + out1 = out.splitlines() - run_cmd(base_app, 'help set') - out2, err2 = capsys.readouterr() + out2 = run_cmd(base_app, 'help set') assert out1 == out2 - assert err1 == err2 - out = out1.splitlines() - assert out[0].startswith('usage: set') - assert out[1] == '' - assert out[2].startswith('Sets a settable parameter') + assert out1[0].startswith('usage: set') + assert out1[1] == '' + assert out1[2].startswith('Sets a settable parameter') def test_base_invalid_option(base_app, capsys): run_cmd(base_app, 'set -z') @@ -606,17 +602,15 @@ def test_allow_redirection(base_app): assert not os.path.exists(filename) -def test_input_redirection(base_app, request, capsys): +def test_input_redirection(base_app, request): test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'redirect.txt') # NOTE: File 'redirect.txt" contains 1 word "history" # Verify that redirecting input ffom a file works - run_cmd(base_app, 'help < {}'.format(filename)) - out, err = capsys.readouterr() - assert out == HELP_HISTORY - assert err == '' + out = run_cmd(base_app, 'help < {}'.format(filename)) + assert out == normalize(HELP_HISTORY) def test_pipe_to_shell(base_app, capsys): diff --git a/tests/test_completion.py b/tests/test_completion.py index 05774df9..70f77d0a 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -80,9 +80,9 @@ def test_complete_command_invalid_state(cmd2_app): with mock.patch.object(readline, 'get_line_buffer', get_line): with mock.patch.object(readline, 'get_begidx', get_begidx): with mock.patch.object(readline, 'get_endidx', get_endidx): - with pytest.raises(AttributeError): - # Run the readline tab-completion function with readline mocks in place and cause an exception - completion = cmd2_app.complete(text, state) + # Run the readline tab-completion function with readline mocks in place get None + completion = cmd2_app.complete(text, state) + assert completion is None def test_complete_empty_arg(cmd2_app): text = '' |