Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions cmd2/clipboard.py
Original file line number Diff line number Diff line change
@@ -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)
67 changes: 9 additions & 58 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,15 @@
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"
Expand Down Expand Up @@ -79,15 +78,6 @@
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:
Expand Down Expand Up @@ -121,7 +111,7 @@ def __subclasshook__(cls, C):
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'
Expand Down Expand Up @@ -271,48 +261,6 @@ def cmd_wrapper(instance, cmdline):
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
Expand Down Expand Up @@ -546,6 +494,9 @@ def __init__(self, completekey: str='tab', stdin=None, stdout=None, persistent_h
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
Expand Down Expand Up @@ -1881,7 +1832,7 @@ def _redirect_output(self, statement: Statement) -> None:
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',))
Expand Down
10 changes: 5 additions & 5 deletions tests/test_cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down