4141import sys
4242from typing import Any , Callable , Dict , List , Mapping , Optional , Tuple , Union
4343
44- import pyperclip
45-
4644from . import constants
4745from . 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
5251from .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 "
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
9383try :
@@ -121,7 +111,7 @@ def __subclasshook__(cls, C):
121111try :
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-
316264class EmbeddedConsoleExit (SystemExit ):
317265 """Custom exception class for use with the py command."""
318266 pass
@@ -356,7 +304,6 @@ class Cmd(cmd.Cmd):
356304 Line-oriented command interpreters are often useful for test harnesses, internal tools, and rapid prototypes.
357305 """
358306 # Attributes used to configure the StatementParser, best not to change these at runtime
359- blankLinesAllowed = False
360307 multiline_commands = []
361308 shortcuts = {'?' : 'help' , '!' : 'shell' , '@' : 'load' , '@@' : '_relative_load' }
362309 aliases = dict ()
@@ -505,7 +452,7 @@ def __init__(self, completekey: str='tab', stdin=None, stdout=None, persistent_h
505452 if startup_script is not None :
506453 startup_script = os .path .expanduser (startup_script )
507454 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 ))
509456
510457 ############################################################################################################
511458 # 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
534481 # quote matches that are completed in a delimited fashion
535482 self .matches_delimited = False
536483
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+
537499 # ----- Methods related to presenting output to the user -----
538500
539501 @property
@@ -608,14 +570,20 @@ def pfeedback(self, msg: str) -> None:
608570 else :
609571 sys .stderr .write ("{}\n " .format (msg ))
610572
611- def ppaged (self , msg : str , end : str = '\n ' ) -> None :
573+ def ppaged (self , msg : str , end : str = '\n ' , chop : bool = False ) -> None :
612574 """Print output using a pager if it would go off screen and stdout isn't currently being redirected.
613575
614576 Never uses a pager inside of a script (Python or text) or when output is being redirected or piped or when
615577 stdout or stdin are not a fully functional terminal.
616578
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
619587 """
620588 import subprocess
621589 if msg is not None and msg != '' :
@@ -635,17 +603,10 @@ def ppaged(self, msg: str, end: str='\n') -> None:
635603 # Don't attempt to use a pager that can block if redirecting or running a script (either text or Python)
636604 # Also only attempt to use a pager if actually running in a real fully functional terminal
637605 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 )
649610 try :
650611 self .pipe_proc .stdin .write (msg_str .encode ('utf-8' , 'replace' ))
651612 self .pipe_proc .stdin .close ()
@@ -1870,7 +1831,7 @@ def _redirect_output(self, statement: Statement) -> None:
18701831 raise ex
18711832 elif statement .output :
18721833 import tempfile
1873- if (not statement .output_to ) and (not can_clip ):
1834+ if (not statement .output_to ) and (not self . can_clip ):
18741835 raise EnvironmentError ("Cannot redirect to paste buffer; install 'pyperclip' and re-run to enable" )
18751836 self .kept_state = Statekeeper (self , ('stdout' ,))
18761837 self .kept_sys = Statekeeper (sys , ('stdout' ,))
@@ -3257,7 +3218,7 @@ def restore(self) -> None:
32573218
32583219
32593220class 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.
32613222
32623223 This is provided as a convenience and an example for one possible way for end users to store results in
32633224 the self._last_result attribute of cmd2.Cmd class instances. See the "python_scripting.py" example for how it can
0 commit comments