Skip to content

Commit 39f2409

Browse files
committed
Update vendored pyperclip to 1.5.15
1 parent 547750a commit 39f2409

File tree

3 files changed

+205
-111
lines changed

3 files changed

+205
-111
lines changed

doc/source/whatsnew/v0.18.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,4 @@ Bug Fixes
193193

194194
- Bug in ``.loc`` result with duplicated key may have ``Index`` with incorrect dtype (:issue:`11497`)
195195
- Bug in ``pd.rolling_median`` where memory allocation failed even with sufficient memory (:issue:`11696`)
196+
- Bug in ``pd.read_clipboard`` and ``pd.to_clipboard`` functions not supporting Unicode (:issue:`9263`)

pandas/io/tests/test_clipboard.py

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# -*- coding: utf-8 -*-
12
import numpy as np
23
from numpy.random import randint
34

@@ -46,6 +47,9 @@ def setUpClass(cls):
4647
cls.data['longdf'] = mkdf(max_rows+1, 3, data_gen_f=lambda *args: randint(2),
4748
c_idx_type='s', r_idx_type='i',
4849
c_idx_names=[None], r_idx_names=[None])
50+
# Test for non-ascii text: GH9263
51+
cls.data['nonascii'] = pd.DataFrame({'en': 'in English'.split(),
52+
'es': 'en español'.split()})
4953
cls.data_types = list(cls.data.keys())
5054

5155
@classmethod

pandas/util/clipboard.py

+200-111
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
1-
# Pyperclip v1.3
2-
# A cross-platform clipboard module for Python. (only handles plain text for now)
3-
# By Al Sweigart al@coffeeghost.net
1+
# Pyperclip v1.5.15
2+
# A cross-platform clipboard module for Python.
3+
# By Al Sweigart al@inventwithpython.com
44

55
# Usage:
66
# import pyperclip
77
# pyperclip.copy('The text to be copied to the clipboard.')
88
# spam = pyperclip.paste()
99

10-
# On Mac, this module makes use of the pbcopy and pbpaste commands, which should come with the os.
11-
# On Linux, this module makes use of the xclip command, which should come with the os. Otherwise run "sudo apt-get install xclip"
10+
# On Windows, no additional modules are needed.
11+
# On Mac, this module makes use of the pbcopy and pbpaste commands, which
12+
# should come with the os.
13+
# On Linux, this module makes use of the xclip or xsel commands, which should
14+
# come with the os. Otherwise run "sudo apt-get install xclip" or
15+
# "sudo apt-get install xsel"
16+
# Otherwise on Linux, you will need the gtk or PyQt4 modules installed.
17+
# The gtk module is not available for Python 3, and this module does not work
18+
# with PyGObject yet.
1219

1320

14-
# Copyright (c) 2010, Albert Sweigart
21+
# Copyright (c) 2015, Albert Sweigart
1522
# All rights reserved.
1623
#
1724
# BSD-style license:
@@ -38,139 +45,221 @@
3845
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
3946
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
4047

41-
# Change Log:
42-
# 1.2 Use the platform module to help determine OS.
43-
# 1.3 Changed ctypes.windll.user32.OpenClipboard(None) to ctypes.windll.user32.OpenClipboard(0), after some people ran into some TypeError
4448

45-
import platform, os
49+
import platform
50+
import os
51+
from subprocess import call, Popen, PIPE
52+
53+
PY2 = '2' == platform.python_version_tuple()[0]
54+
text_type = unicode if PY2 else str
55+
4656

4757
class NoClipboardProgramError(OSError):
4858
pass
4959

5060

51-
def winGetClipboard():
52-
ctypes.windll.user32.OpenClipboard(0)
53-
pcontents = ctypes.windll.user32.GetClipboardData(1) # 1 is CF_TEXT
54-
data = ctypes.c_char_p(pcontents).value
55-
#ctypes.windll.kernel32.GlobalUnlock(pcontents)
56-
ctypes.windll.user32.CloseClipboard()
61+
def _pasteWindows():
62+
CF_UNICODETEXT = 13
63+
d = ctypes.windll
64+
d.user32.OpenClipboard(0)
65+
handle = d.user32.GetClipboardData(CF_UNICODETEXT)
66+
data = ctypes.c_wchar_p(handle).value
67+
d.user32.CloseClipboard()
5768
return data
5869

59-
def winSetClipboard(text):
70+
71+
def _copyWindows(text):
6072
GMEM_DDESHARE = 0x2000
61-
ctypes.windll.user32.OpenClipboard(0)
62-
ctypes.windll.user32.EmptyClipboard()
63-
try:
64-
# works on Python 2 (bytes() only takes one argument)
65-
hCd = ctypes.windll.kernel32.GlobalAlloc(GMEM_DDESHARE, len(bytes(text))+1)
66-
except TypeError:
67-
# works on Python 3 (bytes() requires an encoding)
68-
hCd = ctypes.windll.kernel32.GlobalAlloc(GMEM_DDESHARE, len(bytes(text, 'ascii'))+1)
69-
pchData = ctypes.windll.kernel32.GlobalLock(hCd)
70-
try:
71-
# works on Python 2 (bytes() only takes one argument)
72-
ctypes.cdll.msvcrt.strcpy(ctypes.c_char_p(pchData), bytes(text))
73-
except TypeError:
74-
# works on Python 3 (bytes() requires an encoding)
75-
ctypes.cdll.msvcrt.strcpy(ctypes.c_char_p(pchData), bytes(text, 'ascii'))
76-
ctypes.windll.kernel32.GlobalUnlock(hCd)
77-
ctypes.windll.user32.SetClipboardData(1,hCd)
78-
ctypes.windll.user32.CloseClipboard()
79-
80-
def macSetClipboard(text):
81-
outf = os.popen('pbcopy', 'w')
82-
outf.write(text)
83-
outf.close()
84-
85-
def macGetClipboard():
86-
outf = os.popen('pbpaste', 'r')
87-
content = outf.read()
88-
outf.close()
89-
return content
90-
91-
def gtkGetClipboard():
73+
CF_UNICODETEXT = 13
74+
d = ctypes.windll # cdll expects 4 more bytes in user32.OpenClipboard(0)
75+
if not isinstance(text, text_type):
76+
text = text.decode('mbcs')
77+
78+
d.user32.OpenClipboard(0)
79+
80+
d.user32.EmptyClipboard()
81+
hCd = d.kernel32.GlobalAlloc(GMEM_DDESHARE,
82+
len(text.encode('utf-16-le')) + 2)
83+
pchData = d.kernel32.GlobalLock(hCd)
84+
ctypes.cdll.msvcrt.wcscpy(ctypes.c_wchar_p(pchData), text)
85+
d.kernel32.GlobalUnlock(hCd)
86+
d.user32.SetClipboardData(CF_UNICODETEXT, hCd)
87+
d.user32.CloseClipboard()
88+
89+
90+
def _pasteCygwin():
91+
CF_UNICODETEXT = 13
92+
d = ctypes.cdll
93+
d.user32.OpenClipboard(0)
94+
handle = d.user32.GetClipboardData(CF_UNICODETEXT)
95+
data = ctypes.c_wchar_p(handle).value
96+
d.user32.CloseClipboard()
97+
return data
98+
99+
100+
def _copyCygwin(text):
101+
GMEM_DDESHARE = 0x2000
102+
CF_UNICODETEXT = 13
103+
d = ctypes.cdll
104+
if not isinstance(text, text_type):
105+
text = text.decode('mbcs')
106+
d.user32.OpenClipboard(0)
107+
d.user32.EmptyClipboard()
108+
hCd = d.kernel32.GlobalAlloc(GMEM_DDESHARE,
109+
len(text.encode('utf-16-le')) + 2)
110+
pchData = d.kernel32.GlobalLock(hCd)
111+
ctypes.cdll.msvcrt.wcscpy(ctypes.c_wchar_p(pchData), text)
112+
d.kernel32.GlobalUnlock(hCd)
113+
d.user32.SetClipboardData(CF_UNICODETEXT, hCd)
114+
d.user32.CloseClipboard()
115+
116+
117+
def _copyOSX(text):
118+
p = Popen(['pbcopy', 'w'], stdin=PIPE, close_fds=True)
119+
p.communicate(input=text.encode('utf-8'))
120+
121+
122+
def _pasteOSX():
123+
p = Popen(['pbpaste', 'r'], stdout=PIPE, close_fds=True)
124+
stdout, stderr = p.communicate()
125+
return stdout.decode('utf-8')
126+
127+
128+
def _pasteGtk():
92129
return gtk.Clipboard().wait_for_text()
93130

94-
def gtkSetClipboard(text):
131+
132+
def _copyGtk(text):
133+
global cb
95134
cb = gtk.Clipboard()
96135
cb.set_text(text)
97136
cb.store()
98137

99-
def qtGetClipboard():
138+
139+
def _pasteQt():
100140
return str(cb.text())
101141

102-
def qtSetClipboard(text):
142+
143+
def _copyQt(text):
103144
cb.setText(text)
104145

105-
def xclipSetClipboard(text):
106-
outf = os.popen('xclip -selection c', 'w')
107-
outf.write(text)
108-
outf.close()
109146

110-
def xclipGetClipboard():
111-
outf = os.popen('xclip -selection c -o', 'r')
112-
content = outf.read()
113-
outf.close()
114-
return content
147+
def _copyXclip(text):
148+
p = Popen(['xclip', '-selection', 'c'], stdin=PIPE, close_fds=True)
149+
p.communicate(input=text.encode('utf-8'))
150+
151+
152+
def _pasteXclip():
153+
p = Popen(['xclip', '-selection', 'c', '-o'], stdout=PIPE, close_fds=True)
154+
stdout, stderr = p.communicate()
155+
return stdout.decode('utf-8')
156+
157+
158+
def _copyXsel(text):
159+
p = Popen(['xsel', '-b', '-i'], stdin=PIPE, close_fds=True)
160+
p.communicate(input=text.encode('utf-8'))
161+
115162

116-
def xselSetClipboard(text):
117-
outf = os.popen('xsel -i', 'w')
118-
outf.write(text)
119-
outf.close()
163+
def _pasteXsel():
164+
p = Popen(['xsel', '-b', '-o'], stdout=PIPE, close_fds=True)
165+
stdout, stderr = p.communicate()
166+
return stdout.decode('utf-8')
120167

121-
def xselGetClipboard():
122-
outf = os.popen('xsel -o', 'r')
123-
content = outf.read()
124-
outf.close()
125-
return content
126168

169+
def _copyKlipper(text):
170+
p = Popen(['qdbus', 'org.kde.klipper', '/klipper',
171+
'setClipboardContents', text.encode('utf-8')],
172+
stdin=PIPE, close_fds=True)
173+
p.communicate(input=None)
127174

128-
if os.name == 'nt' or platform.system() == 'Windows':
175+
176+
def _pasteKlipper():
177+
p = Popen(['qdbus', 'org.kde.klipper', '/klipper',
178+
'getClipboardContents'], stdout=PIPE, close_fds=True)
179+
stdout, stderr = p.communicate()
180+
return stdout.decode('utf-8')
181+
182+
183+
# Determine the OS/platform and set the copy() and paste() functions
184+
# accordingly.
185+
if 'cygwin' in platform.system().lower():
186+
_functions = 'Cygwin' # for debugging
129187
import ctypes
130-
getcb = winGetClipboard
131-
setcb = winSetClipboard
188+
paste = _pasteCygwin
189+
copy = _copyCygwin
190+
elif os.name == 'nt' or platform.system() == 'Windows':
191+
_functions = 'Windows' # for debugging
192+
import ctypes
193+
paste = _pasteWindows
194+
copy = _copyWindows
132195
elif os.name == 'mac' or platform.system() == 'Darwin':
133-
getcb = macGetClipboard
134-
setcb = macSetClipboard
196+
_functions = 'OS X pbcopy/pbpaste' # for debugging
197+
paste = _pasteOSX
198+
copy = _copyOSX
135199
elif os.name == 'posix' or platform.system() == 'Linux':
136-
xclipExists = os.system('which xclip > /dev/null') == 0
137-
if xclipExists:
138-
getcb = xclipGetClipboard
139-
setcb = xclipSetClipboard
140-
else:
141-
xselExists = os.system('which xsel > /dev/null') == 0
142-
if xselExists:
143-
getcb = xselGetClipboard
144-
setcb = xselSetClipboard
145-
else:
200+
# Determine which command/module is installed, if any.
201+
xclipExists = call(['which', 'xclip'],
202+
stdout=PIPE, stderr=PIPE) == 0
203+
204+
xselExists = call(['which', 'xsel'],
205+
stdout=PIPE, stderr=PIPE) == 0
206+
207+
xklipperExists = (
208+
call(['which', 'klipper'], stdout=PIPE, stderr=PIPE) == 0 and
209+
call(['which', 'qdbus'], stdout=PIPE, stderr=PIPE) == 0
210+
)
211+
212+
gtkInstalled = False
213+
try:
214+
# Check it gtk is installed.
215+
import gtk
216+
gtkInstalled = True
217+
except ImportError:
218+
pass
219+
220+
if not gtkInstalled:
221+
# Check for either PyQt4 or PySide
222+
qtBindingInstalled = True
223+
try:
224+
from PyQt4 import QtGui
225+
except ImportError:
146226
try:
147-
import gtk
227+
from PySide import QtGui
148228
except ImportError:
149-
try:
150-
import PyQt4 as qt4
151-
import PyQt4.QtCore
152-
import PyQt4.QtGui
153-
except ImportError:
154-
try:
155-
import PySide as qt4
156-
import PySide.QtCore
157-
import PySide.QtGui
158-
except ImportError:
159-
raise NoClipboardProgramError('Pyperclip requires the'
160-
' gtk, PyQt4, or PySide'
161-
' module installed, or '
162-
'either the xclip or '
163-
'xsel command.')
164-
app = qt4.QtGui.QApplication([])
165-
cb = qt4.QtGui.QApplication.clipboard()
166-
getcb = qtGetClipboard
167-
setcb = qtSetClipboard
168-
else:
169-
getcb = gtkGetClipboard
170-
setcb = gtkSetClipboard
171-
copy = setcb
172-
paste = getcb
173-
174-
## pandas aliases
229+
qtBindingInstalled = False
230+
231+
# Set one of the copy & paste functions.
232+
if xclipExists:
233+
_functions = 'xclip command' # for debugging
234+
paste = _pasteXclip
235+
copy = _copyXclip
236+
elif xklipperExists:
237+
_functions = '(KDE Klipper) - qdbus (external)' # for debugging
238+
paste = _pasteKlipper
239+
copy = _copyKlipper
240+
elif gtkInstalled:
241+
_functions = 'gtk module' # for debugging
242+
paste = _pasteGtk
243+
copy = _copyGtk
244+
elif qtBindingInstalled:
245+
_functions = 'PyQt4 module' # for debugging
246+
app = QtGui.QApplication([])
247+
cb = QtGui.QApplication.clipboard()
248+
paste = _pasteQt
249+
copy = _copyQt
250+
elif xselExists:
251+
# TODO: xsel doesn't seem to work on Raspberry Pi (my test Linux
252+
# environment). Putting this as the last method tried.
253+
_functions = 'xsel command' # for debugging
254+
paste = _pasteXsel
255+
copy = _copyXsel
256+
else:
257+
raise NoClipboardProgramError('Pyperclip requires the gtk, PyQt4, or '
258+
'PySide module installed, or either the '
259+
'xclip or xsel command.')
260+
else:
261+
raise RuntimeError('pyperclip does not support your system.')
262+
263+
# pandas aliases
175264
clipboard_get = paste
176265
clipboard_set = copy

0 commit comments

Comments
 (0)