41
41
import sys
42
42
from typing import Any , Callable , Dict , List , Mapping , Optional , Tuple , Union
43
43
44
- import pyperclip
45
-
46
44
from . import constants
47
45
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
50
49
51
50
# Set up readline
52
51
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
54
53
rl_warning = "Readline features including tab completion have been disabled since no \n " \
55
54
"supported version of readline was found. To resolve this, install \n " \
56
55
"pyreadline on Windows or gnureadline on Mac.\n \n "
79
78
rl_basic_quote_characters = ctypes .c_char_p .in_dll (readline_lib , "rl_basic_quote_characters" )
80
79
orig_rl_basic_quotes = ctypes .cast (rl_basic_quote_characters , ctypes .c_void_p ).value
81
80
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
-
91
81
# Collection is a container that is sizable and iterable
92
82
# It was introduced in Python 3.6. We will try to import it, otherwise use our implementation
93
83
try :
@@ -121,7 +111,7 @@ def __subclasshook__(cls, C):
121
111
try :
122
112
# noinspection PyUnresolvedReferences,PyPackageRequirements
123
113
from IPython import embed
124
- except ImportError : # pragma: no cover
114
+ except ImportError : # pragma: no cover
125
115
ipython_available = False
126
116
127
117
__version__ = '0.9.2a'
@@ -271,48 +261,6 @@ def cmd_wrapper(instance, cmdline):
271
261
return arg_decorator
272
262
273
263
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
-
316
264
class EmbeddedConsoleExit (SystemExit ):
317
265
"""Custom exception class for use with the py command."""
318
266
pass
@@ -356,7 +304,6 @@ class Cmd(cmd.Cmd):
356
304
Line-oriented command interpreters are often useful for test harnesses, internal tools, and rapid prototypes.
357
305
"""
358
306
# Attributes used to configure the StatementParser, best not to change these at runtime
359
- blankLinesAllowed = False
360
307
multiline_commands = []
361
308
shortcuts = {'?' : 'help' , '!' : 'shell' , '@' : 'load' , '@@' : '_relative_load' }
362
309
aliases = dict ()
@@ -505,7 +452,7 @@ def __init__(self, completekey: str='tab', stdin=None, stdout=None, persistent_h
505
452
if startup_script is not None :
506
453
startup_script = os .path .expanduser (startup_script )
507
454
if os .path .exists (startup_script ) and os .path .getsize (startup_script ) > 0 :
508
- self .cmdqueue .append (' load {}' .format (startup_script ))
455
+ self .cmdqueue .append (" load ' {}'" .format (startup_script ))
509
456
510
457
############################################################################################################
511
458
# The following variables are used by tab-completion functions. They are reset each time complete() is run
@@ -534,6 +481,21 @@ def __init__(self, completekey: str='tab', stdin=None, stdout=None, persistent_h
534
481
# quote matches that are completed in a delimited fashion
535
482
self .matches_delimited = False
536
483
484
+ # Set the pager(s) for use with the ppaged() method for displaying output using a pager
485
+ if sys .platform .startswith ('win' ):
486
+ self .pager = self .pager_chop = 'more'
487
+ else :
488
+ # Here is the meaning of the various flags we are using with the less command:
489
+ # -S causes lines longer than the screen width to be chopped (truncated) rather than wrapped
490
+ # -R causes ANSI "color" escape sequences to be output in raw form (i.e. colors are displayed)
491
+ # -X disables sending the termcap initialization and deinitialization strings to the terminal
492
+ # -F causes less to automatically exit if the entire file can be displayed on the first screen
493
+ self .pager = 'less -RXF'
494
+ self .pager_chop = 'less -SRXF'
495
+
496
+ # This boolean flag determines whether or not the cmd2 application can interact with the clipboard
497
+ self .can_clip = can_clip
498
+
537
499
# ----- Methods related to presenting output to the user -----
538
500
539
501
@property
@@ -608,14 +570,20 @@ def pfeedback(self, msg: str) -> None:
608
570
else :
609
571
sys .stderr .write ("{}\n " .format (msg ))
610
572
611
- def ppaged (self , msg : str , end : str = '\n ' ) -> None :
573
+ def ppaged (self , msg : str , end : str = '\n ' , chop : bool = False ) -> None :
612
574
"""Print output using a pager if it would go off screen and stdout isn't currently being redirected.
613
575
614
576
Never uses a pager inside of a script (Python or text) or when output is being redirected or piped or when
615
577
stdout or stdin are not a fully functional terminal.
616
578
617
- :param msg: str - message to print to current stdout - anything convertible to a str with '{}'.format() is OK
618
- :param end: str - string appended after the end of the message if not already present, default a newline
579
+ :param msg: message to print to current stdout - anything convertible to a str with '{}'.format() is OK
580
+ :param end: string appended after the end of the message if not already present, default a newline
581
+ :param chop: True -> causes lines longer than the screen width to be chopped (truncated) rather than wrapped
582
+ - truncated text is still accessible by scrolling with the right & left arrow keys
583
+ - chopping is ideal for displaying wide tabular data as is done in utilities like pgcli
584
+ False -> causes lines longer than the screen width to wrap to the next line
585
+ - wrapping is ideal when you want to avoid users having to use horizontal scrolling
586
+ WARNING: On Windows, the text always wraps regardless of what the chop argument is set to
619
587
"""
620
588
import subprocess
621
589
if msg is not None and msg != '' :
@@ -635,17 +603,10 @@ def ppaged(self, msg: str, end: str='\n') -> None:
635
603
# Don't attempt to use a pager that can block if redirecting or running a script (either text or Python)
636
604
# Also only attempt to use a pager if actually running in a real fully functional terminal
637
605
if functional_terminal and not self .redirecting and not self ._in_py and not self ._script_dir :
638
-
639
- if sys .platform .startswith ('win' ):
640
- pager_cmd = 'more'
641
- else :
642
- # Here is the meaning of the various flags we are using with the less command:
643
- # -S causes lines longer than the screen width to be chopped (truncated) rather than wrapped
644
- # -R causes ANSI "color" escape sequences to be output in raw form (i.e. colors are displayed)
645
- # -X disables sending the termcap initialization and deinitialization strings to the terminal
646
- # -F causes less to automatically exit if the entire file can be displayed on the first screen
647
- pager_cmd = 'less -SRXF'
648
- self .pipe_proc = subprocess .Popen (pager_cmd , shell = True , stdin = subprocess .PIPE )
606
+ pager = self .pager
607
+ if chop :
608
+ pager = self .pager_chop
609
+ self .pipe_proc = subprocess .Popen (pager , shell = True , stdin = subprocess .PIPE )
649
610
try :
650
611
self .pipe_proc .stdin .write (msg_str .encode ('utf-8' , 'replace' ))
651
612
self .pipe_proc .stdin .close ()
@@ -1870,7 +1831,7 @@ def _redirect_output(self, statement: Statement) -> None:
1870
1831
raise ex
1871
1832
elif statement .output :
1872
1833
import tempfile
1873
- if (not statement .output_to ) and (not can_clip ):
1834
+ if (not statement .output_to ) and (not self . can_clip ):
1874
1835
raise EnvironmentError ("Cannot redirect to paste buffer; install 'pyperclip' and re-run to enable" )
1875
1836
self .kept_state = Statekeeper (self , ('stdout' ,))
1876
1837
self .kept_sys = Statekeeper (sys , ('stdout' ,))
@@ -3257,7 +3218,7 @@ def restore(self) -> None:
3257
3218
3258
3219
3259
3220
class CmdResult (utils .namedtuple_with_two_defaults ('CmdResult' , ['out' , 'err' , 'war' ])):
3260
- """Derive a class to store results from a named tuple so we can tweak dunder methods for convenience.
3221
+ """DEPRECATED: Derive a class to store results from a named tuple so we can tweak dunder methods for convenience.
3261
3222
3262
3223
This is provided as a convenience and an example for one possible way for end users to store results in
3263
3224
the self._last_result attribute of cmd2.Cmd class instances. See the "python_scripting.py" example for how it can
0 commit comments