diff options
-rw-r--r-- | cmd2/clipboard.py | 49 | ||||
-rw-r--r-- | cmd2/cmd2.py | 67 | ||||
-rw-r--r-- | tests/test_cmd2.py | 10 |
3 files changed, 63 insertions, 63 deletions
diff --git a/cmd2/clipboard.py b/cmd2/clipboard.py new file mode 100644 index 00000000..e0d1fc03 --- /dev/null +++ b/cmd2/clipboard.py @@ -0,0 +1,49 @@ +# coding=utf-8 +""" +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 + 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() +except PyperclipException: + can_clip = False +else: + can_clip = True + + +def get_paste_buffer() -> str: + """Get the contents of the clipboard / paste buffer. + + :return: contents of the clipboard + """ + pb_str = pyperclip.paste() + return pb_str + + +def write_to_paste_buffer(txt: str) -> None: + """Copy text to the clipboard / paste buffer. + + :param txt: text to copy to the clipboard + """ + pyperclip.copy(txt) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index f143d7b3..e2fd25fa 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -41,16 +41,15 @@ import shlex import sys from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Union -import pyperclip - from . import constants from . import utils - -from cmd2.parsing import StatementParser, Statement +from .argparse_completer import AutoCompleter, ACArgumentParser +from .clipboard import can_clip, get_paste_buffer, write_to_paste_buffer +from .parsing import StatementParser, Statement # Set up readline from .rl_utils import rl_type, RlType -if rl_type == RlType.NONE: # pragma: no cover +if rl_type == RlType.NONE: # pragma: no cover rl_warning = "Readline features including tab completion have been disabled since no \n" \ "supported version of readline was found. To resolve this, install \n" \ "pyreadline on Windows or gnureadline on Mac.\n\n" @@ -79,15 +78,6 @@ else: rl_basic_quote_characters = ctypes.c_char_p.in_dll(readline_lib, "rl_basic_quote_characters") orig_rl_basic_quotes = ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value -from .argparse_completer import AutoCompleter, ACArgumentParser - -# 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 - from pyperclip import PyperclipException - # Collection is a container that is sizable and iterable # It was introduced in Python 3.6. We will try to import it, otherwise use our implementation try: @@ -121,7 +111,7 @@ ipython_available = True try: # noinspection PyUnresolvedReferences,PyPackageRequirements from IPython import embed -except ImportError: # pragma: no cover +except ImportError: # pragma: no cover ipython_available = False __version__ = '0.9.2a' @@ -271,48 +261,6 @@ def with_argparser(argparser: argparse.ArgumentParser) -> Callable: return arg_decorator -# 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() -except PyperclipException: - can_clip = False -else: - can_clip = True - - -def disable_clip() -> None: - """ Allows user of cmd2 to manually disable clipboard cut-and-paste functionality.""" - global can_clip - can_clip = False - - -def get_paste_buffer() -> str: - """Get the contents of the clipboard / paste buffer. - - :return: contents of the clipboard - """ - pb_str = pyperclip.paste() - return pb_str - - -def write_to_paste_buffer(txt: str) -> None: - """Copy text to the clipboard / paste buffer. - - :param txt: text to copy to the clipboard - """ - pyperclip.copy(txt) - - class EmbeddedConsoleExit(SystemExit): """Custom exception class for use with the py command.""" pass @@ -546,6 +494,9 @@ class Cmd(cmd.Cmd): self.pager = 'less -RXF' self.pager_chop = 'less -SRXF' + # This boolean flag determines whether or not the cmd2 application can interact with the clipboard + self.can_clip = can_clip + # ----- Methods related to presenting output to the user ----- @property @@ -1881,7 +1832,7 @@ class Cmd(cmd.Cmd): raise ex elif statement.output: import tempfile - if (not statement.output_to) and (not can_clip): + if (not statement.output_to) and (not self.can_clip): raise EnvironmentError("Cannot redirect to paste buffer; install 'pyperclip' and re-run to enable") self.kept_state = Statekeeper(self, ('stdout',)) self.kept_sys = Statekeeper(sys, ('stdout',)) diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index f167793e..26df4807 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -22,6 +22,7 @@ except ImportError: from unittest import mock import cmd2 +from cmd2 import clipboard from cmd2 import utils from .conftest import run_cmd, normalize, BASE_HELP, BASE_HELP_VERBOSE, \ HELP_HISTORY, SHORTCUTS_TXT, SHOW_TXT, SHOW_LONG, StdOut @@ -735,7 +736,7 @@ def test_pipe_to_shell_error(base_app, capsys): assert err.startswith("EXCEPTION of type '{}' occurred with message:".format(expected_error)) -@pytest.mark.skipif(not cmd2.cmd2.can_clip, +@pytest.mark.skipif(not clipboard.can_clip, reason="Pyperclip could not find a copy/paste mechanism for your system") def test_send_to_paste_buffer(base_app): # Test writing to the PasteBuffer/Clipboard @@ -1454,13 +1455,12 @@ def test_multiline_complete_statement_without_terminator(multiline_app): assert statement.command == command -def test_clipboard_failure(capsys): +def test_clipboard_failure(base_app, capsys): # Force cmd2 clipboard to be disabled - cmd2.cmd2.disable_clip() - app = cmd2.Cmd() + base_app.can_clip = False # Redirect command output to the clipboard when a clipboard isn't present - app.onecmd_plus_hooks('help > ') + base_app.onecmd_plus_hooks('help > ') # Make sure we got the error output out, err = capsys.readouterr() |