Added interaction to hooke.ui.commandline.LocalCommandExit.
authorW. Trevor King <wking@drexel.edu>
Tue, 11 May 2010 10:52:31 +0000 (06:52 -0400)
committerW. Trevor King <wking@drexel.edu>
Tue, 11 May 2010 10:52:31 +0000 (06:52 -0400)
Also broke out commandline's exit and help commands.  They were
getting complicated enough to deserve their own classes.

hooke/command.py
hooke/hooke_cli.py
hooke/ui/commandline.py

index b6d6b3522bdfcb87c038a3821e3d93a575370255..73f01fe3d443a20b0fb9483ed462896e4a2f3884 100644 (file)
@@ -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:', '']
index bbb6cc69e31176ac0aa4bc1e37fcac214e72eda3..03385226c4be3696988be671fad0e8a90e542c29 100644 (file)
@@ -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__':
index 027a7879f7edf009be0ba6f3b78d8101ae130949..5198f8fcbaf992662d0828263f7c509054bb385e 100644 (file)
@@ -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):