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, Command, Argument
11 from ..ui import UserInterface, CommandMessage
14 # Define a few helper classes.
16 class CommandLineParser (optparse.OptionParser):
17 """Implement a command line syntax for a
18 :class:`hooke.command.Command`.
20 def __init__(self, command):
21 optparse.OptionParser.__init__(self, prog=command._cl_name)
22 self.command = command
23 self.command_opts = []
24 self.command_args = []
25 for a in command.arguments:
27 continue # 'help' is a default OptionParser option
28 if a.optional == True:
30 '--%s' % a._cl_name, dest=a._cl_name, default=a.default)
31 self.command_opts.append(a)
33 self.command_args.append(a)
35 def exit(self, status=0, msg=None):
36 """Override :meth:`optparse.OptionParser.exit` which calls
40 raise optparse.OptParseError(msg)
41 raise optparse.OptParseError('OptParse EXIT')
43 class CommandMethod (object):
44 """Base class for method replacer.
46 The .__call__ methods of `CommandMethod` subclasses functions will
47 provide the `do_*`, `help_*`, and `complete_*` methods of
50 def __init__(self, cmd, command):
52 self.command = command
54 def __call__(self, *args, **kwargs):
55 raise NotImplementedError
57 class DoCommand (CommandMethod):
58 def __init__(self, *args, **kwargs):
59 super(DoCommand, self).__init__(*args, **kwargs)
60 self.parser = CommandLineParser(self.command)
62 def __call__(self, args):
64 args = self._parse_args(self.command, args)
65 except optparse.OptParseError, e:
66 self.cmd.stdout.write(str(e).lstrip()+'\n')
67 self.cmd.stdout.write('Failure\n')
69 self.cmd.inqueue.put(CommandMessage(self.command, args))
71 msg = self.cmd.outqueue.get()
72 if isinstance(msg, CommandExit):
73 self.cmd.stdout.write(msg.__class__.__name__+'\n')
74 self.cmd.stdout.write(str(msg).rstrip()+'\n')
76 self.cmd.stdout.write(str(msg).rstrip()+'\n')
78 def _parse_args(self, command, args):
79 argv = shlex.split(args, comments=True, posix=True)
80 options,args = self.parser.parse_args(argv)
81 if len(args) != len(self.parser.command_args):
82 raise optparse.OptParseError('%d arguments given, but %s takes %d'
83 % (len(args), command._cl_name,
84 len(self.parser.command_args)))
86 for argument in self.parser.command_opts:
87 params[argument.name] = getattr(options, argument._cl_name)
88 for i,argument in enumerate(self.parser.command_args):
89 params[argument.name] = args[i]
92 class HelpCommand (CommandMethod):
93 def __init__(self, *args, **kwargs):
94 super(HelpCommand, self).__init__(*args, **kwargs)
95 self.parser = CommandLineParser(self.command)
98 blocks = [self.command.help(),
100 'Usage: ' + self._usage_string(),
102 self.cmd.stdout.write('\n'.join(blocks))
105 return self.command.help()
107 def _usage_string(self):
108 if len(self.parser.command_opts) == 0:
111 options_string = '[options]'
112 arg_string = ' '.join(
113 [arg._cl_name for arg in self.parser.command_args])
114 return ' '.join([x for x in [self.command.name,
119 class CompleteCommand (CommandMethod):
120 def __call__(self, text, line, begidx, endidx):
124 # Now onto the main attraction.
126 class HookeCmd (cmd.Cmd):
127 def __init__(self, commands, inqueue, outqueue):
128 cmd.Cmd.__init__(self)
129 self.commands = commands
130 self.prompt = 'hooke> '
131 self._add_command_methods()
132 self.inqueue = inqueue
133 self.outqueue = outqueue
135 def _safe_name(self, name):
136 return name.lower().replace(' ', '_')
138 def _add_command_methods(self):
139 for command in self.commands:
140 self._setup_command(command)
141 for name in [command._cl_name] + command._cl_aliases:
142 setattr(self.__class__, 'do_%s' % name,
143 DoCommand(self, command))
144 setattr(self.__class__, 'help_%s' % name,
145 HelpCommand(self, command))
146 setattr(self.__class__, 'complete_%s' % name,
147 CompleteCommand(self, command))
149 exit_command = Command(
150 name='exit', aliases=['quit', 'EOF'],
151 help='Exit Hooke cleanly.')
152 self._setup_command(exit_command)
153 exit_command.arguments = [] # remove help argument
154 for name in [exit_command.name] + exit_command.aliases:
155 setattr(self.__class__, 'do_%s' % name,
156 lambda self, args : True)
157 # the True return stops .cmdloop execution
158 setattr(self.__class__, 'help_%s' % name,
159 HelpCommand(self, exit_command))
161 help_command = Command(
164 Called with an argument, prints that command's documentation.
166 With no argument, lists all available help topics as well as any
167 undocumented commands.
169 help_command.arguments = [ # overwrite help argument
170 Argument(name='command', type='string', optional=True,
171 help='The name of the command you want help with.')
173 self._setup_command(help_command)
174 setattr(self.__class__, 'help_help',
175 HelpCommand(self, help_command))
177 def _setup_command(self, command):
178 """Attach some UI specific data to `command`. After this
179 point, the :attr:`commands` are read only.
181 command._cl_name = self._safe_name(command.name)
182 command._cl_aliases = [self._safe_name(n) for n in command.aliases]
183 for argument in command.arguments:
184 argument._cl_name = self._safe_name(argument.name)
185 argument._cl_aliases = [self._safe_name(n)
186 for n in argument.aliases]
189 class CommandLine (UserInterface):
190 """Command line interface. Simple and powerful.
193 super(CommandLine, self).__init__(name='command line')
195 def run(self, commands, ui_to_command_queue, command_to_ui_queue):
196 cmd = HookeCmd(commands,
197 inqueue=ui_to_command_queue,
198 outqueue=command_to_ui_queue)
199 cmd.cmdloop(self._splash_text())