summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd2/__init__.py3
-rw-r--r--cmd2/argcomplete_bridge.py246
-rwxr-xr-xcmd2/cmd2.py95
-rw-r--r--cmd2/utils.py15
-rwxr-xr-xexamples/alias_startup.py6
-rwxr-xr-xexamples/arg_print.py6
-rwxr-xr-xexamples/argparse_example.py2
-rwxr-xr-xexamples/environment.py2
-rwxr-xr-xexamples/event_loops.py2
-rwxr-xr-xexamples/example.py2
-rwxr-xr-xexamples/help_categories.py3
-rwxr-xr-xexamples/paged_output.py4
-rwxr-xr-xexamples/persistent_history.py2
-rwxr-xr-xexamples/pirate.py2
-rwxr-xr-xexamples/python_scripting.py2
-rwxr-xr-xexamples/remove_unused.py2
-rwxr-xr-xexamples/subcommands.py75
-rwxr-xr-xexamples/submenus.py2
-rwxr-xr-xexamples/tab_autocompletion.py4
-rwxr-xr-xexamples/tab_completion.py4
-rwxr-xr-xexamples/table_display.py2
-rw-r--r--tests/conftest.py2
-rw-r--r--tests/test_argparse.py2
-rw-r--r--tests/test_cmd2.py20
-rw-r--r--tests/test_completion.py2
-rw-r--r--tests/test_parsing.py18
-rw-r--r--tests/test_submenu.py2
-rw-r--r--tests/test_transcript.py8
28 files changed, 395 insertions, 140 deletions
diff --git a/cmd2/__init__.py b/cmd2/__init__.py
index 8e744e03..bf6c047f 100644
--- a/cmd2/__init__.py
+++ b/cmd2/__init__.py
@@ -1,5 +1,2 @@
#
# -*- coding: utf-8 -*-
-#
-from .cmd2 import __version__, Cmd, set_posix_shlex, set_strip_quotes, AddSubmenu, CmdResult, categorize
-from .cmd2 import with_argument_list, with_argparser, with_argparser_and_unknown_args, with_category
diff --git a/cmd2/argcomplete_bridge.py b/cmd2/argcomplete_bridge.py
new file mode 100644
index 00000000..583f3345
--- /dev/null
+++ b/cmd2/argcomplete_bridge.py
@@ -0,0 +1,246 @@
+# coding=utf-8
+"""Hijack the ArgComplete's bash completion handler to return AutoCompleter results"""
+
+try:
+ # check if argcomplete is installed
+ import argcomplete
+except ImportError:
+ # not installed, skip the rest of the file
+ pass
+
+else:
+ # argcomplete is installed
+
+ from contextlib import redirect_stdout
+ import copy
+ from io import StringIO
+ import os
+ import shlex
+ import sys
+
+ from . import constants
+ from . import utils
+
+
+ def tokens_for_completion(line, endidx):
+ """
+ Used by tab completion functions to get all tokens through the one being completed
+ :param line: str - the current input line with leading whitespace removed
+ :param endidx: int - the ending index of the prefix text
+ :return: A 4 item tuple where the items are
+ On Success
+ tokens: list of unquoted tokens
+ this is generally the list needed for tab completion functions
+ raw_tokens: list of tokens with any quotes preserved
+ this can be used to know if a token was quoted or is missing a closing quote
+ begidx: beginning of last token
+ endidx: cursor position
+
+ Both lists are guaranteed to have at least 1 item
+ The last item in both lists is the token being tab completed
+
+ On Failure
+ Both items are None
+ """
+ unclosed_quote = ''
+ quotes_to_try = copy.copy(constants.QUOTES)
+
+ tmp_line = line[:endidx]
+ tmp_endidx = endidx
+
+ # Parse the line into tokens
+ while True:
+ try:
+ # Use non-POSIX parsing to keep the quotes around the tokens
+ initial_tokens = shlex.split(tmp_line[:tmp_endidx], posix=False)
+
+ # calculate begidx
+ if unclosed_quote:
+ begidx = tmp_line[:tmp_endidx].rfind(initial_tokens[-1]) + 1
+ else:
+ if tmp_endidx > 0 and tmp_line[tmp_endidx - 1] == ' ':
+ begidx = endidx
+ else:
+ begidx = tmp_line[:tmp_endidx].rfind(initial_tokens[-1])
+
+ # If the cursor is at an empty token outside of a quoted string,
+ # then that is the token being completed. Add it to the list.
+ if not unclosed_quote and begidx == tmp_endidx:
+ initial_tokens.append('')
+ break
+ except ValueError:
+ # ValueError can be caused by missing closing quote
+ if not quotes_to_try:
+ # Since we have no more quotes to try, something else
+ # is causing the parsing error. Return None since
+ # this means the line is malformed.
+ return None, None, None, None
+
+ # Add a closing quote and try to parse again
+ unclosed_quote = quotes_to_try[0]
+ quotes_to_try = quotes_to_try[1:]
+
+ tmp_line = line[:endidx]
+ tmp_line += unclosed_quote
+ tmp_endidx = endidx + 1
+
+ raw_tokens = initial_tokens
+
+ # Save the unquoted tokens
+ tokens = [utils.strip_quotes(cur_token) for cur_token in raw_tokens]
+
+ # If the token being completed had an unclosed quote, we need
+ # to remove the closing quote that was added in order for it
+ # to match what was on the command line.
+ if unclosed_quote:
+ raw_tokens[-1] = raw_tokens[-1][:-1]
+
+ return tokens, raw_tokens, begidx, endidx
+
+ class CompletionFinder(argcomplete.CompletionFinder):
+ """Hijack the functor from argcomplete to call AutoCompleter"""
+
+ def __call__(self, argument_parser, completer=None, always_complete_options=True, exit_method=os._exit, output_stream=None,
+ exclude=None, validator=None, print_suppressed=False, append_space=None,
+ default_completer=argcomplete.FilesCompleter()):
+ """
+ :param argument_parser: The argument parser to autocomplete on
+ :type argument_parser: :class:`argparse.ArgumentParser`
+ :param always_complete_options:
+ Controls the autocompletion of option strings if an option string opening character (normally ``-``) has not
+ been entered. If ``True`` (default), both short (``-x``) and long (``--x``) option strings will be
+ suggested. If ``False``, no option strings will be suggested. If ``long``, long options and short options
+ with no long variant will be suggested. If ``short``, short options and long options with no short variant
+ will be suggested.
+ :type always_complete_options: boolean or string
+ :param exit_method:
+ Method used to stop the program after printing completions. Defaults to :meth:`os._exit`. If you want to
+ perform a normal exit that calls exit handlers, use :meth:`sys.exit`.
+ :type exit_method: callable
+ :param exclude: List of strings representing options to be omitted from autocompletion
+ :type exclude: iterable
+ :param validator:
+ Function to filter all completions through before returning (called with two string arguments, completion
+ and prefix; return value is evaluated as a boolean)
+ :type validator: callable
+ :param print_suppressed:
+ Whether or not to autocomplete options that have the ``help=argparse.SUPPRESS`` keyword argument set.
+ :type print_suppressed: boolean
+ :param append_space:
+ Whether to append a space to unique matches. The default is ``True``.
+ :type append_space: boolean
+
+ .. note::
+ If you are not subclassing CompletionFinder to override its behaviors,
+ use ``argcomplete.autocomplete()`` directly. It has the same signature as this method.
+
+ Produces tab completions for ``argument_parser``. See module docs for more info.
+
+ Argcomplete only executes actions if their class is known not to have side effects. Custom action classes can be
+ added to argcomplete.safe_actions, if their values are wanted in the ``parsed_args`` completer argument, or
+ their execution is otherwise desirable.
+ """
+ self.__init__(argument_parser, always_complete_options=always_complete_options, exclude=exclude,
+ validator=validator, print_suppressed=print_suppressed, append_space=append_space,
+ default_completer=default_completer)
+
+ if "_ARGCOMPLETE" not in os.environ:
+ # not an argument completion invocation
+ return
+
+ try:
+ argcomplete.debug_stream = os.fdopen(9, "w")
+ except IOError:
+ argcomplete.debug_stream = sys.stderr
+
+ if output_stream is None:
+ try:
+ output_stream = os.fdopen(8, "wb")
+ except IOError:
+ argcomplete.debug("Unable to open fd 8 for writing, quitting")
+ exit_method(1)
+
+ # print("", stream=debug_stream)
+ # for v in "COMP_CWORD COMP_LINE COMP_POINT COMP_TYPE COMP_KEY _ARGCOMPLETE_COMP_WORDBREAKS COMP_WORDS".split():
+ # print(v, os.environ[v], stream=debug_stream)
+
+ ifs = os.environ.get("_ARGCOMPLETE_IFS", "\013")
+ if len(ifs) != 1:
+ argcomplete.debug("Invalid value for IFS, quitting [{v}]".format(v=ifs))
+ exit_method(1)
+
+ comp_line = os.environ["COMP_LINE"]
+ comp_point = int(os.environ["COMP_POINT"])
+
+ comp_line = argcomplete.ensure_str(comp_line)
+
+ ##############################
+ # SWAPPED FOR AUTOCOMPLETER
+ #
+ # Replaced with our own tokenizer function
+ ##############################
+
+ # cword_prequote, cword_prefix, cword_suffix, comp_words, last_wordbreak_pos = split_line(comp_line, comp_point)
+ tokens, _, begidx, endidx = tokens_for_completion(comp_line, comp_point)
+
+ # _ARGCOMPLETE is set by the shell script to tell us where comp_words
+ # should start, based on what we're completing.
+ # 1: <script> [args]
+ # 2: python <script> [args]
+ # 3: python -m <module> [args]
+ start = int(os.environ["_ARGCOMPLETE"]) - 1
+ ##############################
+ # SWAPPED FOR AUTOCOMPLETER
+ #
+ # Applying the same token dropping to our tokens
+ ##############################
+ # comp_words = comp_words[start:]
+ tokens = tokens[start:]
+
+ # debug("\nLINE: {!r}".format(comp_line),
+ # "\nPOINT: {!r}".format(comp_point),
+ # "\nPREQUOTE: {!r}".format(cword_prequote),
+ # "\nPREFIX: {!r}".format(cword_prefix),
+ # "\nSUFFIX: {!r}".format(cword_suffix),
+ # "\nWORDS:", comp_words)
+
+ ##############################
+ # SWAPPED FOR AUTOCOMPLETER
+ #
+ # Replaced with our own completion function and customizing the returned values
+ ##############################
+ # completions = self._get_completions(comp_words, cword_prefix, cword_prequote, last_wordbreak_pos)
+
+ # capture stdout from the autocompleter
+ result = StringIO()
+ with redirect_stdout(result):
+ completions = completer.complete_command(tokens, tokens[-1], comp_line, begidx, endidx)
+ outstr = result.getvalue()
+
+ if completions:
+ # If any completion has a space in it, then quote all completions
+ # this improves the user experience so they don't nede to go back and add a quote
+ if ' ' in ''.join(completions):
+ completions = ['"{}"'.format(entry) for entry in completions]
+
+ argcomplete.debug("\nReturning completions:", completions)
+
+ output_stream.write(ifs.join(completions).encode(argcomplete.sys_encoding))
+ elif outstr:
+ # if there are no completions, but we got something from stdout, try to print help
+
+ # trick the bash completion into thinking there are 2 completions that are unlikely
+ # to ever match.
+ outstr = outstr.replace('\n', ' ').replace('\t', ' ').replace(' ', ' ').strip()
+ # generate a filler entry that should always sort first
+ filler = ' {0:><{width}}'.format('', width=len(outstr)/2)
+ outstr = ifs.join([filler, outstr])
+
+ output_stream.write(outstr.encode(argcomplete.sys_encoding))
+ else:
+ # if completions is None we assume we don't know how to handle it so let bash
+ # go forward with normal filesystem completion
+ output_stream.write(ifs.join([]).encode(argcomplete.sys_encoding))
+ output_stream.flush()
+ argcomplete.debug_stream.flush()
+ exit_method(0)
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 98adc01f..80fa5601 100755
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -71,10 +71,6 @@ elif rl_type == RlType.GNU:
import ctypes
from .rl_utils import readline_lib
- # Save address that rl_basic_quote_characters is pointing to since we need to override and restore it
- rl_basic_quote_characters = ctypes.c_char_p.in_dll(readline_lib, "rl_basic_quote_characters")
- orig_rl_basic_quote_characters_addr = ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value
-
# Newer versions of pyperclip are released as a single file, but older versions had a more complicated structure
try:
from pyperclip.exceptions import PyperclipException
@@ -184,21 +180,6 @@ def _which(editor: str) -> Optional[str]:
return editor_path
-def strip_quotes(arg: str) -> str:
- """ Strip outer quotes from a string.
-
- Applies to both single and double quotes.
-
- :param arg: string to strip outer quotes from
- :return: same string with potentially outer quotes stripped
- """
- quote_chars = '"' + "'"
-
- if len(arg) > 1 and arg[0] == arg[-1] and arg[0] in quote_chars:
- arg = arg[1:-1]
- return arg
-
-
def parse_quoted_string(cmdline: str) -> List[str]:
"""Parse a quoted string into a list of arguments."""
if isinstance(cmdline, list):
@@ -211,7 +192,7 @@ def parse_quoted_string(cmdline: str) -> List[str]:
if not POSIX_SHLEX and STRIP_QUOTES_FOR_NON_POSIX:
temp_arglist = []
for arg in lexed_arglist:
- temp_arglist.append(strip_quotes(arg))
+ temp_arglist.append(utils.strip_quotes(arg))
lexed_arglist = temp_arglist
return lexed_arglist
@@ -385,7 +366,7 @@ def replace_with_file_contents(fname: str) -> str:
"""
try:
# Any outer quotes are not part of the filename
- unquoted_file = strip_quotes(fname[0])
+ unquoted_file = utils.strip_quotes(fname[0])
with open(os.path.expanduser(unquoted_file)) as source_file:
result = source_file.read()
except IOError:
@@ -603,11 +584,22 @@ class AddSubmenu(object):
try:
# copy over any shared attributes
self._copy_in_shared_attrs(_self)
+
+ # Reset the submenu's tab completion parameters
+ submenu.allow_appended_space = True
+ submenu.allow_closing_quote = True
+ submenu.display_matches = []
+
return _complete_from_cmd(submenu, text, line, begidx, endidx)
finally:
# copy back original attributes
self._copy_out_shared_attrs(_self, original_attributes)
+ # Pass the submenu's tab completion parameters back up to the menu that called complete()
+ _self.allow_appended_space = submenu.allow_appended_space
+ _self.allow_closing_quote = submenu.allow_closing_quote
+ _self.display_matches = copy.copy(submenu.display_matches)
+
original_do_help = cmd_obj.do_help
original_complete_help = cmd_obj.complete_help
@@ -996,6 +988,11 @@ class Cmd(cmd.Cmd):
self.allow_closing_quote = True
self.display_matches = []
+ if rl_type == RlType.GNU:
+ readline.set_completion_display_matches_hook(self._display_matches_gnu_readline)
+ elif rl_type == RlType.PYREADLINE:
+ readline.rl.mode._display_completions = self._display_matches_pyreadline
+
def tokens_for_completion(self, line, begidx, endidx):
"""
Used by tab completion functions to get all tokens through the one being completed
@@ -1105,7 +1102,7 @@ class Cmd(cmd.Cmd):
raw_tokens = initial_tokens
# Save the unquoted tokens
- tokens = [strip_quotes(cur_token) for cur_token in raw_tokens]
+ tokens = [utils.strip_quotes(cur_token) for cur_token in raw_tokens]
# If the token being completed had an unclosed quote, we need
# to remove the closing quote that was added in order for it
@@ -2368,38 +2365,33 @@ class Cmd(cmd.Cmd):
"""
# An almost perfect copy from Cmd; however, the pseudo_raw_input portion
# has been split out so that it can be called separately
- if self.use_rawinput and self.completekey:
+ if self.use_rawinput and self.completekey and rl_type != RlType.NONE:
# Set up readline for our tab completion needs
if rl_type == RlType.GNU:
- readline.set_completion_display_matches_hook(self._display_matches_gnu_readline)
-
# Set GNU readline's rl_basic_quote_characters to NULL so it won't automatically add a closing quote
# We don't need to worry about setting rl_completion_suppress_quote since we never declared
# rl_completer_quote_characters.
- rl_basic_quote_characters.value = None
+ basic_quote_characters = ctypes.c_char_p.in_dll(readline_lib, "rl_basic_quote_characters")
+ old_basic_quote_characters = ctypes.cast(basic_quote_characters, ctypes.c_void_p).value
+ basic_quote_characters.value = None
- elif rl_type == RlType.PYREADLINE:
- readline.rl.mode._display_completions = self._display_matches_pyreadline
+ old_completer = readline.get_completer()
+ old_delims = readline.get_completer_delims()
+ readline.set_completer(self.complete)
- try:
- self.old_completer = readline.get_completer()
- self.old_delims = readline.get_completer_delims()
- readline.set_completer(self.complete)
+ # Break words on whitespace and quotes when tab completing
+ completer_delims = " \t\n" + ''.join(constants.QUOTES)
- # Break words on whitespace and quotes when tab completing
- completer_delims = " \t\n" + ''.join(constants.QUOTES)
+ if self.allow_redirection:
+ # If redirection is allowed, then break words on those characters too
+ completer_delims += ''.join(constants.REDIRECTION_CHARS)
- if self.allow_redirection:
- # If redirection is allowed, then break words on those characters too
- completer_delims += ''.join(constants.REDIRECTION_CHARS)
+ readline.set_completer_delims(completer_delims)
- readline.set_completer_delims(completer_delims)
+ # Enable tab completion
+ readline.parse_and_bind(self.completekey + ": complete")
- # Enable tab completion
- readline.parse_and_bind(self.completekey + ": complete")
- except NameError:
- pass
stop = None
try:
while not stop:
@@ -2423,19 +2415,15 @@ class Cmd(cmd.Cmd):
# Run the command along with all associated pre and post hooks
stop = self.onecmd_plus_hooks(line)
finally:
- if self.use_rawinput and self.completekey:
+ if self.use_rawinput and self.completekey and rl_type != RlType.NONE:
# Restore what we changed in readline
- try:
- readline.set_completer(self.old_completer)
- readline.set_completer_delims(self.old_delims)
- except NameError:
- pass
+ readline.set_completer(old_completer)
+ readline.set_completer_delims(old_delims)
if rl_type == RlType.GNU:
readline.set_completion_display_matches_hook(None)
- rl_basic_quote_characters.value = orig_rl_basic_quote_characters_addr
-
+ basic_quote_characters.value = old_basic_quote_characters
elif rl_type == RlType.PYREADLINE:
readline.rl.mode._display_completions = orig_pyreadline_display
@@ -2781,7 +2769,7 @@ Usage: Usage: unalias [-a] name [name ...]
set_parser = ACArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
set_parser.add_argument('-a', '--all', action='store_true', help='display read-only settings as well')
set_parser.add_argument('-l', '--long', action='store_true', help='describe function of parameter')
- set_parser.add_argument('settable', nargs=(0,2), help='[param_name] [value]')
+ set_parser.add_argument('settable', nargs=(0, 2), help='[param_name] [value]')
@with_argparser(set_parser)
def do_set(self, args):
@@ -2838,7 +2826,7 @@ Usage: Usage: unalias [-a] name [name ...]
# an unclosed quote, so we only need to check the first character.
first_char = tokens[index][0]
if first_char in constants.QUOTES:
- tokens[index] = strip_quotes(tokens[index])
+ tokens[index] = utils.strip_quotes(tokens[index])
tokens[index] = os.path.expanduser(tokens[index])
@@ -2862,7 +2850,6 @@ Usage: Usage: unalias [-a] name [name ...]
index_dict = {1: self.shell_cmd_complete}
return self.index_based_complete(text, line, begidx, endidx, index_dict, self.path_complete)
-
# noinspection PyBroadException
def do_py(self, arg):
"""
@@ -3356,7 +3343,7 @@ class ParserManager:
ignore=do_not_parse)('pipeTo')) + \
pyparsing.Optional(output_destination_parser +
pyparsing.SkipTo(string_end, ignore=do_not_parse).
- setParseAction(lambda x: strip_quotes(x[0].strip()))('outputTo'))
+ setParseAction(lambda x: utils.strip_quotes(x[0].strip()))('outputTo'))
multilineCommand.setParseAction(lambda x: x[0])
oneline_command.setParseAction(lambda x: x[0])
diff --git a/cmd2/utils.py b/cmd2/utils.py
index 167879aa..a975c6b8 100644
--- a/cmd2/utils.py
+++ b/cmd2/utils.py
@@ -5,6 +5,7 @@
import collections
from . import constants
+
def strip_ansi(text: str) -> str:
"""Strip ANSI escape codes from a string.
@@ -14,6 +15,19 @@ def strip_ansi(text: str) -> str:
return constants.ANSI_ESCAPE_RE.sub('', text)
+def strip_quotes(arg: str) -> str:
+ """ Strip outer quotes from a string.
+
+ Applies to both single and double quotes.
+
+ :param arg: string to strip outer quotes from
+ :return: same string with potentially outer quotes stripped
+ """
+ if len(arg) > 1 and arg[0] == arg[-1] and arg[0] in constants.QUOTES:
+ arg = arg[1:-1]
+ return arg
+
+
def namedtuple_with_defaults(typename, field_names, default_values=()):
"""
Convenience function for defining a namedtuple with default values
@@ -41,3 +55,4 @@ def namedtuple_with_defaults(typename, field_names, default_values=()):
prototype = T(*default_values)
T.__new__.__defaults__ = tuple(prototype)
return T
+
diff --git a/examples/alias_startup.py b/examples/alias_startup.py
index 30764c27..7ccfa6e5 100755
--- a/examples/alias_startup.py
+++ b/examples/alias_startup.py
@@ -4,12 +4,8 @@
1) How to add custom command aliases using the alias command
2) How to load an initialization script at startup
"""
-import argparse
-import cmd2
-import pyparsing
-
-from cmd2 import with_argument_list, with_argparser, with_argparser_and_unknown_args
+from cmd2 import cmd2
class AliasAndStartup(cmd2.Cmd):
diff --git a/examples/arg_print.py b/examples/arg_print.py
index 18fa483f..90df053a 100755
--- a/examples/arg_print.py
+++ b/examples/arg_print.py
@@ -11,10 +11,8 @@ It also serves as an example of how to create command aliases (shortcuts).
"""
import argparse
-import cmd2
-import pyparsing
-
-from cmd2 import with_argument_list, with_argparser, with_argparser_and_unknown_args
+from cmd2 import cmd2
+from cmd2.cmd2 import with_argument_list, with_argparser, with_argparser_and_unknown_args
class ArgumentAndOptionPrinter(cmd2.Cmd):
diff --git a/examples/argparse_example.py b/examples/argparse_example.py
index e9b377ba..e8afef5c 100755
--- a/examples/argparse_example.py
+++ b/examples/argparse_example.py
@@ -14,7 +14,7 @@ verifying that the output produced matches the transcript.
import argparse
import sys
-from cmd2 import Cmd, with_argparser, with_argument_list
+from cmd2.cmd2 import Cmd, with_argparser, with_argument_list
class CmdLineApp(Cmd):
diff --git a/examples/environment.py b/examples/environment.py
index c245f55d..af452e4e 100755
--- a/examples/environment.py
+++ b/examples/environment.py
@@ -4,7 +4,7 @@
A sample application for cmd2 demonstrating customized environment parameters
"""
-from cmd2 import Cmd
+from cmd2.cmd2 import Cmd
class EnvironmentApp(Cmd):
diff --git a/examples/event_loops.py b/examples/event_loops.py
index 53d3ca2b..a76c5d91 100755
--- a/examples/event_loops.py
+++ b/examples/event_loops.py
@@ -6,7 +6,7 @@ This is an example of how to use cmd2 in a way so that cmd2 doesn't own the inne
This opens up the possibility of registering cmd2 input with event loops, like asyncio, without occupying the main loop.
"""
-import cmd2
+from cmd2 import cmd2
class Cmd2EventBased(cmd2.Cmd):
diff --git a/examples/example.py b/examples/example.py
index 612d81e5..1fc6bf6d 100755
--- a/examples/example.py
+++ b/examples/example.py
@@ -14,7 +14,7 @@ the transcript.
import random
import argparse
-from cmd2 import Cmd, with_argparser
+from cmd2.cmd2 import Cmd, with_argparser
class CmdLineApp(Cmd):
diff --git a/examples/help_categories.py b/examples/help_categories.py
index cfb5f253..dcfbd31f 100755
--- a/examples/help_categories.py
+++ b/examples/help_categories.py
@@ -4,9 +4,10 @@
A sample application for tagging categories on commands.
"""
-from cmd2 import Cmd, categorize, __version__, with_argparser, with_category
import argparse
+from cmd2.cmd2 import Cmd, categorize, __version__, with_argparser, with_category
+
class HelpCategories(Cmd):
""" Example cmd2 application. """
diff --git a/examples/paged_output.py b/examples/paged_output.py
index bb410af6..9396f04e 100755
--- a/examples/paged_output.py
+++ b/examples/paged_output.py
@@ -3,8 +3,8 @@
"""A simple example demonstrating the using paged output via the ppaged() method.
"""
-import cmd2
-from cmd2 import with_argument_list
+from cmd2 import cmd2
+from cmd2.cmd2 import with_argument_list
class PagedOutput(cmd2.Cmd):
diff --git a/examples/persistent_history.py b/examples/persistent_history.py
index 61e26b9c..251dbd67 100755
--- a/examples/persistent_history.py
+++ b/examples/persistent_history.py
@@ -5,7 +5,7 @@
This will allow end users of your cmd2-based application to use the arrow keys and Ctrl+r in a manner which persists
across invocations of your cmd2 application. This can make it much easier for them to use your application.
"""
-import cmd2
+from cmd2 import cmd2
class Cmd2PersistentHistory(cmd2.Cmd):
diff --git a/examples/pirate.py b/examples/pirate.py
index 7fe3884b..2daa8631 100755
--- a/examples/pirate.py
+++ b/examples/pirate.py
@@ -7,7 +7,7 @@ presented as part of her PyCon 2010 talk.
It demonstrates many features of cmd2.
"""
import argparse
-from cmd2 import Cmd, with_argparser
+from cmd2.cmd2 import Cmd, with_argparser
class Pirate(Cmd):
diff --git a/examples/python_scripting.py b/examples/python_scripting.py
index 7e2cf345..865cf052 100755
--- a/examples/python_scripting.py
+++ b/examples/python_scripting.py
@@ -17,7 +17,7 @@ This application and the "scripts/conditional.py" script serve as an example for
import argparse
import os
-import cmd2
+from cmd2 import cmd2
class CmdLineApp(cmd2.Cmd):
diff --git a/examples/remove_unused.py b/examples/remove_unused.py
index 8a567123..dfe0a055 100755
--- a/examples/remove_unused.py
+++ b/examples/remove_unused.py
@@ -9,7 +9,7 @@ name, they just won't clutter the help menu.
Commands can also be removed entirely by using Python's "del".
"""
-import cmd2
+from cmd2 import cmd2
class RemoveUnusedBuiltinCommands(cmd2.Cmd):
diff --git a/examples/subcommands.py b/examples/subcommands.py
index eda88072..9bf6c666 100755
--- a/examples/subcommands.py
+++ b/examples/subcommands.py
@@ -1,5 +1,6 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# coding=utf-8
+# PYTHON_ARGCOMPLETE_OK
"""A simple example demonstrating how to use Argparse to support subcommands.
@@ -8,11 +9,51 @@ and provides separate contextual help.
"""
import argparse
-import cmd2
-from cmd2 import with_argparser
-
sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball']
+# create the top-level parser for the base command
+base_parser = argparse.ArgumentParser(prog='base')
+base_subparsers = base_parser.add_subparsers(title='subcommands', help='subcommand help')
+
+# create the parser for the "foo" subcommand
+parser_foo = base_subparsers.add_parser('foo', help='foo help')
+parser_foo.add_argument('-x', type=int, default=1, help='integer')
+parser_foo.add_argument('y', type=float, help='float')
+
+# create the parser for the "bar" subcommand
+parser_bar = base_subparsers.add_parser('bar', help='bar help')
+
+bar_subparsers = parser_bar.add_subparsers(title='layer3', help='help for 3rd layer of commands')
+parser_bar.add_argument('z', help='string')
+
+bar_subparsers.add_parser('apple', help='apple help')
+bar_subparsers.add_parser('artichoke', help='artichoke help')
+bar_subparsers.add_parser('cranberries', help='cranberries help')
+
+# create the parser for the "sport" subcommand
+parser_sport = base_subparsers.add_parser('sport', help='sport help')
+sport_arg = parser_sport.add_argument('sport', help='Enter name of a sport')
+setattr(sport_arg, 'arg_choices', sport_item_strs)
+
+# Handle bash completion if it's installed
+try:
+ # only move forward if we can import CompletionFinder and AutoCompleter
+ from cmd2.argcomplete_bridge import CompletionFinder
+ from cmd2.argparse_completer import AutoCompleter
+ if __name__ == '__main__':
+ with open('out.txt', 'a') as f:
+ f.write('Here 1')
+ f.flush()
+ completer = CompletionFinder()
+ completer(base_parser, AutoCompleter(base_parser))
+except ImportError:
+ pass
+
+
+# Intentionally below the bash completion code to reduce tab completion lag
+from cmd2 import cmd2
+from cmd2.cmd2 import with_argparser
+
class SubcommandsExample(cmd2.Cmd):
"""
@@ -36,33 +77,9 @@ class SubcommandsExample(cmd2.Cmd):
"""sport subcommand of base command"""
self.poutput('Sport is {}'.format(args.sport))
- # create the top-level parser for the base command
- base_parser = argparse.ArgumentParser(prog='base')
- base_subparsers = base_parser.add_subparsers(title='subcommands', help='subcommand help')
-
- # create the parser for the "foo" subcommand
- parser_foo = base_subparsers.add_parser('foo', help='foo help')
- parser_foo.add_argument('-x', type=int, default=1, help='integer')
- parser_foo.add_argument('y', type=float, help='float')
+ # Set handler functions for the subcommands
parser_foo.set_defaults(func=base_foo)
-
- # create the parser for the "bar" subcommand
- parser_bar = base_subparsers.add_parser('bar', help='bar help')
parser_bar.set_defaults(func=base_bar)
-
- bar_subparsers = parser_bar.add_subparsers(title='layer3', help='help for 3rd layer of commands')
- parser_bar.add_argument('z', help='string')
-
- bar_subparsers.add_parser('apple', help='apple help')
- bar_subparsers.add_parser('artichoke', help='artichoke help')
- bar_subparsers.add_parser('cranberries', help='cranberries help')
-
- # create the parser for the "sport" subcommand
- parser_sport = base_subparsers.add_parser('sport', help='sport help')
- sport_arg = parser_sport.add_argument('sport', help='Enter name of a sport')
- setattr(sport_arg, 'arg_choices', sport_item_strs)
-
- # Set both a function and tab completer for the "sport" subcommand
parser_sport.set_defaults(func=base_sport)
@with_argparser(base_parser)
diff --git a/examples/submenus.py b/examples/submenus.py
index 44b17f33..27c8cb10 100755
--- a/examples/submenus.py
+++ b/examples/submenus.py
@@ -11,7 +11,7 @@ of the submenu. Nesting of the submenus is done with the cmd2.AddSubmenu() decor
from __future__ import print_function
import sys
-import cmd2
+from cmd2 import cmd2
from IPython import embed
diff --git a/examples/tab_autocompletion.py b/examples/tab_autocompletion.py
index 6146b64b..f3302533 100755
--- a/examples/tab_autocompletion.py
+++ b/examples/tab_autocompletion.py
@@ -10,8 +10,8 @@ import argparse
import itertools
from typing import List
-import cmd2
-from cmd2 import with_argparser, with_category, argparse_completer
+from cmd2 import cmd2, argparse_completer
+from cmd2.cmd2 import with_argparser, with_category
actors = ['Mark Hamill', 'Harrison Ford', 'Carrie Fisher', 'Alec Guinness', 'Peter Mayhew',
'Anthony Daniels', 'Adam Driver', 'Daisy Ridley', 'John Boyega', 'Oscar Isaac',
diff --git a/examples/tab_completion.py b/examples/tab_completion.py
index 919e9560..30fa283d 100755
--- a/examples/tab_completion.py
+++ b/examples/tab_completion.py
@@ -4,8 +4,8 @@
"""
import argparse
-import cmd2
-from cmd2 import with_argparser, with_argument_list
+from cmd2 import cmd2
+from cmd2.cmd2 import with_argparser, with_argument_list
# List of strings used with flag and index based completion functions
food_item_strs = ['Pizza', 'Ham', 'Ham Sandwich', 'Potato']
diff --git a/examples/table_display.py b/examples/table_display.py
index 2e6ea804..5d168408 100755
--- a/examples/table_display.py
+++ b/examples/table_display.py
@@ -12,7 +12,7 @@ WARNING: This example requires the tabulate module.
"""
import functools
-import cmd2
+from cmd2 import cmd2
import tabulate
# Format to use with tabulate module when displaying tables
diff --git a/tests/conftest.py b/tests/conftest.py
index 90d45bd9..562ca4fa 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -10,7 +10,7 @@ import sys
from pytest import fixture
from unittest import mock
-import cmd2
+from cmd2 import cmd2
# Prefer statically linked gnureadline if available (for macOS compatibility due to issues with libedit)
try:
diff --git a/tests/test_argparse.py b/tests/test_argparse.py
index 6a9a93a7..e23c5d17 100644
--- a/tests/test_argparse.py
+++ b/tests/test_argparse.py
@@ -5,7 +5,7 @@ Cmd2 testing for argument parsing
import argparse
import pytest
-import cmd2
+from cmd2 import cmd2
from unittest import mock
from .conftest import run_cmd, StdOut
diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py
index 48f50bdc..c8955497 100644
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -21,7 +21,7 @@ try:
except ImportError:
from unittest import mock
-import cmd2
+from cmd2 import cmd2
from .conftest import run_cmd, normalize, BASE_HELP, BASE_HELP_VERBOSE, \
HELP_HISTORY, SHORTCUTS_TXT, SHOW_TXT, SHOW_LONG, StdOut
@@ -109,8 +109,8 @@ def test_base_show_readonly(base_app):
Strip Quotes after splitting arguments: {}
""".format(base_app.terminators, base_app.allow_cli_args, base_app.allow_redirection,
- "POSIX" if cmd2.cmd2.POSIX_SHLEX else "non-POSIX",
- "True" if cmd2.cmd2.STRIP_QUOTES_FOR_NON_POSIX and not cmd2.cmd2.POSIX_SHLEX else "False"))
+ "POSIX" if cmd2.POSIX_SHLEX else "non-POSIX",
+ "True" if cmd2.STRIP_QUOTES_FOR_NON_POSIX and not cmd2.POSIX_SHLEX else "False"))
assert out == expected
@@ -647,18 +647,18 @@ def test_pipe_to_shell_error(base_app, capsys):
assert err.startswith("EXCEPTION of type '{}' occurred with message:".format(expected_error))
-@pytest.mark.skipif(not cmd2.cmd2.can_clip,
+@pytest.mark.skipif(not cmd2.can_clip,
reason="Pyperclip could not find a copy/paste mechanism for your system")
def test_send_to_paste_buffer(base_app):
# Test writing to the PasteBuffer/Clipboard
run_cmd(base_app, 'help >')
expected = normalize(BASE_HELP)
- assert normalize(cmd2.cmd2.get_paste_buffer()) == expected
+ assert normalize(cmd2.get_paste_buffer()) == expected
# Test appending to the PasteBuffer/Clipboard
run_cmd(base_app, 'help history >>')
expected = normalize(BASE_HELP + '\n' + HELP_HISTORY)
- assert normalize(cmd2.cmd2.get_paste_buffer()) == expected
+ assert normalize(cmd2.get_paste_buffer()) == expected
def test_base_timing(base_app, capsys):
@@ -1314,7 +1314,7 @@ optional arguments:
reason="cmd2._which function only used on Mac and Linux")
def test_which_editor_good():
editor = 'vi'
- path = cmd2.cmd2._which(editor)
+ path = cmd2._which(editor)
# Assert that the vi editor was found because it should exist on all Mac and Linux systems
assert path
@@ -1322,7 +1322,7 @@ def test_which_editor_good():
reason="cmd2._which function only used on Mac and Linux")
def test_which_editor_bad():
editor = 'notepad.exe'
- path = cmd2.cmd2._which(editor)
+ path = cmd2._which(editor)
# Assert that the editor wasn't found because no notepad.exe on non-Windows systems ;-)
assert path is None
@@ -1349,7 +1349,7 @@ def multiline_app():
return app
def test_multiline_complete_empty_statement_raises_exception(multiline_app):
- with pytest.raises(cmd2.cmd2.EmptyStatement):
+ with pytest.raises(cmd2.EmptyStatement):
multiline_app._complete_statement('')
def test_multiline_complete_statement_without_terminator(multiline_app):
@@ -1367,7 +1367,7 @@ def test_multiline_complete_statement_without_terminator(multiline_app):
def test_clipboard_failure(capsys):
# Force cmd2 clipboard to be disabled
- cmd2.cmd2.disable_clip()
+ cmd2.disable_clip()
app = cmd2.Cmd()
# Redirect command output to the clipboard when a clipboard isn't present
diff --git a/tests/test_completion.py b/tests/test_completion.py
index ef35b635..7026db48 100644
--- a/tests/test_completion.py
+++ b/tests/test_completion.py
@@ -12,7 +12,7 @@ import argparse
import os
import sys
-import cmd2
+from cmd2 import cmd2
import pytest
from .conftest import complete_tester, StdOut
from examples.subcommands import SubcommandsExample
diff --git a/tests/test_parsing.py b/tests/test_parsing.py
index 2682ec68..b61e2d06 100644
--- a/tests/test_parsing.py
+++ b/tests/test_parsing.py
@@ -8,16 +8,14 @@ problematic because they worked properly for some versions of pyparsing but not
Copyright 2017 Todd Leonhardt <todd.leonhardt@gmail.com>
Released under MIT license, see LICENSE file
"""
-import sys
-
-import cmd2
+from cmd2 import cmd2
import pytest
@pytest.fixture
def hist():
from cmd2.cmd2 import HistoryItem
- h = cmd2.cmd2.History([HistoryItem('first'), HistoryItem('second'), HistoryItem('third'), HistoryItem('fourth')])
+ h = cmd2.History([HistoryItem('first'), HistoryItem('second'), HistoryItem('third'), HistoryItem('fourth')])
return h
# Case-sensitive parser
@@ -25,7 +23,7 @@ def hist():
def parser():
c = cmd2.Cmd()
c.multilineCommands = ['multiline']
- c.parser_manager = cmd2.cmd2.ParserManager(redirector=c.redirector, terminators=c.terminators,
+ c.parser_manager = cmd2.ParserManager(redirector=c.redirector, terminators=c.terminators,
multilineCommands=c.multilineCommands, legalChars=c.legalChars,
commentGrammars=c.commentGrammars, commentInProgress=c.commentInProgress,
blankLinesAllowed=c.blankLinesAllowed, prefixParser=c.prefixParser,
@@ -38,7 +36,7 @@ def parser():
def cs_pm():
c = cmd2.Cmd()
c.multilineCommands = ['multiline']
- c.parser_manager = cmd2.cmd2.ParserManager(redirector=c.redirector, terminators=c.terminators,
+ c.parser_manager = cmd2.ParserManager(redirector=c.redirector, terminators=c.terminators,
multilineCommands=c.multilineCommands, legalChars=c.legalChars,
commentGrammars=c.commentGrammars, commentInProgress=c.commentInProgress,
blankLinesAllowed=c.blankLinesAllowed, prefixParser=c.prefixParser,
@@ -77,7 +75,7 @@ def test_history_get(hist):
def test_cast():
- cast = cmd2.cmd2.cast
+ cast = cmd2.cast
# Boolean
assert cast(True, True) == True
@@ -101,7 +99,7 @@ def test_cast():
def test_cast_problems(capsys):
- cast = cmd2.cmd2.cast
+ cast = cmd2.cast
expected = 'Problem setting parameter (now {}) to {}; incorrect type?\n'
@@ -327,8 +325,8 @@ def test_parse_input_redirect_from_unicode_filename(input_parser):
def test_empty_statement_raises_exception():
app = cmd2.Cmd()
- with pytest.raises(cmd2.cmd2.EmptyStatement):
+ with pytest.raises(cmd2.EmptyStatement):
app._complete_statement('')
- with pytest.raises(cmd2.cmd2.EmptyStatement):
+ with pytest.raises(cmd2.EmptyStatement):
app._complete_statement(' ')
diff --git a/tests/test_submenu.py b/tests/test_submenu.py
index fbb9857b..db334daa 100644
--- a/tests/test_submenu.py
+++ b/tests/test_submenu.py
@@ -4,7 +4,7 @@ Cmd2 testing for argument parsing
"""
import pytest
-import cmd2
+from cmd2 import cmd2
from .conftest import run_cmd, StdOut, normalize
diff --git a/tests/test_transcript.py b/tests/test_transcript.py
index 8ee5f3f6..4f821c06 100644
--- a/tests/test_transcript.py
+++ b/tests/test_transcript.py
@@ -14,8 +14,8 @@ import random
from unittest import mock
import pytest
-import cmd2
-from cmd2 import set_posix_shlex, set_strip_quotes
+from cmd2 import cmd2
+from cmd2.cmd2 import set_posix_shlex, set_strip_quotes
from .conftest import run_cmd, StdOut, normalize
class CmdLineApp(cmd2.Cmd):
@@ -189,7 +189,7 @@ now: --->
assert out == expected
-class TestMyAppCase(cmd2.cmd2.Cmd2TestCase):
+class TestMyAppCase(cmd2.Cmd2TestCase):
CmdApp = CmdLineApp
CmdApp.testfiles = ['tests/transcript.txt']
@@ -293,7 +293,7 @@ def test_transcript(request, capsys, filename, feedback_to_output):
def test_parse_transcript_expected(expected, transformed):
app = CmdLineApp()
- class TestMyAppCase(cmd2.cmd2.Cmd2TestCase):
+ class TestMyAppCase(cmd2.Cmd2TestCase):
cmdapp = app
testcase = TestMyAppCase()