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