Skip to content

Commit 52795b7

Browse files
authored
Merge pull request #445 from python-cmd2/cleanup_clip
Moved clipboard/pastebuffer functionality to new file clipboard.py
2 parents b5def93 + 88b94f8 commit 52795b7

File tree

3 files changed

+63
-63
lines changed

3 files changed

+63
-63
lines changed

cmd2/clipboard.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# coding=utf-8
2+
"""
3+
This module provides basic ability to copy from and paste to the clipboard/pastebuffer.
4+
"""
5+
import sys
6+
7+
import pyperclip
8+
9+
# Newer versions of pyperclip are released as a single file, but older versions had a more complicated structure
10+
try:
11+
from pyperclip.exceptions import PyperclipException
12+
except ImportError: # pragma: no cover
13+
# noinspection PyUnresolvedReferences
14+
from pyperclip import PyperclipException
15+
16+
# Can we access the clipboard? Should always be true on Windows and Mac, but only sometimes on Linux
17+
# noinspection PyUnresolvedReferences
18+
try:
19+
# Get the version of the pyperclip module as a float
20+
pyperclip_ver = float('.'.join(pyperclip.__version__.split('.')[:2]))
21+
22+
# The extraneous output bug in pyperclip on Linux using xclip was fixed in more recent versions of pyperclip
23+
if sys.platform.startswith('linux') and pyperclip_ver < 1.6:
24+
# Avoid extraneous output to stderr from xclip when clipboard is empty at cost of overwriting clipboard contents
25+
pyperclip.copy('')
26+
else:
27+
# Try getting the contents of the clipboard
28+
_ = pyperclip.paste()
29+
except PyperclipException:
30+
can_clip = False
31+
else:
32+
can_clip = True
33+
34+
35+
def get_paste_buffer() -> str:
36+
"""Get the contents of the clipboard / paste buffer.
37+
38+
:return: contents of the clipboard
39+
"""
40+
pb_str = pyperclip.paste()
41+
return pb_str
42+
43+
44+
def write_to_paste_buffer(txt: str) -> None:
45+
"""Copy text to the clipboard / paste buffer.
46+
47+
:param txt: text to copy to the clipboard
48+
"""
49+
pyperclip.copy(txt)

cmd2/cmd2.py

Lines changed: 9 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,15 @@
4141
import sys
4242
from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Union
4343

44-
import pyperclip
45-
4644
from . import constants
4745
from . import utils
48-
49-
from cmd2.parsing import StatementParser, Statement
46+
from .argparse_completer import AutoCompleter, ACArgumentParser
47+
from .clipboard import can_clip, get_paste_buffer, write_to_paste_buffer
48+
from .parsing import StatementParser, Statement
5049

5150
# Set up readline
5251
from .rl_utils import rl_type, RlType
53-
if rl_type == RlType.NONE: # pragma: no cover
52+
if rl_type == RlType.NONE: # pragma: no cover
5453
rl_warning = "Readline features including tab completion have been disabled since no \n" \
5554
"supported version of readline was found. To resolve this, install \n" \
5655
"pyreadline on Windows or gnureadline on Mac.\n\n"
@@ -79,15 +78,6 @@
7978
rl_basic_quote_characters = ctypes.c_char_p.in_dll(readline_lib, "rl_basic_quote_characters")
8079
orig_rl_basic_quotes = ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value
8180

82-
from .argparse_completer import AutoCompleter, ACArgumentParser
83-
84-
# Newer versions of pyperclip are released as a single file, but older versions had a more complicated structure
85-
try:
86-
from pyperclip.exceptions import PyperclipException
87-
except ImportError: # pragma: no cover
88-
# noinspection PyUnresolvedReferences
89-
from pyperclip import PyperclipException
90-
9181
# Collection is a container that is sizable and iterable
9282
# It was introduced in Python 3.6. We will try to import it, otherwise use our implementation
9383
try:
@@ -121,7 +111,7 @@ def __subclasshook__(cls, C):
121111
try:
122112
# noinspection PyUnresolvedReferences,PyPackageRequirements
123113
from IPython import embed
124-
except ImportError: # pragma: no cover
114+
except ImportError: # pragma: no cover
125115
ipython_available = False
126116

127117
__version__ = '0.9.2a'
@@ -271,48 +261,6 @@ def cmd_wrapper(instance, cmdline):
271261
return arg_decorator
272262

273263

274-
# Can we access the clipboard? Should always be true on Windows and Mac, but only sometimes on Linux
275-
# noinspection PyUnresolvedReferences
276-
try:
277-
# Get the version of the pyperclip module as a float
278-
pyperclip_ver = float('.'.join(pyperclip.__version__.split('.')[:2]))
279-
280-
# The extraneous output bug in pyperclip on Linux using xclip was fixed in more recent versions of pyperclip
281-
if sys.platform.startswith('linux') and pyperclip_ver < 1.6:
282-
# Avoid extraneous output to stderr from xclip when clipboard is empty at cost of overwriting clipboard contents
283-
pyperclip.copy('')
284-
else:
285-
# Try getting the contents of the clipboard
286-
_ = pyperclip.paste()
287-
except PyperclipException:
288-
can_clip = False
289-
else:
290-
can_clip = True
291-
292-
293-
def disable_clip() -> None:
294-
""" Allows user of cmd2 to manually disable clipboard cut-and-paste functionality."""
295-
global can_clip
296-
can_clip = False
297-
298-
299-
def get_paste_buffer() -> str:
300-
"""Get the contents of the clipboard / paste buffer.
301-
302-
:return: contents of the clipboard
303-
"""
304-
pb_str = pyperclip.paste()
305-
return pb_str
306-
307-
308-
def write_to_paste_buffer(txt: str) -> None:
309-
"""Copy text to the clipboard / paste buffer.
310-
311-
:param txt: text to copy to the clipboard
312-
"""
313-
pyperclip.copy(txt)
314-
315-
316264
class EmbeddedConsoleExit(SystemExit):
317265
"""Custom exception class for use with the py command."""
318266
pass
@@ -546,6 +494,9 @@ def __init__(self, completekey: str='tab', stdin=None, stdout=None, persistent_h
546494
self.pager = 'less -RXF'
547495
self.pager_chop = 'less -SRXF'
548496

497+
# This boolean flag determines whether or not the cmd2 application can interact with the clipboard
498+
self.can_clip = can_clip
499+
549500
# ----- Methods related to presenting output to the user -----
550501

551502
@property
@@ -1881,7 +1832,7 @@ def _redirect_output(self, statement: Statement) -> None:
18811832
raise ex
18821833
elif statement.output:
18831834
import tempfile
1884-
if (not statement.output_to) and (not can_clip):
1835+
if (not statement.output_to) and (not self.can_clip):
18851836
raise EnvironmentError("Cannot redirect to paste buffer; install 'pyperclip' and re-run to enable")
18861837
self.kept_state = Statekeeper(self, ('stdout',))
18871838
self.kept_sys = Statekeeper(sys, ('stdout',))

tests/test_cmd2.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from unittest import mock
2323

2424
import cmd2
25+
from cmd2 import clipboard
2526
from cmd2 import utils
2627
from .conftest import run_cmd, normalize, BASE_HELP, BASE_HELP_VERBOSE, \
2728
HELP_HISTORY, SHORTCUTS_TXT, SHOW_TXT, SHOW_LONG, StdOut
@@ -735,7 +736,7 @@ def test_pipe_to_shell_error(base_app, capsys):
735736
assert err.startswith("EXCEPTION of type '{}' occurred with message:".format(expected_error))
736737

737738

738-
@pytest.mark.skipif(not cmd2.cmd2.can_clip,
739+
@pytest.mark.skipif(not clipboard.can_clip,
739740
reason="Pyperclip could not find a copy/paste mechanism for your system")
740741
def test_send_to_paste_buffer(base_app):
741742
# Test writing to the PasteBuffer/Clipboard
@@ -1454,13 +1455,12 @@ def test_multiline_complete_statement_without_terminator(multiline_app):
14541455
assert statement.command == command
14551456

14561457

1457-
def test_clipboard_failure(capsys):
1458+
def test_clipboard_failure(base_app, capsys):
14581459
# Force cmd2 clipboard to be disabled
1459-
cmd2.cmd2.disable_clip()
1460-
app = cmd2.Cmd()
1460+
base_app.can_clip = False
14611461

14621462
# Redirect command output to the clipboard when a clipboard isn't present
1463-
app.onecmd_plus_hooks('help > ')
1463+
base_app.onecmd_plus_hooks('help > ')
14641464

14651465
# Make sure we got the error output
14661466
out, err = capsys.readouterr()

0 commit comments

Comments
 (0)