From f201a3efe4cf7e1a20292dbd9858163358596081 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 11 May 2010 06:52:31 -0400 Subject: [PATCH] Added interaction to hooke.ui.commandline.LocalCommandExit. Also broke out commandline's exit and help commands. They were getting complicated enough to deserve their own classes. --- hooke/command.py | 34 +++++++++- hooke/hooke_cli.py | 22 ------- hooke/ui/commandline.py | 133 +++++++++++++++++++++++++++++----------- 3 files changed, 129 insertions(+), 60 deletions(-) diff --git a/hooke/command.py b/hooke/command.py index b6d6b35..73f01fe 100644 --- a/hooke/command.py +++ b/hooke/command.py @@ -14,6 +14,11 @@ class CommandExit (Exception): class Success (CommandExit): pass +class Exit (Success): + """The command requests an end to the interpreter session. + """ + pass + class Failure (CommandExit): pass @@ -24,6 +29,32 @@ class UncaughtException (Failure): sys.exc_clear() super(UncaughtException, self).__init__(self.exc_string) +class Interaction (object): + """Mid-command inter-process interaction. + """ + pass + +class Request (Interaction): + """Command engine requests for information from the UI. + """ + def __init__(self, msg, default=None): + super(Request, self).__init__() + self.msg = msg + self.default = default + +class Response (Interaction): + """UI response to a :class:`Request`. + """ + def __init__(self, value): + super(Response, self).__init__() + self.value = value + +class BooleanRequest (Request): + pass + +class BooleanResponse (Response): + pass + class Command (object): """One-line command description here. @@ -123,7 +154,8 @@ class Command (object): """ name_part = 'Command: %s' % name_fn(self.name) if len(self.aliases) > 0: - name_part += ' (%s)' % ', '.join([name_fn(n) for n in aliases]) + name_part += ' (%s)' % ', '.join( + [name_fn(n) for n in self.aliases]) parts = [name_part] if len(self.arguments) > 0: argument_part = ['Arguments:', ''] diff --git a/hooke/hooke_cli.py b/hooke/hooke_cli.py index bbb6cc6..0338522 100644 --- a/hooke/hooke_cli.py +++ b/hooke/hooke_cli.py @@ -798,28 +798,6 @@ Syntax: current print '---' print 'Loaded plugins:',self.config['loaded_plugins'] - def help_exit(self): - print ''' -EXIT, QUIT -Exits the program cleanly. ------- -Syntax: exit -Syntax: quit -''' - def do_exit(self,args): - we_exit='N' - - if (not self.playlist_saved) or (not self.notes_saved): - we_exit=linp.safeinput('You did not save your playlist and/or notes. Exit?',['n']) - else: - we_exit=linp.safeinput('Exit?',['y']) - - if we_exit[0].upper()=='Y': - wx.CallAfter(self.frame.Close) - sys.exit(0) - else: - return - if __name__ == '__main__': diff --git a/hooke/ui/commandline.py b/hooke/ui/commandline.py index 027a787..5198f8f 100644 --- a/hooke/ui/commandline.py +++ b/hooke/ui/commandline.py @@ -7,12 +7,18 @@ import optparse import readline # including readline makes cmd.Cmd.cmdloop() smarter import shlex -from ..command import CommandExit, Command, Argument +from ..command import CommandExit, Exit, BooleanRequest, BooleanResponse, \ + Command, Argument, StoreValue from ..ui import UserInterface, CommandMessage # Define a few helper classes. +class Default (object): + """Marker for options not given on the command line. + """ + pass + class CommandLineParser (optparse.OptionParser): """Implement a command line syntax for a :class:`hooke.command.Command`. @@ -28,7 +34,7 @@ class CommandLineParser (optparse.OptionParser): if a.optional == True: name = name_fn(a.name) self.add_option( - '--%s' % name, dest=name, default=a.default) + '--%s' % name, dest=name, default=Default) self.command_opts.append(a) else: self.command_args.append(a) @@ -71,10 +77,15 @@ class DoCommand (CommandMethod): self.cmd.inqueue.put(CommandMessage(self.command, args)) while True: msg = self.cmd.outqueue.get() - if isinstance(msg, CommandExit): + if isinstance(msg, Exit): + return True + elif isinstance(msg, CommandExit): self.cmd.stdout.write(msg.__class__.__name__+'\n') self.cmd.stdout.write(str(msg).rstrip()+'\n') break + elif isinstance(msg, BooleanRequest): + self._boolean_request(msg) + continue self.cmd.stdout.write(str(msg).rstrip()+'\n') def _parse_args(self, args): @@ -87,12 +98,27 @@ class DoCommand (CommandMethod): len(self.parser.command_args))) params = {} for argument in self.parser.command_opts: - params[argument.name] = getattr(options, - self.name_fn(argument.name)) + value = getattr(options, self.name_fn(argument.name)) + if value != Default: + params[argument.name] = value for i,argument in enumerate(self.parser.command_args): params[argument.name] = args[i] return params + def _boolean_request(self, msg): + if msg.default == True: + yn = ' [Y/n] ' + else: + yn = ' [y/N] ' + self.cmd.stdout.write(msg.msg+yn) + response = self.cmd.stdin.readline().strip().lower() + if response.startswith('y'): + self.cmd.inqueue.put(BooleanResponse(True)) + elif response.startswith('n'): + self.cmd.inqueue.put(BooleanResponse(False)) + else: + self.cmd.inqueue.put(BooleanResponse(msg.default)) + class HelpCommand (CommandMethod): def __init__(self, *args, **kwargs): super(HelpCommand, self).__init__(*args, **kwargs) @@ -125,56 +151,89 @@ class CompleteCommand (CommandMethod): pass +# Define some additional commands + +class LocalHelpCommand (Command): + """Called with an argument, prints that command's documentation. + + With no argument, lists all available help topics as well as any + undocumented commands. + """ + def __init__(self): + super(LocalHelpCommand, self).__init__(name='help', help=self.__doc__) + # We set .arguments now (vs. using th arguments option to __init__), + # to overwrite the default help argument. We don't override + # :meth:`cmd.Cmd.do_help`, so `help --help` is not a valid command. + self.arguments = [ + Argument(name='command', type='string', optional=True, + help='The name of the command you want help with.') + ] + + def _run(self, hooke, inqueue, outqueue, params): + raise NotImplementedError # cmd.Cmd already implements .do_help() + +class LocalExitCommand (Command): + """Exit Hooke cleanly. + """ + def __init__(self): + super(LocalExitCommand, self).__init__( + name='exit', aliases=['quit', 'EOF'], help=self.__doc__, + arguments = [ + Argument(name='force', type='bool', default=False, + callback=StoreValue(True), help=""" +Exit without prompting the user. Use if you save often or don't make +typing mistakes ;). +""".strip()), + ]) + + def _run(self, hooke, inqueue, outqueue, params): + """The guts of the `do_exit/_quit/_EOF` commands. + + A `True` return stops :meth:`.cmdloop` execution. + """ + _exit = True + if params['force'] == False: + # TODO: get results of hooke.playlists.current().is_saved() + is_saved = True + msg = 'Exit?' + default = True + if is_saved == False: + msg = 'You did not save your playlist. ' + msg + default = False + outqueue.put(BooleanRequest(msg, default)) + result = inqueue.get() + assert isinstance(result, BooleanResponse) + _exit = result.value + if _exit == True: + raise Exit() + + # Now onto the main attraction. class HookeCmd (cmd.Cmd): def __init__(self, commands, inqueue, outqueue): cmd.Cmd.__init__(self) self.commands = commands + self.local_commands = [LocalExitCommand(), LocalHelpCommand()] self.prompt = 'hooke> ' self._add_command_methods() self.inqueue = inqueue self.outqueue = outqueue def _name_fn(self, name): - return name.lower().replace(' ', '_') + return name.replace(' ', '_') def _add_command_methods(self): - for command in self.commands: + for command in self.commands + self.local_commands: for name in [command.name] + command.aliases: name = self._name_fn(name) - setattr(self.__class__, 'do_%s' % name, - DoCommand(self, command, self._name_fn)) setattr(self.__class__, 'help_%s' % name, HelpCommand(self, command, self._name_fn)) - setattr(self.__class__, 'complete_%s' % name, - CompleteCommand(self, command, self._name_fn)) - - exit_command = Command( - name='exit', aliases=['quit', 'EOF'], - help='Exit Hooke cleanly.') - exit_command.arguments = [] # remove help argument - for name in [exit_command.name] + exit_command.aliases: - setattr(self.__class__, 'do_%s' % name, - lambda self, args : True) - # the True return stops .cmdloop execution - setattr(self.__class__, 'help_%s' % name, - HelpCommand(self, exit_command, self._name_fn)) - - help_command = Command( - name='help', - help=""" -Called with an argument, prints that command's documentation. - -With no argument, lists all available help topics as well as any -undocumented commands. -""".strip()) - help_command.arguments = [ # overwrite help argument - Argument(name='command', type='string', optional=True, - help='The name of the command you want help with.') - ] - setattr(self.__class__, 'help_help', - HelpCommand(self, help_command, self._name_fn)) + if name != 'help': + setattr(self.__class__, 'do_%s' % name, + DoCommand(self, command, self._name_fn)) + setattr(self.__class__, 'complete_%s' % name, + CompleteCommand(self, command, self._name_fn)) class CommandLine (UserInterface): -- 2.26.2