1 """Defines :class:`CommandLine` for driving Hooke from the command
7 import readline # including readline makes cmd.Cmd.cmdloop() smarter
10 from ..command import CommandExit, Exit, BooleanRequest, BooleanResponse, \
11 Command, Argument, StoreValue
12 from ..ui import UserInterface, CommandMessage
15 # Define a few helper classes.
17 class Default (object):
18 """Marker for options not given on the command line.
22 class CommandLineParser (optparse.OptionParser):
23 """Implement a command line syntax for a
24 :class:`hooke.command.Command`.
26 def __init__(self, command, name_fn):
27 optparse.OptionParser.__init__(self, prog=name_fn(command.name))
28 self.command = command
29 self.command_opts = []
30 self.command_args = []
31 for a in command.arguments:
33 continue # 'help' is a default OptionParser option
34 if a.optional == True:
35 name = name_fn(a.name)
37 '--%s' % name, dest=name, default=Default)
38 self.command_opts.append(a)
40 self.command_args.append(a)
42 def exit(self, status=0, msg=None):
43 """Override :meth:`optparse.OptionParser.exit` which calls
47 raise optparse.OptParseError(msg)
48 raise optparse.OptParseError('OptParse EXIT')
50 class CommandMethod (object):
51 """Base class for method replacer.
53 The .__call__ methods of `CommandMethod` subclasses functions will
54 provide the `do_*`, `help_*`, and `complete_*` methods of
57 def __init__(self, cmd, command, name_fn):
59 self.command = command
60 self.name_fn = name_fn
62 def __call__(self, *args, **kwargs):
63 raise NotImplementedError
65 class DoCommand (CommandMethod):
66 def __init__(self, *args, **kwargs):
67 super(DoCommand, self).__init__(*args, **kwargs)
68 self.parser = CommandLineParser(self.command, self.name_fn)
70 def __call__(self, args):
72 args = self._parse_args(args)
73 except optparse.OptParseError, e:
74 self.cmd.stdout.write(str(e).lstrip()+'\n')
75 self.cmd.stdout.write('Failure\n')
77 self.cmd.inqueue.put(CommandMessage(self.command, args))
79 msg = self.cmd.outqueue.get()
80 if isinstance(msg, Exit):
82 elif isinstance(msg, CommandExit):
83 self.cmd.stdout.write(msg.__class__.__name__+'\n')
84 self.cmd.stdout.write(str(msg).rstrip()+'\n')
86 elif isinstance(msg, BooleanRequest):
87 self._boolean_request(msg)
89 self.cmd.stdout.write(str(msg).rstrip()+'\n')
91 def _parse_args(self, args):
92 argv = shlex.split(args, comments=True, posix=True)
93 options,args = self.parser.parse_args(argv)
94 if len(args) != len(self.parser.command_args):
95 raise optparse.OptParseError('%d arguments given, but %s takes %d'
97 self.name_fn(self.command.name),
98 len(self.parser.command_args)))
100 for argument in self.parser.command_opts:
101 value = getattr(options, self.name_fn(argument.name))
103 params[argument.name] = value
104 for i,argument in enumerate(self.parser.command_args):
105 params[argument.name] = args[i]
108 def _boolean_request(self, msg):
109 if msg.default == True:
113 self.cmd.stdout.write(msg.msg+yn)
114 response = self.cmd.stdin.readline().strip().lower()
115 if response.startswith('y'):
116 self.cmd.inqueue.put(BooleanResponse(True))
117 elif response.startswith('n'):
118 self.cmd.inqueue.put(BooleanResponse(False))
120 self.cmd.inqueue.put(BooleanResponse(msg.default))
122 class HelpCommand (CommandMethod):
123 def __init__(self, *args, **kwargs):
124 super(HelpCommand, self).__init__(*args, **kwargs)
125 self.parser = CommandLineParser(self.command, self.name_fn)
128 blocks = [self.command.help(name_fn=self.name_fn),
130 'Usage: ' + self._usage_string(),
132 self.cmd.stdout.write('\n'.join(blocks))
135 return self.command.help(name_fn=self.name_fn)
137 def _usage_string(self):
138 if len(self.parser.command_opts) == 0:
141 options_string = '[options]'
142 arg_string = ' '.join(
143 [self.name_fn(arg.name) for arg in self.parser.command_args])
144 return ' '.join([x for x in [self.parser.prog,
149 class CompleteCommand (CommandMethod):
150 def __call__(self, text, line, begidx, endidx):
154 # Define some additional commands
156 class LocalHelpCommand (Command):
157 """Called with an argument, prints that command's documentation.
159 With no argument, lists all available help topics as well as any
160 undocumented commands.
163 super(LocalHelpCommand, self).__init__(name='help', help=self.__doc__)
164 # We set .arguments now (vs. using th arguments option to __init__),
165 # to overwrite the default help argument. We don't override
166 # :meth:`cmd.Cmd.do_help`, so `help --help` is not a valid command.
168 Argument(name='command', type='string', optional=True,
169 help='The name of the command you want help with.')
172 def _run(self, hooke, inqueue, outqueue, params):
173 raise NotImplementedError # cmd.Cmd already implements .do_help()
175 class LocalExitCommand (Command):
176 """Exit Hooke cleanly.
179 super(LocalExitCommand, self).__init__(
180 name='exit', aliases=['quit', 'EOF'], help=self.__doc__,
182 Argument(name='force', type='bool', default=False,
183 callback=StoreValue(True), help="""
184 Exit without prompting the user. Use if you save often or don't make
189 def _run(self, hooke, inqueue, outqueue, params):
190 """The guts of the `do_exit/_quit/_EOF` commands.
192 A `True` return stops :meth:`.cmdloop` execution.
195 if params['force'] == False:
196 # TODO: get results of hooke.playlists.current().is_saved()
200 if is_saved == False:
201 msg = 'You did not save your playlist. ' + msg
203 outqueue.put(BooleanRequest(msg, default))
204 result = inqueue.get()
205 assert isinstance(result, BooleanResponse)
211 # Now onto the main attraction.
213 class HookeCmd (cmd.Cmd):
214 def __init__(self, commands, inqueue, outqueue):
215 cmd.Cmd.__init__(self)
216 self.commands = commands
217 self.local_commands = [LocalExitCommand(), LocalHelpCommand()]
218 self.prompt = 'hooke> '
219 self._add_command_methods()
220 self.inqueue = inqueue
221 self.outqueue = outqueue
223 def _name_fn(self, name):
224 return name.replace(' ', '_')
226 def _add_command_methods(self):
227 for command in self.commands + self.local_commands:
228 for name in [command.name] + command.aliases:
229 name = self._name_fn(name)
230 setattr(self.__class__, 'help_%s' % name,
231 HelpCommand(self, command, self._name_fn))
233 setattr(self.__class__, 'do_%s' % name,
234 DoCommand(self, command, self._name_fn))
235 setattr(self.__class__, 'complete_%s' % name,
236 CompleteCommand(self, command, self._name_fn))
239 class CommandLine (UserInterface):
240 """Command line interface. Simple and powerful.
243 super(CommandLine, self).__init__(name='command line')
245 def run(self, commands, ui_to_command_queue, command_to_ui_queue):
246 cmd = HookeCmd(commands,
247 inqueue=ui_to_command_queue,
248 outqueue=command_to_ui_queue)
249 cmd.cmdloop(self._splash_text())