X-Git-Url: http://git.tremily.us/?p=hooke.git;a=blobdiff_plain;f=hooke%2Fui%2Fcommandline.py;h=d6801f93d35227287b9fc0b68a10860d4d55b695;hp=132d65785cefa3850a4a9e1cd6e5984d6e427b8e;hb=HEAD;hpb=ca8b1f45e3e1f6a6183ceb0165ee61b71f6fe66c diff --git a/hooke/ui/commandline.py b/hooke/ui/commandline.py index 132d657..d6801f9 100644 --- a/hooke/ui/commandline.py +++ b/hooke/ui/commandline.py @@ -1,20 +1,19 @@ -# Copyright (C) 2010 W. Trevor King +# Copyright (C) 2010-2012 W. Trevor King # # 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 -# . +# You should have received a copy of the GNU Lesser General Public License +# along with Hooke. If not, see . """Defines :class:`CommandLine` for driving Hooke from the command line. @@ -22,18 +21,34 @@ line. 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. """ @@ -53,8 +68,42 @@ class CommandLineParser (optparse.OptionParser): 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) @@ -67,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`. @@ -99,25 +152,33 @@ class DoCommand (CommandMethod): 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) @@ -130,12 +191,15 @@ class DoCommand (CommandMethod): 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 @@ -182,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 @@ -266,19 +340,22 @@ class DoCommand (CommandMethod): 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: @@ -297,63 +374,6 @@ 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, - 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. @@ -362,7 +382,6 @@ class HookeCmd (cmd.Cmd): 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 @@ -372,7 +391,9 @@ class HookeCmd (cmd.Cmd): 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, @@ -399,11 +420,13 @@ class HookeCmd (cmd.Cmd): 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. @@ -412,7 +435,7 @@ class HookeCmd (cmd.Cmd): 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 @@ -420,6 +443,7 @@ class HookeCmd (cmd.Cmd): """ pass + class CommandLine (UserInterface): """Command line interface. Simple and powerful. """