first pass at interactive app
This commit is contained in:
		@@ -25,5 +25,7 @@ To do
 | 
			
		||||
  to manage transactions
 | 
			
		||||
- switch setup/teardown functions in app to use some sort of context
 | 
			
		||||
  manager?
 | 
			
		||||
- interactive shell mode
 | 
			
		||||
- 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
 | 
			
		||||
 | 
			
		||||
from .help import HelpAction, HelpCommand
 | 
			
		||||
from .interactive import InteractiveApp
 | 
			
		||||
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
@@ -39,6 +40,7 @@ class App(object):
 | 
			
		||||
        self.stdout = stdout or sys.stdout
 | 
			
		||||
        self.stderr = stderr or sys.stderr
 | 
			
		||||
        self.parser = self.build_option_parser(description, version)
 | 
			
		||||
        self.interactive_mode = False
 | 
			
		||||
 | 
			
		||||
    def build_option_parser(self, description, version):
 | 
			
		||||
        """Return an argparse option parser for this application.
 | 
			
		||||
@@ -77,7 +79,7 @@ class App(object):
 | 
			
		||||
            help="show this help message and exit",
 | 
			
		||||
            )
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--debug', 
 | 
			
		||||
            '--debug',
 | 
			
		||||
            default=False,
 | 
			
		||||
            action='store_true',
 | 
			
		||||
            help='show tracebacks on errors',
 | 
			
		||||
@@ -112,6 +114,28 @@ class App(object):
 | 
			
		||||
        root_logger.addHandler(console)
 | 
			
		||||
        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):
 | 
			
		||||
        """Perform any preliminary work needed to run a command.
 | 
			
		||||
        """
 | 
			
		||||
@@ -122,20 +146,22 @@ class App(object):
 | 
			
		||||
        """
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    def run(self, argv):
 | 
			
		||||
        """Equivalent to the main program for the application.
 | 
			
		||||
        """
 | 
			
		||||
        if not argv:
 | 
			
		||||
            argv = ['-h']
 | 
			
		||||
        self.options, remainder = self.parser.parse_known_args(argv)
 | 
			
		||||
        self.configure_logging()
 | 
			
		||||
        cmd_factory, cmd_name, sub_argv = self.command_manager.find_command(remainder)
 | 
			
		||||
    def interact(self):
 | 
			
		||||
        self.interactive_mode = True
 | 
			
		||||
        interpreter = InteractiveApp(self, self.command_manager, self.stdin, self.stdout)
 | 
			
		||||
        interpreter.prompt = '(%s) ' % self.NAME
 | 
			
		||||
        interpreter.cmdloop()
 | 
			
		||||
        return 0
 | 
			
		||||
 | 
			
		||||
    def run_subcommand(self, argv):
 | 
			
		||||
        cmd_factory, cmd_name, sub_argv = self.command_manager.find_command(argv)
 | 
			
		||||
        cmd = cmd_factory(self, self.options)
 | 
			
		||||
        err = None
 | 
			
		||||
        result = 1
 | 
			
		||||
        try:
 | 
			
		||||
            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)
 | 
			
		||||
            result = cmd.run(parsed_args)
 | 
			
		||||
        except Exception as err:
 | 
			
		||||
 
 | 
			
		||||
@@ -38,9 +38,13 @@ class HelpCommand(Command):
 | 
			
		||||
 | 
			
		||||
    def run(self, parsed_args):
 | 
			
		||||
        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_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:
 | 
			
		||||
            cmd_parser = self.get_parser(' '.join([self.app.NAME, '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'),
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    def initialize_app(self):
 | 
			
		||||
        self.log.debug('initialize_app')
 | 
			
		||||
 | 
			
		||||
    def prepare_to_run_command(self, cmd):
 | 
			
		||||
        self.log.debug('prepare_to_run_command %s', cmd.__class__.__name__)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user