summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rw-r--r--cmd2/__init__.py12
-rw-r--r--cmd2/argparse_custom.py12
-rw-r--r--cmd2/cmd2.py36
-rw-r--r--examples/custom_parser.py35
-rwxr-xr-xexamples/override_parser.py24
6 files changed, 101 insertions, 20 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8264049b..d78bf0ea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@
* Enhancements
* Added `read_input()` function that is used to read from stdin. Unlike the Python built-in `input()`, it also has
an argument to disable tab completion while input is being entered.
+ * Added capability to override the argument parser class used by cmd2 built-in commands. See override_parser.py
+ example for more details.
## 0.9.20 (November 12, 2019)
* Bug Fixes
diff --git a/cmd2/__init__.py b/cmd2/__init__.py
index 8e8a8845..8fc5e9f2 100644
--- a/cmd2/__init__.py
+++ b/cmd2/__init__.py
@@ -11,7 +11,17 @@ except DistributionNotFound:
pass
from .ansi import style
-from .argparse_custom import Cmd2ArgumentParser, CompletionError, CompletionItem
+from .argparse_custom import Cmd2ArgumentParser, CompletionError, CompletionItem, set_default_argument_parser
+
+# Check if user has defined a module that sets a custom value for argparse_custom.DEFAULT_ARGUMENT_PARSER
+import argparse
+cmd2_parser_module = getattr(argparse, 'cmd2_parser_module', None)
+if cmd2_parser_module is not None:
+ import importlib
+ importlib.import_module(cmd2_parser_module)
+
+# Get the current value for argparse_custom.DEFAULT_ARGUMENT_PARSER
+from .argparse_custom import DEFAULT_ARGUMENT_PARSER
from .cmd2 import Cmd, EmptyStatement
from .constants import COMMAND_NAME, DEFAULT_SHORTCUTS
from .decorators import categorize, with_argument_list, with_argparser, with_argparser_and_unknown_args, with_category
diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py
index f7dbc8a3..c6aa6550 100644
--- a/cmd2/argparse_custom.py
+++ b/cmd2/argparse_custom.py
@@ -182,7 +182,7 @@ import re
import sys
# noinspection PyUnresolvedReferences,PyProtectedMember
from argparse import ZERO_OR_MORE, ONE_OR_MORE, ArgumentError, _
-from typing import Callable, Optional, Tuple, Union
+from typing import Callable, Optional, Tuple, Type, Union
from .ansi import ansi_aware_write, style_error
@@ -806,3 +806,13 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
if file is None:
file = sys.stderr
ansi_aware_write(file, message)
+
+
+# The default ArgumentParser class for a cmd2 app
+DEFAULT_ARGUMENT_PARSER = Cmd2ArgumentParser
+
+
+def set_default_argument_parser(parser: Type[argparse.ArgumentParser]) -> None:
+ """Set the default ArgumentParser class for a cmd2 app"""
+ global DEFAULT_ARGUMENT_PARSER
+ DEFAULT_ARGUMENT_PARSER = parser
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 29bf9529..bd9dd4ff 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -47,7 +47,7 @@ from . import ansi
from . import constants
from . import plugin
from . import utils
-from .argparse_custom import Cmd2ArgumentParser, CompletionItem
+from .argparse_custom import CompletionItem, DEFAULT_ARGUMENT_PARSER
from .clipboard import can_clip, get_paste_buffer, write_to_paste_buffer
from .decorators import with_argparser
from .history import History, HistoryItem
@@ -2244,7 +2244,7 @@ class Cmd(cmd.Cmd):
"An alias is a command that enables replacement of a word by another string.")
alias_epilog = ("See also:\n"
" macro")
- alias_parser = Cmd2ArgumentParser(description=alias_description, epilog=alias_epilog)
+ alias_parser = DEFAULT_ARGUMENT_PARSER(description=alias_description, epilog=alias_epilog)
# Add subcommands to alias
alias_subparsers = alias_parser.add_subparsers(dest='subcommand')
@@ -2421,7 +2421,7 @@ class Cmd(cmd.Cmd):
"A macro is similar to an alias, but it can contain argument placeholders.")
macro_epilog = ("See also:\n"
" alias")
- macro_parser = Cmd2ArgumentParser(description=macro_description, epilog=macro_epilog)
+ macro_parser = DEFAULT_ARGUMENT_PARSER(description=macro_description, epilog=macro_epilog)
# Add subcommands to macro
macro_subparsers = macro_parser.add_subparsers(dest='subcommand')
@@ -2537,8 +2537,8 @@ class Cmd(cmd.Cmd):
completer = AutoCompleter(argparser, self)
return completer.complete_subcommand_help(tokens, text, line, begidx, endidx)
- help_parser = Cmd2ArgumentParser(description="List available commands or provide "
- "detailed help for a specific command")
+ help_parser = DEFAULT_ARGUMENT_PARSER(description="List available commands or provide "
+ "detailed help for a specific command")
help_parser.add_argument('command', nargs=argparse.OPTIONAL, help="command to retrieve help for",
completer_method=complete_help_command)
help_parser.add_argument('subcommands', nargs=argparse.REMAINDER, help="subcommand(s) to retrieve help for",
@@ -2707,7 +2707,7 @@ class Cmd(cmd.Cmd):
command = ''
self.stdout.write("\n")
- @with_argparser(Cmd2ArgumentParser(description="List available shortcuts"))
+ @with_argparser(DEFAULT_ARGUMENT_PARSER(description="List available shortcuts"))
def do_shortcuts(self, _: argparse.Namespace) -> None:
"""List available shortcuts"""
# Sort the shortcut tuples by name
@@ -2715,13 +2715,13 @@ class Cmd(cmd.Cmd):
result = "\n".join('{}: {}'.format(sc[0], sc[1]) for sc in sorted_shortcuts)
self.poutput("Shortcuts for other commands:\n{}".format(result))
- @with_argparser(Cmd2ArgumentParser(epilog=INTERNAL_COMMAND_EPILOG))
+ @with_argparser(DEFAULT_ARGUMENT_PARSER(epilog=INTERNAL_COMMAND_EPILOG))
def do_eof(self, _: argparse.Namespace) -> bool:
"""Called when <Ctrl>-D is pressed"""
# Return True to stop the command loop
return True
- @with_argparser(Cmd2ArgumentParser(description="Exit this application"))
+ @with_argparser(DEFAULT_ARGUMENT_PARSER(description="Exit this application"))
def do_quit(self, _: argparse.Namespace) -> bool:
"""Exit this application"""
# Return True to stop the command loop
@@ -2824,7 +2824,7 @@ class Cmd(cmd.Cmd):
"Accepts abbreviated parameter names so long as there is no ambiguity.\n"
"Call without arguments for a list of settable parameters with their values.")
- set_parser = Cmd2ArgumentParser(description=set_description)
+ set_parser = DEFAULT_ARGUMENT_PARSER(description=set_description)
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('param', nargs=argparse.OPTIONAL, help='parameter to set or view',
@@ -2869,7 +2869,7 @@ class Cmd(cmd.Cmd):
if onchange_hook is not None:
onchange_hook(old=orig_value, new=new_value) # pylint: disable=not-callable
- shell_parser = Cmd2ArgumentParser(description="Execute a command as if at the OS prompt")
+ shell_parser = DEFAULT_ARGUMENT_PARSER(description="Execute a command as if at the OS prompt")
shell_parser.add_argument('command', help='the command to run', completer_method=shell_cmd_complete)
shell_parser.add_argument('command_args', nargs=argparse.REMAINDER, help='arguments to pass to command',
completer_method=path_complete)
@@ -3034,7 +3034,7 @@ class Cmd(cmd.Cmd):
"If you see strange parsing behavior, it's best to just open the Python shell\n"
"by providing no arguments to py and run more complex statements there.")
- py_parser = Cmd2ArgumentParser(description=py_description)
+ py_parser = DEFAULT_ARGUMENT_PARSER(description=py_description)
py_parser.add_argument('command', nargs=argparse.OPTIONAL, help="command to run")
py_parser.add_argument('remainder', nargs=argparse.REMAINDER, help="remainder of command")
@@ -3156,7 +3156,7 @@ class Cmd(cmd.Cmd):
return py_bridge.stop
- run_pyscript_parser = Cmd2ArgumentParser(description="Run a Python script file inside the console")
+ run_pyscript_parser = DEFAULT_ARGUMENT_PARSER(description="Run a Python script file inside the console")
run_pyscript_parser.add_argument('script_path', help='path to the script file', completer_method=path_complete)
run_pyscript_parser.add_argument('script_arguments', nargs=argparse.REMAINDER,
help='arguments to pass to script', completer_method=path_complete)
@@ -3201,7 +3201,7 @@ class Cmd(cmd.Cmd):
# Only include the do_ipy() method if IPython is available on the system
if ipython_available: # pragma: no cover
- @with_argparser(Cmd2ArgumentParser(description="Enter an interactive IPython shell"))
+ @with_argparser(DEFAULT_ARGUMENT_PARSER(description="Enter an interactive IPython shell"))
def do_ipy(self, _: argparse.Namespace) -> None:
"""Enter an interactive IPython shell"""
from .py_bridge import PyBridge
@@ -3232,7 +3232,7 @@ class Cmd(cmd.Cmd):
history_description = "View, run, edit, save, or clear previously entered commands"
- history_parser = Cmd2ArgumentParser(description=history_description)
+ history_parser = DEFAULT_ARGUMENT_PARSER(description=history_description)
history_action_group = history_parser.add_mutually_exclusive_group()
history_action_group.add_argument('-r', '--run', action='store_true', help='run selected history items')
history_action_group.add_argument('-e', '--edit', action='store_true',
@@ -3543,7 +3543,7 @@ class Cmd(cmd.Cmd):
"\n"
" set editor (program-name)")
- edit_parser = Cmd2ArgumentParser(description=edit_description)
+ edit_parser = DEFAULT_ARGUMENT_PARSER(description=edit_description)
edit_parser.add_argument('file_path', nargs=argparse.OPTIONAL,
help="optional path to a file to open in editor", completer_method=path_complete)
@@ -3584,7 +3584,7 @@ class Cmd(cmd.Cmd):
"If the -t/--transcript flag is used, this command instead records\n"
"the output of the script commands to a transcript for testing purposes.\n")
- run_script_parser = Cmd2ArgumentParser(description=run_script_description)
+ run_script_parser = DEFAULT_ARGUMENT_PARSER(description=run_script_description)
run_script_parser.add_argument('-t', '--transcript', metavar='TRANSCRIPT_FILE',
help='record the output of the script as a transcript file',
completer_method=path_complete)
@@ -3658,8 +3658,8 @@ class Cmd(cmd.Cmd):
relative_run_script_epilog = ("Notes:\n"
" This command is intended to only be used within text file scripts.")
- relative_run_script_parser = Cmd2ArgumentParser(description=relative_run_script_description,
- epilog=relative_run_script_epilog)
+ relative_run_script_parser = DEFAULT_ARGUMENT_PARSER(description=relative_run_script_description,
+ epilog=relative_run_script_epilog)
relative_run_script_parser.add_argument('file_path', help='a file path pointing to a script')
@with_argparser(relative_run_script_parser)
diff --git a/examples/custom_parser.py b/examples/custom_parser.py
new file mode 100644
index 00000000..efeb362e
--- /dev/null
+++ b/examples/custom_parser.py
@@ -0,0 +1,35 @@
+# coding=utf-8
+"""
+Defines the CustomParser used with override_parser.py example
+"""
+import sys
+
+from cmd2 import Cmd2ArgumentParser, set_default_argument_parser
+from cmd2.ansi import style_warning
+
+
+# First define the parser
+class CustomParser(Cmd2ArgumentParser):
+ """Overrides error class"""
+ def __init__(self, *args, **kwargs) -> None:
+ super().__init__(*args, **kwargs)
+
+ def error(self, message: str) -> None:
+ """Custom override that applies custom formatting to the error message"""
+ lines = message.split('\n')
+ linum = 0
+ formatted_message = ''
+ for line in lines:
+ if linum == 0:
+ formatted_message = 'Error: ' + line
+ else:
+ formatted_message += '\n ' + line
+ linum += 1
+
+ self.print_usage(sys.stderr)
+ formatted_message = style_warning(formatted_message)
+ self.exit(2, '{}\n\n'.format(formatted_message))
+
+
+# Now set the default parser for a cmd2 app
+set_default_argument_parser(CustomParser)
diff --git a/examples/override_parser.py b/examples/override_parser.py
new file mode 100755
index 00000000..ddfa8323
--- /dev/null
+++ b/examples/override_parser.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+# coding=utf-8
+# flake8: noqa F402
+"""
+The standard parser used by cmd2 built-in commands is Cmd2ArgumentParser.
+The following code shows how to override it with your own parser class.
+"""
+
+# First set a value called argparse.cmd2_parser_module with the module that defines the custom parser
+# See the code for custom_parser.py. It simply defines a parser and calls cmd2.set_default_argument_parser()
+# with the custom parser's type.
+import argparse
+argparse.cmd2_parser_module = 'examples.custom_parser'
+
+# Next import stuff from cmd2. It will import your module just before the cmd2.Cmd class file is imported
+# and therefore override the parser class it uses on its commands.
+from cmd2 import cmd2
+
+if __name__ == '__main__':
+ import sys
+ app = cmd2.Cmd(use_ipython=True, persistent_history_file='cmd2_history.dat')
+ app.locals_in_py = True # Enable access to "self" within the py command
+ app.debug = True # Show traceback if/when an exception occurs
+ sys.exit(app.cmdloop())