first pass at interactive app
This commit is contained in:
@@ -25,5 +25,7 @@ To do
|
|||||||
to manage transactions
|
to manage transactions
|
||||||
- switch setup/teardown functions in app to use some sort of context
|
- switch setup/teardown functions in app to use some sort of context
|
||||||
manager?
|
manager?
|
||||||
- interactive shell mode
|
|
||||||
- add options to csv formatter to control output (delimiter, etc.)
|
- add options to csv formatter to control output (delimiter, etc.)
|
||||||
|
- option to spit out bash completion data
|
||||||
|
- move command execution into a separate class to be used by App and
|
||||||
|
InteractiveApp?
|
||||||
|
|||||||
46
cliff/app.py
46
cliff/app.py
@@ -8,6 +8,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from .help import HelpAction, HelpCommand
|
from .help import HelpAction, HelpCommand
|
||||||
|
from .interactive import InteractiveApp
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -39,6 +40,7 @@ class App(object):
|
|||||||
self.stdout = stdout or sys.stdout
|
self.stdout = stdout or sys.stdout
|
||||||
self.stderr = stderr or sys.stderr
|
self.stderr = stderr or sys.stderr
|
||||||
self.parser = self.build_option_parser(description, version)
|
self.parser = self.build_option_parser(description, version)
|
||||||
|
self.interactive_mode = False
|
||||||
|
|
||||||
def build_option_parser(self, description, version):
|
def build_option_parser(self, description, version):
|
||||||
"""Return an argparse option parser for this application.
|
"""Return an argparse option parser for this application.
|
||||||
@@ -77,7 +79,7 @@ class App(object):
|
|||||||
help="show this help message and exit",
|
help="show this help message and exit",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--debug',
|
'--debug',
|
||||||
default=False,
|
default=False,
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='show tracebacks on errors',
|
help='show tracebacks on errors',
|
||||||
@@ -112,6 +114,28 @@ class App(object):
|
|||||||
root_logger.addHandler(console)
|
root_logger.addHandler(console)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def run(self, argv):
|
||||||
|
"""Equivalent to the main program for the application.
|
||||||
|
"""
|
||||||
|
self.options, remainder = self.parser.parse_known_args(argv)
|
||||||
|
self.configure_logging()
|
||||||
|
self.initialize_app()
|
||||||
|
result = 1
|
||||||
|
if not remainder:
|
||||||
|
result = self.interact()
|
||||||
|
else:
|
||||||
|
result = self.run_subcommand(remainder)
|
||||||
|
return result
|
||||||
|
|
||||||
|
# FIXME(dhellmann): Consider moving these command handling methods
|
||||||
|
# to a separate class.
|
||||||
|
def initialize_app(self):
|
||||||
|
"""Hook for subclasses to take global initialization action
|
||||||
|
after the arguments are parsed but before a command is run.
|
||||||
|
Invoked only once, even in interactive mode.
|
||||||
|
"""
|
||||||
|
return
|
||||||
|
|
||||||
def prepare_to_run_command(self, cmd):
|
def prepare_to_run_command(self, cmd):
|
||||||
"""Perform any preliminary work needed to run a command.
|
"""Perform any preliminary work needed to run a command.
|
||||||
"""
|
"""
|
||||||
@@ -122,20 +146,22 @@ class App(object):
|
|||||||
"""
|
"""
|
||||||
return
|
return
|
||||||
|
|
||||||
def run(self, argv):
|
def interact(self):
|
||||||
"""Equivalent to the main program for the application.
|
self.interactive_mode = True
|
||||||
"""
|
interpreter = InteractiveApp(self, self.command_manager, self.stdin, self.stdout)
|
||||||
if not argv:
|
interpreter.prompt = '(%s) ' % self.NAME
|
||||||
argv = ['-h']
|
interpreter.cmdloop()
|
||||||
self.options, remainder = self.parser.parse_known_args(argv)
|
return 0
|
||||||
self.configure_logging()
|
|
||||||
cmd_factory, cmd_name, sub_argv = self.command_manager.find_command(remainder)
|
def run_subcommand(self, argv):
|
||||||
|
cmd_factory, cmd_name, sub_argv = self.command_manager.find_command(argv)
|
||||||
cmd = cmd_factory(self, self.options)
|
cmd = cmd_factory(self, self.options)
|
||||||
err = None
|
err = None
|
||||||
result = 1
|
result = 1
|
||||||
try:
|
try:
|
||||||
self.prepare_to_run_command(cmd)
|
self.prepare_to_run_command(cmd)
|
||||||
cmd_parser = cmd.get_parser(' '.join([self.NAME, cmd_name]))
|
full_name = cmd_name if self.interactive_mode else ' '.join([self.NAME, cmd_name])
|
||||||
|
cmd_parser = cmd.get_parser(full_name)
|
||||||
parsed_args = cmd_parser.parse_args(sub_argv)
|
parsed_args = cmd_parser.parse_args(sub_argv)
|
||||||
result = cmd.run(parsed_args)
|
result = cmd.run(parsed_args)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
|
|||||||
@@ -38,9 +38,13 @@ class HelpCommand(Command):
|
|||||||
|
|
||||||
def run(self, parsed_args):
|
def run(self, parsed_args):
|
||||||
if parsed_args.cmd:
|
if parsed_args.cmd:
|
||||||
cmd_factory, name, search_args = self.app.command_manager.find_command(parsed_args.cmd)
|
cmd_factory, cmd_name, search_args = self.app.command_manager.find_command(parsed_args.cmd)
|
||||||
cmd = cmd_factory(self.app, search_args)
|
cmd = cmd_factory(self.app, search_args)
|
||||||
cmd_parser = cmd.get_parser(' '.join([self.app.NAME, name]))
|
full_name = (cmd_name
|
||||||
|
if self.app.interactive_mode
|
||||||
|
else ' '.join([self.app.NAME, cmd_name])
|
||||||
|
)
|
||||||
|
cmd_parser = cmd.get_parser(full_name)
|
||||||
else:
|
else:
|
||||||
cmd_parser = self.get_parser(' '.join([self.app.NAME, 'help']))
|
cmd_parser = self.get_parser(' '.join([self.app.NAME, 'help']))
|
||||||
cmd_parser.parse_args(['--help'])
|
cmd_parser.parse_args(['--help'])
|
||||||
|
|||||||
81
cliff/interactive.py
Normal file
81
cliff/interactive.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
"""Application base class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import logging
|
||||||
|
import logging.handlers
|
||||||
|
import shlex
|
||||||
|
|
||||||
|
import cmd2
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class InteractiveApp(cmd2.Cmd):
|
||||||
|
|
||||||
|
use_rawinput = True
|
||||||
|
doc_header = "Shell commands (type help <topic>):"
|
||||||
|
app_cmd_header = "Application commands (type help <topic>):"
|
||||||
|
|
||||||
|
def __init__(self, parent_app, command_manager, stdin, stdout):
|
||||||
|
self.parent_app = parent_app
|
||||||
|
self.command_manager = command_manager
|
||||||
|
cmd2.Cmd.__init__(self, 'tab', stdin=stdin, stdout=stdout)
|
||||||
|
|
||||||
|
def default(self, line):
|
||||||
|
# Tie in the the default command processor to
|
||||||
|
# dispatch commands known to the command manager.
|
||||||
|
# We send the message through our parent app,
|
||||||
|
# since it already has the logic for executing
|
||||||
|
# the subcommand.
|
||||||
|
line_parts = shlex.split(line)
|
||||||
|
self.parent_app.run_subcommand(line_parts)
|
||||||
|
|
||||||
|
def completedefault(self, text, line, begidx, endidx):
|
||||||
|
"""Tab-completion for commands known to the command manager.
|
||||||
|
|
||||||
|
Does not handle options on the commands.
|
||||||
|
"""
|
||||||
|
if not text:
|
||||||
|
completions = sorted(n for n, v in self.command_manager)
|
||||||
|
else:
|
||||||
|
completions = sorted(n for n, v in self.command_manager
|
||||||
|
if n.startswith(text)
|
||||||
|
)
|
||||||
|
return completions
|
||||||
|
|
||||||
|
def help_help(self):
|
||||||
|
# Use the command manager to get instructions for "help"
|
||||||
|
self.default('help help')
|
||||||
|
|
||||||
|
def do_help(self, arg):
|
||||||
|
if arg:
|
||||||
|
# Check if the arg is a builtin command or something
|
||||||
|
# coming from the command manager
|
||||||
|
arg_parts = shlex.split(arg)
|
||||||
|
method_name = '_'.join(
|
||||||
|
itertools.chain(['do'],
|
||||||
|
itertools.takewhile(lambda x: not x.startswith('-'),
|
||||||
|
arg_parts)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# Have the command manager version of the help
|
||||||
|
# command produce the help text since cmd and
|
||||||
|
# cmd2 do not provide help for "help"
|
||||||
|
if hasattr(self, method_name):
|
||||||
|
return cmd2.Cmd.do_help(self, arg)
|
||||||
|
# Dispatch to the underlying help command,
|
||||||
|
# which knows how to provide help for extension
|
||||||
|
# commands.
|
||||||
|
self.default('help ' + arg)
|
||||||
|
else:
|
||||||
|
cmd2.Cmd.do_help(self, arg)
|
||||||
|
cmd_names = [n for n, v in self.command_manager]
|
||||||
|
self.print_topics(self.app_cmd_header, cmd_names, 15, 80)
|
||||||
|
return
|
||||||
|
|
||||||
|
def get_names(self):
|
||||||
|
return [n
|
||||||
|
for n in cmd2.Cmd.get_names(self)
|
||||||
|
if not n.startswith('do__')
|
||||||
|
]
|
||||||
@@ -16,6 +16,9 @@ class DemoApp(App):
|
|||||||
command_manager=CommandManager('cliff.demo'),
|
command_manager=CommandManager('cliff.demo'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def initialize_app(self):
|
||||||
|
self.log.debug('initialize_app')
|
||||||
|
|
||||||
def prepare_to_run_command(self, cmd):
|
def prepare_to_run_command(self, cmd):
|
||||||
self.log.debug('prepare_to_run_command %s', cmd.__class__.__name__)
|
self.log.debug('prepare_to_run_command %s', cmd.__class__.__name__)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user