diff options
author | Todd Leonhardt <todd.leonhardt@gmail.com> | 2018-06-27 06:05:59 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-06-27 06:05:59 -0700 |
commit | 842788675e9c4f0d416626ea3d8a9dac5e00b595 (patch) | |
tree | 15c42850f2d624ed60565876d20889ca8b7cf8ff /cmd2 | |
parent | f0c98ac10e60995bbf2dff9848931109cac2f96e (diff) | |
parent | 3c8880e40bf653e098577de94be7eb2171dbc6b4 (diff) | |
download | cmd2-git-842788675e9c4f0d416626ea3d8a9dac5e00b595.tar.gz |
Merge branch 'master' into autocompleter
Diffstat (limited to 'cmd2')
-rw-r--r-- | cmd2/__init__.py | 3 | ||||
-rw-r--r-- | cmd2/clipboard.py | 49 | ||||
-rw-r--r-- | cmd2/cmd2.py | 111 | ||||
-rw-r--r-- | cmd2/pyscript_bridge.py | 2 | ||||
-rw-r--r-- | cmd2/rl_utils.py | 10 |
5 files changed, 94 insertions, 81 deletions
diff --git a/cmd2/__init__.py b/cmd2/__init__.py index e9a82acb..f61b7165 100644 --- a/cmd2/__init__.py +++ b/cmd2/__init__.py @@ -1,5 +1,6 @@ # # -*- coding: utf-8 -*- """This simply imports certain things for backwards compatibility.""" -from .cmd2 import __version__, Cmd, CmdResult, Statement, EmptyStatement, categorize +from .cmd2 import __version__, Cmd, Statement, EmptyStatement, categorize from .cmd2 import with_argument_list, with_argparser, with_argparser_and_unknown_args, with_category +from .pyscript_bridge import CommandResult
\ No newline at end of file diff --git a/cmd2/clipboard.py b/cmd2/clipboard.py new file mode 100644 index 00000000..e0d1fc03 --- /dev/null +++ b/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) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 75946764..85439927 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -41,16 +41,15 @@ import shlex 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" @@ -79,15 +78,6 @@ else: 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: @@ -121,7 +111,7 @@ ipython_available = True 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' @@ -271,48 +261,6 @@ def with_argparser(argparser: argparse.ArgumentParser) -> Callable: 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 @@ -356,7 +304,6 @@ class Cmd(cmd.Cmd): Line-oriented command interpreters are often useful for test harnesses, internal tools, and rapid prototypes. """ # Attributes used to configure the StatementParser, best not to change these at runtime - blankLinesAllowed = False multiline_commands = [] shortcuts = {'?': 'help', '!': 'shell', '@': 'load', '@@': '_relative_load'} aliases = dict() @@ -505,7 +452,7 @@ class Cmd(cmd.Cmd): if startup_script is not None: startup_script = os.path.expanduser(startup_script) if os.path.exists(startup_script) and os.path.getsize(startup_script) > 0: - self.cmdqueue.append('load {}'.format(startup_script)) + self.cmdqueue.append("load '{}'".format(startup_script)) ############################################################################################################ # The following variables are used by tab-completion functions. They are reset each time complete() is run @@ -534,6 +481,21 @@ class Cmd(cmd.Cmd): # quote matches that are completed in a delimited fashion self.matches_delimited = False + # Set the pager(s) for use with the ppaged() method for displaying output using a pager + if sys.platform.startswith('win'): + self.pager = self.pager_chop = 'more' + else: + # Here is the meaning of the various flags we are using with the less command: + # -S causes lines longer than the screen width to be chopped (truncated) rather than wrapped + # -R causes ANSI "color" escape sequences to be output in raw form (i.e. colors are displayed) + # -X disables sending the termcap initialization and deinitialization strings to the terminal + # -F causes less to automatically exit if the entire file can be displayed on the first screen + 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 @@ -608,14 +570,20 @@ class Cmd(cmd.Cmd): else: sys.stderr.write("{}\n".format(msg)) - def ppaged(self, msg: str, end: str='\n') -> None: + def ppaged(self, msg: str, end: str='\n', chop: bool=False) -> None: """Print output using a pager if it would go off screen and stdout isn't currently being redirected. Never uses a pager inside of a script (Python or text) or when output is being redirected or piped or when stdout or stdin are not a fully functional terminal. - :param msg: str - message to print to current stdout - anything convertible to a str with '{}'.format() is OK - :param end: str - string appended after the end of the message if not already present, default a newline + :param msg: message to print to current stdout - anything convertible to a str with '{}'.format() is OK + :param end: string appended after the end of the message if not already present, default a newline + :param chop: True -> causes lines longer than the screen width to be chopped (truncated) rather than wrapped + - truncated text is still accessible by scrolling with the right & left arrow keys + - chopping is ideal for displaying wide tabular data as is done in utilities like pgcli + False -> causes lines longer than the screen width to wrap to the next line + - wrapping is ideal when you want to avoid users having to use horizontal scrolling + WARNING: On Windows, the text always wraps regardless of what the chop argument is set to """ import subprocess if msg is not None and msg != '': @@ -635,17 +603,10 @@ class Cmd(cmd.Cmd): # Don't attempt to use a pager that can block if redirecting or running a script (either text or Python) # Also only attempt to use a pager if actually running in a real fully functional terminal if functional_terminal and not self.redirecting and not self._in_py and not self._script_dir: - - if sys.platform.startswith('win'): - pager_cmd = 'more' - else: - # Here is the meaning of the various flags we are using with the less command: - # -S causes lines longer than the screen width to be chopped (truncated) rather than wrapped - # -R causes ANSI "color" escape sequences to be output in raw form (i.e. colors are displayed) - # -X disables sending the termcap initialization and deinitialization strings to the terminal - # -F causes less to automatically exit if the entire file can be displayed on the first screen - pager_cmd = 'less -SRXF' - self.pipe_proc = subprocess.Popen(pager_cmd, shell=True, stdin=subprocess.PIPE) + pager = self.pager + if chop: + pager = self.pager_chop + self.pipe_proc = subprocess.Popen(pager, shell=True, stdin=subprocess.PIPE) try: self.pipe_proc.stdin.write(msg_str.encode('utf-8', 'replace')) self.pipe_proc.stdin.close() @@ -1870,7 +1831,7 @@ class Cmd(cmd.Cmd): 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',)) @@ -3257,7 +3218,7 @@ class Statekeeper(object): class CmdResult(utils.namedtuple_with_two_defaults('CmdResult', ['out', 'err', 'war'])): - """Derive a class to store results from a named tuple so we can tweak dunder methods for convenience. + """DEPRECATED: Derive a class to store results from a named tuple so we can tweak dunder methods for convenience. This is provided as a convenience and an example for one possible way for end users to store results in the self._last_result attribute of cmd2.Cmd class instances. See the "python_scripting.py" example for how it can diff --git a/cmd2/pyscript_bridge.py b/cmd2/pyscript_bridge.py index 9353e611..3f58ab84 100644 --- a/cmd2/pyscript_bridge.py +++ b/cmd2/pyscript_bridge.py @@ -22,7 +22,7 @@ from .argparse_completer import _RangeAction from .utils import namedtuple_with_defaults -class CommandResult(namedtuple_with_defaults('CmdResult', ['stdout', 'stderr', 'data'])): +class CommandResult(namedtuple_with_defaults('CommandResult', ['stdout', 'stderr', 'data'])): """Encapsulates the results from a command. Named tuple attributes diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index 55ca4a12..7e49ea47 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -75,16 +75,17 @@ elif 'gnureadline' in sys.modules or 'readline' in sys.modules: readline_lib = ctypes.CDLL(readline.__file__) +# noinspection PyProtectedMember def rl_force_redisplay() -> None: """ - Causes readline to redraw prompt and input line + Causes readline to display the prompt and input text wherever the cursor is and start + reading input from this location. This is the proper way to restore the input line after + printing to the screen """ if not sys.stdout.isatty(): return if rl_type == RlType.GNU: # pragma: no cover - # rl_forced_update_display() is the proper way to redraw the prompt and line, but we - # have to use ctypes to do it since Python's readline API does not wrap the function readline_lib.rl_forced_update_display() # After manually updating the display, readline asks that rl_display_fixed be set to 1 for efficiency @@ -92,5 +93,6 @@ def rl_force_redisplay() -> None: display_fixed.value = 1 elif rl_type == RlType.PYREADLINE: # pragma: no cover - # noinspection PyProtectedMember + # Call _print_prompt() first to set the new location of the prompt readline.rl.mode._print_prompt() + readline.rl.mode._update_line() |