#
# 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
import codecs
import cmd
+import logging
import optparse
-import readline # including readline makes cmd.Cmd.cmdloop() smarter
+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.')
import shlex
+import sys
from ..command import CommandExit, Exit, Command, Argument, StoreValue
-from ..interaction import Request, BooleanRequest, ReloadUserInterfaceConfig
-from ..ui import UserInterface, CommandMessage
+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
# 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.
"""
continue # 'help' is a default OptionParser option
if a.optional == True:
name = name_fn(a.name)
- self.add_option(
- '--%s' % name, dest=name, default=Default)
+ type = a.type
+ if type == 'bool':
+ if a.default == True:
+ 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:
+ 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:
+ type = 'string'
+ elif type not in ['string', 'int', 'long', 'choice', 'float',
+ 'complex']:
+ type = 'string'
+ 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)
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`.
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.cmd.inqueue.put(CommandMessage(self.command, 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)
arg_index = 0
for argument in self.parser.command_args:
if argument.count == 1:
- params[argument.name] = args[arg_index]
+ params[argument.name] = from_string(args[arg_index],
+ argument.type)
elif argument.count > 1:
- params[argument.name] = \
- args[arg_index:arg_index+argument.count]
+ params[argument.name] = [
+ from_string(a, argument.type)
+ for a in args[arg_index:arg_index+argument.count]]
else: # argument.count == -1:
- params[argument.name] = args[arg_index:]
+ params[argument.name] = [
+ from_string(a, argument.type) for a in args[arg_index:]]
arg_index += argument.count
return params
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
return msg.msg + d
def _string_request_parser(self, msg, response):
+ response = response.strip()
+ if response == '':
+ return msg.default
return response.strip()
def _float_request_prompt(self, msg):
return self._string_request_prompt(msg)
def _float_request_parser(self, msg, resposne):
+ if response.strip() == '':
+ return msg.default
return float(response)
def _selection_request_prompt(self, msg):
prompt = '? '
else:
prompt = '? [%d] ' % msg.default
- return '\n'.join([msg,options,prompt])
+ return '\n'.join([msg.msg,options,prompt])
def _selection_request_parser(self, msg, response):
+ if response.strip() == '':
+ return msg.default
+ return int(response)
+
+ def _point_request_prompt(self, msg):
+ block = msg.curve.data[msg.block]
+ block_info = ('(curve: %s, block: %s, %d points)'
+ % (msg.curve.name,
+ block.info['name'],
+ block.shape[0]))
+
+ if msg.default == None:
+ prompt = '? '
+ else:
+ prompt = '? [%d] ' % msg.default
+ return ' '.join([msg.msg,block_info,prompt])
+
+ def _point_request_parser(self, msg, response):
+ if response.strip() == '':
+ return msg.default
return int(response)
class HelpCommand (CommandMethod):
+ """Supersedes :class:`hooke.plugin.engine.HelpCommand`.
+ """
def __init__(self, *args, **kwargs):
super(HelpCommand, self).__init__(*args, **kwargs)
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:
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,
- 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:
- not_saved = [p.name for p in hooke.playlists
- if p.is_saved() == False]
- msg = 'Exit?'
- default = True
- if len(not_saved) > 0:
- msg = 'Unsaved playlists (%s). %s' \
- % (', '.join([str(p) for p in not_saved]), msg)
- default = False
- outqueue.put(BooleanRequest(msg, default))
- result = inqueue.get()
- assert result.type == 'boolean'
- _exit = result.value
- if _exit == True:
- raise Exit()
-
# Now onto the main attraction.
cmd.Cmd.__init__(self)
self.ui = ui
self.commands = commands
- self.local_commands = [LocalExitCommand(), LocalHelpCommand()]
self.prompt = 'hooke> '
self._add_command_methods()
self.inqueue = inqueue
return name.replace(' ', '_')
def _add_command_methods(self):
- for command in self.commands + self.local_commands:
+ for command in self.commands:
+ if command.name == 'exit':
+ command.aliases.extend(['quit', 'EOF'])
for name in [command.name] + command.aliases:
name = self._name_fn(name)
setattr(self.__class__, 'help_%s' % name,
argv = shlex.split(line, comments=True, posix=True)
if len(argv) == 0:
return None, None, '' # return an empty line
- elif argv[0] == '?':
- argv[0] = 'help'
- elif argv[0] == '!':
- argv[0] = 'system'
- return argv[0], argv[1:], line
+ cmd = argv[0]
+ args = argv[1:]
+ if cmd == '?':
+ cmd = 'help'
+ elif cmd == '!':
+ cmd = 'system'
+ return cmd, args, line
def do_help(self, arg):
"""Wrap Cmd.do_help to handle our .parseline argument list.
return cmd.Cmd.do_help(self, '')
return cmd.Cmd.do_help(self, arg[0])
- def empytline(self):
+ def emptyline(self):
"""Override Cmd.emptyline to not do anything.
Repeating the last non-empty command seems unwise. Explicit
"""
pass
+
class CommandLine (UserInterface):
"""Command line interface. Simple and powerful.
"""