Pass Hooke instance to CommandEngine.run(), not UserInterface.run().
[hooke.git] / hooke / ui / commandline.py
1 """Defines :class:`CommandLine` for driving Hooke from the command
2 line.
3 """
4
5 import cmd
6 import optparse
7 import readline # including readline makes cmd.Cmd.cmdloop() smarter
8 import shlex
9
10 from ..command import CommandExit, Command, Argument
11 from ..ui import UserInterface, CommandMessage
12
13
14 # Define a few helper classes.  The .__call__ methods of these
15 # functions will provide the do_*, help_*, and complete_* methods of
16 # HookeCmd.
17
18 class CommandMethod (object):
19     def __init__(self, cmd, command):
20         self.cmd = cmd
21         self.command = command
22
23     def __call__(self, *args, **kwargs):
24         raise NotImplementedError
25
26 def command_parser(command):
27     p = optparse.OptionParser()
28     opts = []
29     args = []
30     for a in command.arguments:
31         if a.name == 'help':
32             continue # 'help' is a default OptionParser option
33         name = a.name.replace('_', '-')
34         if a.optional == True:
35             p.add_option('--%s' % name, dest=a.name, default=a.default)
36             opts.append((name, a))
37         else:
38             args.append((name, a))
39     return (p, opts, args)
40
41 class DoCommand (CommandMethod):
42     def __init__(self, *args, **kwargs):
43         super(DoCommand, self).__init__(*args, **kwargs)
44         self.parser,self.opts,self.args = command_parser(self.command)
45
46     def __call__(self, args):
47         args = self._parse_args(self.command, args)
48         self.cmd.inqueue.put(CommandMessage(self.command, args))
49         while True:
50             msg = self.cmd.outqueue.get()
51             if isinstance(msg, CommandExit):
52                 break
53             self.cmd.stdout.write(str(msg).rstrip()+'\n')
54
55     def _parse_args(self, command, args):
56         argv = shlex.split(args, comments=True, posix=True)
57         options,args = self.parser.parse_args(argv)
58         params = {}
59         for namearg in self.opts:
60             name,argument = namearg
61             params[name] = getattr(options, name)
62         for namearg,value in zip(self.args, args):
63             name,argument = namearg
64             params[name] = value
65         return params
66
67 class HelpCommand (CommandMethod):
68     def __init__(self, *args, **kwargs):
69         super(HelpCommand, self).__init__(*args, **kwargs)
70         self.parser,self.opts,self.args = command_parser(self.command)
71
72     def __call__(self):
73         blocks = [self.command.help(),
74                   '------',
75                   'Usage: ' + self._usage_string(),
76                   '']
77         self.cmd.stdout.write('\n'.join(blocks))
78
79     def _message(self):
80         return self.command.help()
81
82     def _usage_string(self):
83         if len(self.args) == len(self.command.arguments):
84             options_string = ''
85         else:
86             options_string = '[options]'
87         arg_string = ' '.join([name for name,arg in self.args])
88         return ' '.join([x for x in [self.command.name,
89                                      options_string,
90                                      arg_string]
91                          if x != ''])
92
93 class CompleteCommand (CommandMethod):
94     def __call__(self, text, line, begidx, endidx):
95         pass
96
97
98 # Now onto the main attraction.
99
100 class HookeCmd (cmd.Cmd):
101     def __init__(self, commands, inqueue, outqueue):
102         cmd.Cmd.__init__(self)
103         self.commands = commands
104         self.prompt = 'hooke> '
105         self._add_command_methods()
106         self.inqueue = inqueue
107         self.outqueue = outqueue
108
109     def _safe_name(self, name):
110         return name.lower().replace(' ', '_')
111
112     def _add_command_methods(self):
113         for command in self.commands:
114             command.name = self._safe_name(command.name)
115             command.aliases = [self._safe_name(n) for n in command.aliases]
116             for name in [command.name] + command.aliases:
117                 name = self._safe_name(name)
118                 setattr(self.__class__, 'do_%s' % name,
119                         DoCommand(self, command))
120                 setattr(self.__class__, 'help_%s' % name,
121                         HelpCommand(self, command))
122                 setattr(self.__class__, 'complete_%s' % name,
123                         CompleteCommand(self, command))
124
125         exit_command = Command(
126             name='exit', aliases=['quit', 'EOF'],
127             help='Exit Hooke cleanly.')
128         exit_command.arguments = [] # remove help argument
129         for name in [exit_command.name] + exit_command.aliases:
130             setattr(self.__class__, 'do_%s' % name,
131                     lambda self, args : True)
132             # the True return stops .cmdloop execution
133             setattr(self.__class__, 'help_%s' % name,
134                     HelpCommand(self, exit_command))
135
136         help_command = Command(
137             name='help',
138             help="""
139 Called with an argument, prints that command's documentation.
140
141 With no argument, lists all available help topics as well as any
142 undocumented commands.
143 """.strip())
144         help_command.arguments = [ # overwrite help argument
145             Argument(name='command', type='string', optional=True,
146                      help='The name of the command you want help with.')
147             ]
148         setattr(self.__class__, 'help_help',
149                 HelpCommand(self, help_command))
150
151
152 class CommandLine (UserInterface):
153     """Command line interface.  Simple and powerful.
154     """
155     def __init__(self):
156         super(CommandLine, self).__init__(name='command line')
157
158     def run(self, hooke, ui_to_command_queue, command_to_ui_queue):
159         cmd = HookeCmd(hooke,
160                        inqueue=ui_to_command_queue,
161                        outqueue=command_to_ui_queue)
162         cmd.cmdloop(self._splash_text())