Add a `Delete` button to the GUI NavBar, and cleanup deletion callbacks.
[hooke.git] / hooke / ui / commandline.py
index 5f04c366623a0db03689ab29284b19bef6a664a3..ecefd64cbcc566da0b0ae541a691a4286e52a970 100644 (file)
@@ -1,20 +1,19 @@
-# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2010-2012 W. Trevor King <wking@drexel.edu>
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or modify it
-# under the terms of the GNU Lesser General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
 #
-# Hooke is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
-# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
-# Public License for more details.
+# Hooke is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
 #
-# You should have received a copy of the GNU Lesser General Public
-# License along with Hooke.  If not, see
-# <http://www.gnu.org/licenses/>.
+# You should have received a copy of the GNU Lesser General Public License
+# along with Hooke.  If not, see <http://www.gnu.org/licenses/>.
 
 """Defines :class:`CommandLine` for driving Hooke from the command
 line.
@@ -24,16 +23,18 @@ import codecs
 import cmd
 import logging
 import optparse
+import pprint
 try:
     import readline # including readline makes cmd.Cmd.cmdloop() smarter
 except ImportError, e:
     import logging
-    logging.warn('Could not import readline, bash-like line editing disabled.')
+    logging.warn('could not import readline, bash-like line editing disabled.')
 import shlex
+import sys
 
 from ..command import CommandExit, Exit, Command, Argument, StoreValue
-from ..engine import CommandMessage
-from ..interaction import Request, ReloadUserInterfaceConfig
+from ..engine import CommandMessage, CloseEngine
+from ..interaction import EOFResponse, Request, ReloadUserInterfaceConfig
 from ..ui import UserInterface
 from ..util.convert import from_string
 from ..util.encoding import get_input_encoding, get_output_encoding
@@ -41,6 +42,13 @@ from ..util.encoding import get_input_encoding, get_output_encoding
 
 # Define a few helper classes.
 
+class EOF (EOFError):
+    """Raise upon reaching the end of the input file.
+
+    After this point, no more user interaction is possible.
+    """
+    pass
+
 class Default (object):
     """Marker for options not given on the command line.
     """
@@ -63,15 +71,25 @@ class CommandLineParser (optparse.OptionParser):
                 type = a.type
                 if type == 'bool':
                     if a.default == True:
-                        self.add_option(
-                            '--disable-%s' % name, dest=name, default=Default,
-                            action='store_false')
+                        try:
+                            self.add_option(
+                                '--disable-%s' % name, dest=name,
+                                default=Default, action='store_false',
+                                help=self._argument_help(a))
+                        except optparse.OptionConflictError, e:
+                            logging.warn('error in %s: %s' % (command, e))
+                            raise
                         self.command_opts.append(a)
                         continue
                     elif a.default == False:
-                        self.add_option(
-                            '--enable-%s' % name, dest=name, default=Default,
-                            action='store_true')
+                        try:
+                            self.add_option(
+                                '--enable-%s' % name, dest=name,
+                                default=Default, action='store_true',
+                                help=self._argument_help(a))
+                        except optparse.OptionConflictError, e:
+                            logging.warn('error in %s: %s' % (command, e))
+                            raise
                         self.command_opts.append(a)
                         continue
                     else:
@@ -79,8 +97,13 @@ class CommandLineParser (optparse.OptionParser):
                 elif type not in ['string', 'int', 'long', 'choice', 'float',
                                   'complex']:
                     type = 'string'
-                self.add_option(
-                    '--%s' % name, dest=name, type=type, default=Default)
+                try:
+                    self.add_option(
+                        '--%s' % name, dest=name, type=type, default=Default,
+                        help=self._argument_help(a))
+                except optparse.OptionConflictError, e:
+                    logging.warn('error in %s: %s' % (command, e))
+                    raise
                 self.command_opts.append(a)
             else:
                 self.command_args.append(a)
@@ -93,6 +116,10 @@ class CommandLineParser (optparse.OptionParser):
             self.command_args.remove(infinite_counter)
             self.command_args.append(infinite_counter)
 
+    def _argument_help(self, argument):
+        return '%s (%s)' % (argument._help, argument.default)
+        # default in the case of callbacks, config-backed values, etc.?
+
     def exit(self, status=0, msg=None):
         """Override :meth:`optparse.OptionParser.exit` which calls
         :func:`sys.exit`.
@@ -120,32 +147,38 @@ class DoCommand (CommandMethod):
     def __init__(self, *args, **kwargs):
         super(DoCommand, self).__init__(*args, **kwargs)
         self.parser = CommandLineParser(self.command, self.name_fn)
-        self.log = logging.getLogger('hooke')
 
     def __call__(self, args):
         try:
             args = self._parse_args(args)
         except optparse.OptParseError, e:
-            self.cmd.stdout.write(str(e).lstrip()+'\n')
+            self.cmd.stdout.write(unicode(e).lstrip()+'\n')
             self.cmd.stdout.write('Failure\n')
             return
-        self.log.debug('executing %s with %s' % (self.command.name, args))
-        self.cmd.inqueue.put(CommandMessage(self.command.name, args))
+        cm = CommandMessage(self.command.name, args)
+        self.cmd.ui._submit_command(cm, self.cmd.inqueue)
         while True:
             msg = self.cmd.outqueue.get()
             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')
+                self.cmd.stdout.write(unicode(msg).rstrip()+'\n')
                 break
             elif isinstance(msg, ReloadUserInterfaceConfig):
                 self.cmd.ui.reload_config(msg.config)
                 continue
             elif isinstance(msg, Request):
-                self._handle_request(msg)
+                try:
+                    self._handle_request(msg)
+                except EOF:
+                    return True
                 continue
-            self.cmd.stdout.write(str(msg).rstrip()+'\n')
+            if isinstance(msg, dict):
+                text = pprint.pformat(msg)
+            else:
+                text = unicode(msg)
+            self.cmd.stdout.write(text.rstrip()+'\n')
 
     def _parse_args(self, args):
         options,args = self.parser.parse_args(args)
@@ -213,9 +246,19 @@ class DoCommand (CommandMethod):
         while True:
             if error != None:
                 self.cmd.stdout.write(''.join([
-                        error.__class__.__name__, ': ', str(error), '\n']))
+                        error.__class__.__name__, ': ', unicode(error), '\n']))
             self.cmd.stdout.write(prompt_string)
-            value = parser(msg, self.cmd.stdin.readline())
+            stdin = sys.stdin
+            try:
+                sys.stdin = self.cmd.stdin
+                raw_response = raw_input()
+            except EOFError, e:
+                self.cmd.inqueue.put(EOFResponse())
+                self.cmd.inqueue.put(CloseEngine())
+                raise EOF
+            finally:
+                sys.stdin = stdin
+            value = parser(msg, raw_response)
             try:
                 response = msg.response(value)
                 break
@@ -304,14 +347,15 @@ class HelpCommand (CommandMethod):
         self.parser = CommandLineParser(self.command, self.name_fn)
 
     def __call__(self):
-        blocks = [self.command.help(name_fn=self.name_fn),
+        blocks = [self.parser.format_help(),
+                  self._command_message(),
                   '----',
                   'Usage: ' + self._usage_string(),
                   '']
         self.cmd.stdout.write('\n'.join(blocks))
 
-    def _message(self):
-        return self.command.help(name_fn=self.name_fn)
+    def _command_message(self):
+        return self.command._help
 
     def _usage_string(self):
         if len(self.parser.command_opts) == 0: