summaryrefslogtreecommitdiff
path: root/cmd2/cmd2.py
diff options
context:
space:
mode:
Diffstat (limited to 'cmd2/cmd2.py')
-rwxr-xr-xcmd2/cmd2.py122
1 files changed, 76 insertions, 46 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 7547c012..6a2f0e02 100755
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -26,6 +26,7 @@ Git repository on GitHub at https://github.com/python-cmd2/cmd2
import argparse
import cmd
import collections
+from colorama import Fore
import glob
import os
import platform
@@ -39,26 +40,34 @@ import pyperclip
from . import constants
from . import utils
+from cmd2.parsing import StatementParser, Statement
+
# Set up readline
-from .rl_utils import rl_force_redisplay, readline, rl_type, RlType
-from .argparse_completer import AutoCompleter, ACArgumentParser
+from .rl_utils import rl_type, RlType
+if rl_type == RlType.NONE:
+ 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"
+ sys.stderr.write(Fore.LIGHTYELLOW_EX + rl_warning + Fore.RESET)
+else:
+ from .rl_utils import rl_force_redisplay, readline
-from cmd2.parsing import StatementParser, Statement
+ if rl_type == RlType.PYREADLINE:
-if rl_type == RlType.PYREADLINE:
+ # Save the original pyreadline display completion function since we need to override it and restore it
+ # noinspection PyProtectedMember
+ orig_pyreadline_display = readline.rl.mode._display_completions
- # Save the original pyreadline display completion function since we need to override it and restore it
- # noinspection PyProtectedMember
- orig_pyreadline_display = readline.rl.mode._display_completions
+ elif rl_type == RlType.GNU:
-elif rl_type == RlType.GNU:
+ # We need wcswidth to calculate display width of tab completions
+ from wcwidth import wcswidth
- # We need wcswidth to calculate display width of tab completions
- from wcwidth import wcswidth
+ # Get the readline lib so we can make changes to it
+ import ctypes
+ from .rl_utils import readline_lib
- # Get the readline lib so we can make changes to it
- import ctypes
- from .rl_utils import readline_lib
+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:
@@ -312,7 +321,6 @@ class Cmd(cmd.Cmd):
# Attributes used to configure the StatementParser, best not to change these at runtime
blankLinesAllowed = False
multiline_commands = []
- redirector = '>' # for sending output to file
shortcuts = {'?': 'help', '!': 'shell', '@': 'load', '@@': '_relative_load'}
aliases = dict()
terminators = [';']
@@ -377,7 +385,7 @@ class Cmd(cmd.Cmd):
pass
# If persistent readline history is enabled, then read history from file and register to write to file at exit
- if persistent_history_file:
+ if persistent_history_file and rl_type != RlType.NONE:
persistent_history_file = os.path.expanduser(persistent_history_file)
try:
readline.read_history_file(persistent_history_file)
@@ -1127,29 +1135,26 @@ class Cmd(cmd.Cmd):
if len(raw_tokens) > 1:
- # Build a list of all redirection tokens
- all_redirects = constants.REDIRECTION_CHARS + ['>>']
-
# Check if there are redirection strings prior to the token being completed
seen_pipe = False
has_redirection = False
for cur_token in raw_tokens[:-1]:
- if cur_token in all_redirects:
+ if cur_token in constants.REDIRECTION_TOKENS:
has_redirection = True
- if cur_token == '|':
+ if cur_token == constants.REDIRECTION_PIPE:
seen_pipe = True
# Get token prior to the one being completed
prior_token = raw_tokens[-2]
# If a pipe is right before the token being completed, complete a shell command as the piped process
- if prior_token == '|':
+ if prior_token == constants.REDIRECTION_PIPE:
return self.shell_cmd_complete(text, line, begidx, endidx)
# Otherwise do path completion either as files to redirectors or arguments to the piped process
- elif prior_token in all_redirects or seen_pipe:
+ elif prior_token in constants.REDIRECTION_TOKENS or seen_pipe:
return self.path_complete(text, line, begidx, endidx)
# If there were redirection strings anywhere on the command line, then we
@@ -1272,6 +1277,7 @@ class Cmd(cmd.Cmd):
:param state: int - non-negative integer
"""
import functools
+ if state == 0 and rl_type != RlType.NONE:
if state == 0:
unclosed_quote = ''
@@ -1806,7 +1812,7 @@ class Cmd(cmd.Cmd):
# We want Popen to raise an exception if it fails to open the process. Thus we don't set shell to True.
try:
- self.pipe_proc = subprocess.Popen(shlex.split(statement.pipe_to), stdin=subproc_stdin)
+ self.pipe_proc = subprocess.Popen(statement.pipe_to, stdin=subproc_stdin)
except Exception as ex:
# Restore stdout to what it was and close the pipe
self.stdout.close()
@@ -1821,24 +1827,30 @@ class Cmd(cmd.Cmd):
elif statement.output:
import tempfile
if (not statement.output_to) and (not can_clip):
- raise EnvironmentError('Cannot redirect to paste buffer; install ``xclip`` and re-run to enable')
+ 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',))
self.redirecting = True
if statement.output_to:
+ # going to a file
mode = 'w'
- if statement.output == 2 * self.redirector:
+ # statement.output can only contain
+ # REDIRECTION_APPEND or REDIRECTION_OUTPUT
+ if statement.output == constants.REDIRECTION_APPEND:
mode = 'a'
sys.stdout = self.stdout = open(os.path.expanduser(statement.output_to), mode)
else:
+ # going to a paste buffer
sys.stdout = self.stdout = tempfile.TemporaryFile(mode="w+")
- if statement.output == '>>':
+ if statement.output == constants.REDIRECTION_APPEND:
self.poutput(get_paste_buffer())
def _restore_output(self, statement):
- """Handles restoring state after output redirection as well as the actual pipe operation if present.
+ """Handles restoring state after output redirection as well as
+ the actual pipe operation if present.
- :param statement: Statement object which contains the parsed input from the user
+ :param statement: Statement object which contains the parsed
+ input from the user
"""
# If we have redirected output to a file or the clipboard or piped it to a shell command, then restore state
if self.kept_state is not None:
@@ -2116,23 +2128,26 @@ Usage: Usage: alias [name] | [<name> <value>]
name = arglist[0]
value = ' '.join(arglist[1:])
- # Check for a valid name
- for cur_char in name:
- if cur_char not in self.identchars:
- self.perror("Alias names can only contain the following characters: {}".format(self.identchars),
- traceback_war=False)
- return
-
- # Set the alias
- self.aliases[name] = value
- self.poutput("Alias {!r} created".format(name))
+ # Validate the alias to ensure it doesn't include weird characters
+ # like terminators, output redirection, or whitespace
+ valid, invalidchars = self.statement_parser.is_valid_command(name)
+ if valid:
+ # Set the alias
+ self.aliases[name] = value
+ self.poutput("Alias {!r} created".format(name))
+ else:
+ errmsg = "Aliases can not contain: {}".format(invalidchars)
+ self.perror(errmsg, traceback_war=False)
def complete_alias(self, text, line, begidx, endidx):
""" Tab completion for alias """
+ alias_names = set(self.aliases.keys())
+ visible_commands = set(self.get_visible_commands())
+
index_dict = \
{
- 1: self.aliases,
- 2: self.get_visible_commands()
+ 1: alias_names,
+ 2: list(alias_names | visible_commands)
}
return self.index_based_complete(text, line, begidx, endidx, index_dict, self.path_complete)
@@ -2354,9 +2369,12 @@ Usage: Usage: unalias [-a] name [name ...]
self.poutput(' %2d. %s\n' % (idx + 1, text))
while True:
response = input(prompt)
- hlen = readline.get_current_history_length()
- if hlen >= 1 and response != '':
- readline.remove_history_item(hlen - 1)
+
+ if rl_type != RlType.NONE:
+ hlen = readline.get_current_history_length()
+ if hlen >= 1 and response != '':
+ readline.remove_history_item(hlen - 1)
+
try:
response = int(response)
result = fulloptions[response - 1][0]
@@ -2613,9 +2631,21 @@ Paths or arguments that contain spaces must be enclosed in quotes
Run python code from external files with ``run filename.py``
End with ``Ctrl-D`` (Unix) / ``Ctrl-Z`` (Windows), ``quit()``, '`exit()``.
"""
- banner = 'Entering an embedded IPython shell type quit() or <Ctrl>-d to exit ...'
- exit_msg = 'Leaving IPython, back to {}'.format(sys.argv[0])
- embed(banner1=banner, exit_msg=exit_msg)
+ from .pyscript_bridge import PyscriptBridge
+ bridge = PyscriptBridge(self)
+
+ if self.locals_in_py:
+ def load_ipy(self, app):
+ banner = 'Entering an embedded IPython shell type quit() or <Ctrl>-d to exit ...'
+ exit_msg = 'Leaving IPython, back to {}'.format(sys.argv[0])
+ embed(banner1=banner, exit_msg=exit_msg)
+ load_ipy(self, bridge)
+ else:
+ def load_ipy(app):
+ banner = 'Entering an embedded IPython shell type quit() or <Ctrl>-d to exit ...'
+ exit_msg = 'Leaving IPython, back to {}'.format(sys.argv[0])
+ embed(banner1=banner, exit_msg=exit_msg)
+ load_ipy(bridge)
history_parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
history_parser_group = history_parser.add_mutually_exclusive_group()