Skip to content

Commit

Permalink
Moved clipboard/pastebuffer functionality to new file clipboard.py
Browse files Browse the repository at this point in the history
Also:
- Converted global can_clip variable to an instance attribute of cmd2.Cmd class
  • Loading branch information
tleonhardt committed Jun 18, 2018
1 parent b5def93 commit 88b94f8
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 63 deletions.
49 changes: 49 additions & 0 deletions 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)
67 changes: 9 additions & 58 deletions cmd2/cmd2.py
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
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

0 comments on commit 88b94f8

Please sign in to comment.