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