From da7020c26c6bbc871c94587ba10f751e53f7911b Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 29 Jun 2019 17:36:46 -0400 Subject: [PATCH 1/5] Remove load, _relative_load, pyscript aliases which These commands were renamed in the last release, but aliases were created along with warnings to help aid the transition. The command aliases are now being removed in this release. --- CHANGELOG.md | 9 ++++ cmd2/cmd2.py | 93 ++++++++++++++------------------------ docs/integrating.rst | 5 +- docs/unfreefeatures.rst | 17 ++++--- tests/test_cmd2.py | 14 +----- tests/test_run_pyscript.py | 5 -- 6 files changed, 55 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d38d38533..1261bc492 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 0.9.15 (July TBD, 2019) +* **Renamed Commands Notice** + * The following commands were renamed in the last release and have been removed in this release + * `load` - replaced by `run_script` + * `_relative_load` - replaced by `_relative_run_script` + * `pyscript` - replaced by `run_pyscript` + * We apologize for any inconvenience, but the new names are more self-descriptive + * Lots of end users were confused particularly about what exactly `load` should be loading + ## 0.9.14 (June 29, 2019) * Enhancements * Added support for and testing with Python 3.8, starting with 3.8 beta diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index eb2d4c15e..d41a631d4 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -3261,16 +3261,9 @@ def do_run_pyscript(self, args: argparse.Namespace) -> bool: finally: # Restore command line arguments to original state sys.argv = orig_args - if args.__statement__.command == "pyscript": - warning = ("pyscript has been renamed and will be removed in the next release, " - "please use run_pyscript instead\n") - self.perror(ansi.style_warning(warning)) return py_return - # pyscript is deprecated - do_pyscript = do_run_pyscript - # Only include the do_ipy() method if IPython is available on the system if ipython_available: # pragma: no cover @with_argparser(ACArgumentParser()) @@ -3644,59 +3637,49 @@ def do_run_script(self, args: argparse.Namespace) -> Optional[bool]: """ expanded_path = os.path.abspath(os.path.expanduser(args.script_path)) - # Wrap everything in a try/finally just to make sure the warning prints at end if `load` was called - try: - # Make sure the path exists and we can access it - if not os.path.exists(expanded_path): - self.perror("'{}' does not exist or cannot be accessed".format(expanded_path)) - return + # Make sure the path exists and we can access it + if not os.path.exists(expanded_path): + self.perror("'{}' does not exist or cannot be accessed".format(expanded_path)) + return - # Make sure expanded_path points to a file - if not os.path.isfile(expanded_path): - self.perror("'{}' is not a file".format(expanded_path)) - return + # Make sure expanded_path points to a file + if not os.path.isfile(expanded_path): + self.perror("'{}' is not a file".format(expanded_path)) + return - # Make sure the file is not empty - if os.path.getsize(expanded_path) == 0: - self.perror("'{}' is empty".format(expanded_path)) - return + # Make sure the file is not empty + if os.path.getsize(expanded_path) == 0: + self.perror("'{}' is empty".format(expanded_path)) + return - # Make sure the file is ASCII or UTF-8 encoded text - if not utils.is_text_file(expanded_path): - self.perror("'{}' is not an ASCII or UTF-8 encoded text file".format(expanded_path)) - return + # Make sure the file is ASCII or UTF-8 encoded text + if not utils.is_text_file(expanded_path): + self.perror("'{}' is not an ASCII or UTF-8 encoded text file".format(expanded_path)) + return - try: - # Read all lines of the script - with open(expanded_path, encoding='utf-8') as target: - script_commands = target.read().splitlines() - except OSError as ex: # pragma: no cover - self.pexcept("Problem accessing script from '{}': {}".format(expanded_path, ex)) - return + try: + # Read all lines of the script + with open(expanded_path, encoding='utf-8') as target: + script_commands = target.read().splitlines() + except OSError as ex: # pragma: no cover + self.pexcept("Problem accessing script from '{}': {}".format(expanded_path, ex)) + return - orig_script_dir_count = len(self._script_dir) + orig_script_dir_count = len(self._script_dir) - try: - self._script_dir.append(os.path.dirname(expanded_path)) + try: + self._script_dir.append(os.path.dirname(expanded_path)) - if args.transcript: - self._generate_transcript(script_commands, os.path.expanduser(args.transcript)) - else: - return self.runcmds_plus_hooks(script_commands) + if args.transcript: + self._generate_transcript(script_commands, os.path.expanduser(args.transcript)) + else: + return self.runcmds_plus_hooks(script_commands) - finally: - with self.sigint_protection: - # Check if a script dir was added before an exception occurred - if orig_script_dir_count != len(self._script_dir): - self._script_dir.pop() finally: - if args.__statement__.command == "load": - warning = ("load has been renamed and will be removed in the next release, " - "please use run_script instead\n") - self.perror(ansi.style_warning(warning)) - - # load has been deprecated - do_load = do_run_script + with self.sigint_protection: + # Check if a script dir was added before an exception occurred + if orig_script_dir_count != len(self._script_dir): + self._script_dir.pop() relative_run_script_description = run_script_description relative_run_script_description += ( @@ -3717,19 +3700,11 @@ def do__relative_run_script(self, args: argparse.Namespace) -> Optional[bool]: Run commands in script file that is encoded as either ASCII or UTF-8 text :return: True if running of commands should stop """ - if args.__statement__.command == "_relative_load": - warning = ("_relative_load has been renamed and will be removed in the next release, " - "please use _relative_run_script instead\n") - self.perror(ansi.style_warning(warning)) - file_path = args.file_path # NOTE: Relative path is an absolute path, it is just relative to the current script directory relative_path = os.path.join(self._current_script_dir or '', file_path) return self.do_run_script(relative_path) - # _relative_load has been deprecated - do__relative_load = do__relative_run_script - def _run_transcript_tests(self, transcript_paths: List[str]) -> None: """Runs transcript tests for provided file(s). diff --git a/docs/integrating.rst b/docs/integrating.rst index 352bb2f0e..bf79ee4e0 100644 --- a/docs/integrating.rst +++ b/docs/integrating.rst @@ -45,9 +45,8 @@ loop:: Documented commands (type help ): ======================================== - alias history mumble pyscript run_script shell - edit load orate quit say shortcuts - help macro py run_pyscript set speak + alias help macro orate quit run_script set shortcuts + edit history mumble py run_pyscript say shell speak (Cmd) diff --git a/docs/unfreefeatures.rst b/docs/unfreefeatures.rst index 713e44e65..02e1b9814 100644 --- a/docs/unfreefeatures.rst +++ b/docs/unfreefeatures.rst @@ -248,12 +248,8 @@ By default, the ``help`` command displays:: Documented commands (type help ): ======================================== - alias findleakers pyscript sessions status vminfo - config help quit set stop which - connect history redeploy shell thread_dump - deploy list resources shortcuts unalias - edit load restart sslconnectorciphers undeploy - expire py serverinfo start version + alias help ipy py run_pyscript set shortcuts + edit history macro quit run_script shell If you have a large number of commands, you can optionally group your commands into categories. Here's the output from the example ``help_categories.py``:: @@ -265,6 +261,10 @@ Here's the output from the example ``help_categories.py``:: deploy findleakers redeploy sessions stop expire list restart start undeploy + Command Management + ================== + disable_commands enable_commands + Connecting ========== connect which @@ -275,9 +275,8 @@ Here's the output from the example ``help_categories.py``:: Other ===== - alias edit history py quit shell unalias - config help load pyscript set shortcuts version - + alias edit history py run_pyscript set shortcuts + config help macro quit run_script shell version There are 2 methods of specifying command categories, using the ``@with_category`` decorator or with the ``categorize()`` function. Once a single command category is detected, the help output switches to a categorized diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index d3e51130f..e8362afb4 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -306,11 +306,6 @@ def test_run_script(base_app, request): assert script_out == manual_out assert script_err == manual_err -def test_load_deprecated(base_app): - """Delete this when load alias is removed""" - _, err = run_cmd(base_app, "load fake") - assert "load has been renamed and will be removed" in err[-1] - def test_run_script_with_empty_args(base_app): out, err = run_cmd(base_app, 'run_script') assert "the following arguments are required" in err[1] @@ -436,11 +431,6 @@ def test_relative_run_script_requires_an_argument(base_app): out, err = run_cmd(base_app, '_relative_run_script') assert 'Error: the following arguments' in err[1] -def test_relative_load_deprecated(base_app): - """Delete this when _relative_load alias is removed""" - _, err = run_cmd(base_app, "_relative_load fake") - assert "_relative_load has been renamed and will be removed" in err[0] - def test_output_redirection(base_app): fd, filename = tempfile.mkstemp(prefix='cmd2_test', suffix='.txt') os.close(fd) @@ -1848,8 +1838,8 @@ def test_onecmd_raw_str_quit(outsim_app): def test_get_all_commands(base_app): # Verify that the base app has the expected commands commands = base_app.get_all_commands() - expected_commands = ['_relative_load', '_relative_run_script', 'alias', 'edit', 'eof', 'help', 'history', 'load', - 'macro', 'py', 'pyscript', 'quit', 'run_pyscript', 'run_script', 'set', 'shell', 'shortcuts'] + expected_commands = ['_relative_run_script', 'alias', 'edit', 'eof', 'help', 'history', 'macro', + 'py', 'quit', 'run_pyscript', 'run_script', 'set', 'shell', 'shortcuts'] assert commands == expected_commands def test_get_help_topics(base_app): diff --git a/tests/test_run_pyscript.py b/tests/test_run_pyscript.py index 9eb33b314..50b1e3010 100644 --- a/tests/test_run_pyscript.py +++ b/tests/test_run_pyscript.py @@ -84,8 +84,3 @@ def test_run_pyscript_stop(base_app, request): python_script = os.path.join(test_dir, 'pyscript', 'stop.py') stop = base_app.onecmd_plus_hooks('run_pyscript {}'.format(python_script)) assert stop - -def test_pyscript_deprecated(base_app): - """Delete this when pyscript alias is removed""" - _, err = run_cmd(base_app, "pyscript fake") - assert "pyscript has been renamed and will be removed" in err[-1] From 4cef001564a262ff4d2a590bb0f8a9675e952194 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 29 Jun 2019 19:07:20 -0400 Subject: [PATCH 2/5] Fixed a couple example transcripts that didn't get updated when colors got renamed to allow_ansi --- examples/transcripts/exampleSession.txt | 2 +- examples/transcripts/transcript_regex.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/transcripts/exampleSession.txt b/examples/transcripts/exampleSession.txt index 8fa7c9bba..3e579c28f 100644 --- a/examples/transcripts/exampleSession.txt +++ b/examples/transcripts/exampleSession.txt @@ -3,7 +3,7 @@ # The regex for editor will match whatever program you use. # regexes on prompts just make the trailing space obvious (Cmd) set -colors: /(Terminal|Always|Never)/ +allow_ansi: /(Terminal|Always|Never)/ continuation_prompt: >/ / debug: False echo: False diff --git a/examples/transcripts/transcript_regex.txt b/examples/transcripts/transcript_regex.txt index 6980fac60..d94c442b6 100644 --- a/examples/transcripts/transcript_regex.txt +++ b/examples/transcripts/transcript_regex.txt @@ -3,7 +3,7 @@ # The regex for editor will match whatever program you use. # regexes on prompts just make the trailing space obvious (Cmd) set -colors: /(Terminal|Always|Never)/ +allow_ansi: /(Terminal|Always|Never)/ continuation_prompt: >/ / debug: False echo: False From 324c44871e43599fd05a1e95f3c36b1986ca2f64 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 29 Jun 2019 19:24:15 -0400 Subject: [PATCH 3/5] Require pyperclip >= 1.6 which simplifies clipboard.py --- cmd2/clipboard.py | 24 ++++-------------------- setup.py | 2 +- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/cmd2/clipboard.py b/cmd2/clipboard.py index b2331649e..8e3cddc3d 100644 --- a/cmd2/clipboard.py +++ b/cmd2/clipboard.py @@ -2,30 +2,14 @@ """ This module provides basic ability to copy from and paste to the clipboard/pastebuffer. """ -import sys - import pyperclip - -# Newer versions of pyperclip are released as a single file, but older versions had a more complicated structure -try: - from pyperclip.exceptions import PyperclipException -except ImportError: # pragma: no cover - # noinspection PyUnresolvedReferences,PyProtectedMember - from pyperclip import PyperclipException +# noinspection PyProtectedMember +from pyperclip import PyperclipException # Can we access the clipboard? Should always be true on Windows and Mac, but only sometimes on Linux -# noinspection PyUnresolvedReferences try: - # Get the version of the pyperclip module as a float - pyperclip_ver = float('.'.join(pyperclip.__version__.split('.')[:2])) - - # The extraneous output bug in pyperclip on Linux using xclip was fixed in more recent versions of pyperclip - if sys.platform.startswith('linux') and pyperclip_ver < 1.6: - # Avoid extraneous output to stderr from xclip when clipboard is empty at cost of overwriting clipboard contents - pyperclip.copy('') - else: - # Try getting the contents of the clipboard - _ = pyperclip.paste() + # Try getting the contents of the clipboard + _ = pyperclip.paste() except PyperclipException: can_clip = False else: diff --git a/setup.py b/setup.py index f6b8f5127..3319c82d1 100755 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ SETUP_REQUIRES = ['setuptools_scm'] -INSTALL_REQUIRES = ['pyperclip >= 1.5.27', 'colorama', 'attrs >= 16.3.0', 'wcwidth >= 0.1.7'] +INSTALL_REQUIRES = ['pyperclip >= 1.6', 'colorama >= 0.3.7', 'attrs >= 16.3.0', 'wcwidth >= 0.1.7'] EXTRAS_REQUIRE = { # Windows also requires pyreadline to ensure tab completion works From 519f0844374db12aa08a0f9f8c185bacb1f4ca2e Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 29 Jun 2019 20:47:46 -0400 Subject: [PATCH 4/5] Added unit test for startup_script functionality --- tests/.cmd2rc | 2 ++ tests/test_cmd2.py | 13 +++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 tests/.cmd2rc diff --git a/tests/.cmd2rc b/tests/.cmd2rc new file mode 100644 index 000000000..cedcbe205 --- /dev/null +++ b/tests/.cmd2rc @@ -0,0 +1,2 @@ +alias create ls !ls -hal +alias create pwd !pwd diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index e8362afb4..ded43d832 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -2144,3 +2144,16 @@ def test_disabled_message_command_name(disable_commands_app): out, err = run_cmd(disable_commands_app, 'has_help_func') assert err[0].startswith('has_help_func is currently disabled') + + +def test_startup_script(request): + test_dir = os.path.dirname(request.module.__file__) + startup_script = os.path.join(os.path.dirname(__file__), '.cmd2rc') + app = cmd2.Cmd(allow_cli_args=False, startup_script=startup_script) + assert len(app._startup_commands) == 1 + assert app._startup_commands[0] == "run_script '{}'".format(startup_script) + app._startup_commands.append('quit') + app.cmdloop() + out, err = run_cmd(app, 'alias list') + assert len(out) > 1 + assert 'alias create ls' in out[0] From 6f3e4789a9468698194aaaf479a7c332b39e0249 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 29 Jun 2019 21:16:24 -0400 Subject: [PATCH 5/5] Added unit test for passing transcript_files during Cmd __init__ --- tests/test_cmd2.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index ded43d832..c9a41033c 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -1480,7 +1480,6 @@ def test_poutput_ansi_always(outsim_app): assert colored_msg != msg assert out == expected - def test_poutput_ansi_never(outsim_app): msg = 'Hello World' ansi.allow_ansi = ansi.ANSI_NEVER @@ -2148,7 +2147,7 @@ def test_disabled_message_command_name(disable_commands_app): def test_startup_script(request): test_dir = os.path.dirname(request.module.__file__) - startup_script = os.path.join(os.path.dirname(__file__), '.cmd2rc') + startup_script = os.path.join(test_dir, '.cmd2rc') app = cmd2.Cmd(allow_cli_args=False, startup_script=startup_script) assert len(app._startup_commands) == 1 assert app._startup_commands[0] == "run_script '{}'".format(startup_script) @@ -2157,3 +2156,9 @@ def test_startup_script(request): out, err = run_cmd(app, 'alias list') assert len(out) > 1 assert 'alias create ls' in out[0] + + +def test_transcripts_at_init(): + transcript_files = ['foo', 'bar'] + app = cmd2.Cmd(allow_cli_args=False, transcript_files=transcript_files) + assert app._transcript_files == transcript_files