| 1 | # Copyright (c) 2003-2008 LOGILAB S.A. (Paris, FRANCE). |
|---|
| 2 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|---|
| 3 | # |
|---|
| 4 | # This program is free software; you can redistribute it and/or modify it under |
|---|
| 5 | # the terms of the GNU General Public License as published by the Free Software |
|---|
| 6 | # Foundation; either version 2 of the License, or (at your option) any later |
|---|
| 7 | # version. |
|---|
| 8 | # |
|---|
| 9 | # This program is distributed in the hope that it will be useful, but WITHOUT |
|---|
| 10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|---|
| 11 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
|---|
| 12 | # |
|---|
| 13 | # You should have received a copy of the GNU General Public License along with |
|---|
| 14 | # this program; if not, write to the Free Software Foundation, Inc., |
|---|
| 15 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|---|
| 16 | """Command line interface helper classes. |
|---|
| 17 | |
|---|
| 18 | It provides some default commands, a help system, a default readline |
|---|
| 19 | configuration with completion and persistent history |
|---|
| 20 | |
|---|
| 21 | Exemple usage: |
|---|
| 22 | |
|---|
| 23 | class BookShell(CLIHelper): |
|---|
| 24 | |
|---|
| 25 | def __init__(self): |
|---|
| 26 | # quit and help are builtins |
|---|
| 27 | # CMD_MAP keys are commands, values are topics |
|---|
| 28 | self.CMD_MAP['pionce'] = _("Sommeil") |
|---|
| 29 | self.CMD_MAP['ronfle'] = _("Sommeil") |
|---|
| 30 | CLIHelper.__init__(self) |
|---|
| 31 | |
|---|
| 32 | help_do_pionce = ("pionce", "pionce duree", _("met ton corps en veille")) |
|---|
| 33 | def do_pionce(self): |
|---|
| 34 | print 'nap is good' |
|---|
| 35 | |
|---|
| 36 | help_do_ronfle = ("ronfle", "ronfle volume", _("met les autres en veille")) |
|---|
| 37 | def do_ronfle(self): |
|---|
| 38 | print 'fuuuuuuuuuuuu rhhhhhrhrhrrh' |
|---|
| 39 | |
|---|
| 40 | cl = BookShell() |
|---|
| 41 | |
|---|
| 42 | :author: Logilab |
|---|
| 43 | :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE) |
|---|
| 44 | :contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org |
|---|
| 45 | """ |
|---|
| 46 | |
|---|
| 47 | |
|---|
| 48 | import __builtin__ |
|---|
| 49 | if not hasattr(__builtin__, '_'): |
|---|
| 50 | __builtin__._ = str |
|---|
| 51 | |
|---|
| 52 | |
|---|
| 53 | def init_readline(complete_method, histfile=None): |
|---|
| 54 | """init the readline library if available""" |
|---|
| 55 | try: |
|---|
| 56 | import readline |
|---|
| 57 | readline.parse_and_bind("tab: complete") |
|---|
| 58 | readline.set_completer(complete_method) |
|---|
| 59 | string = readline.get_completer_delims().replace(':', '') |
|---|
| 60 | readline.set_completer_delims(string) |
|---|
| 61 | if histfile is not None: |
|---|
| 62 | try: |
|---|
| 63 | readline.read_history_file(histfile) |
|---|
| 64 | except IOError: |
|---|
| 65 | pass |
|---|
| 66 | import atexit |
|---|
| 67 | atexit.register(readline.write_history_file, histfile) |
|---|
| 68 | except: |
|---|
| 69 | print 'readline si not available :-(' |
|---|
| 70 | |
|---|
| 71 | |
|---|
| 72 | class Completer : |
|---|
| 73 | """readline completer""" |
|---|
| 74 | |
|---|
| 75 | def __init__(self, commands): |
|---|
| 76 | self.list = commands |
|---|
| 77 | |
|---|
| 78 | def complete(self, text, state): |
|---|
| 79 | """hook called by readline when <tab> is pressed""" |
|---|
| 80 | n = len(text) |
|---|
| 81 | matches = [] |
|---|
| 82 | for cmd in self.list : |
|---|
| 83 | if cmd[:n] == text : |
|---|
| 84 | matches.append(cmd) |
|---|
| 85 | try: |
|---|
| 86 | return matches[state] |
|---|
| 87 | except IndexError: |
|---|
| 88 | return None |
|---|
| 89 | |
|---|
| 90 | |
|---|
| 91 | class CLIHelper: |
|---|
| 92 | """ an abstract command line interface client which recognize commands |
|---|
| 93 | and provide an help system |
|---|
| 94 | """ |
|---|
| 95 | |
|---|
| 96 | CMD_MAP = {'help' : _("Others"), |
|---|
| 97 | 'quit' : _("Others"), |
|---|
| 98 | } |
|---|
| 99 | CMD_PREFIX = '' |
|---|
| 100 | |
|---|
| 101 | def __init__(self, histfile=None) : |
|---|
| 102 | self._topics = {} |
|---|
| 103 | self.commands = None |
|---|
| 104 | self._completer = Completer(self._register_commands()) |
|---|
| 105 | init_readline(self._completer.complete, histfile) |
|---|
| 106 | |
|---|
| 107 | def run(self): |
|---|
| 108 | """loop on user input, exit on EOF""" |
|---|
| 109 | while 1: |
|---|
| 110 | try: |
|---|
| 111 | line = raw_input('>>> ') |
|---|
| 112 | except EOFError: |
|---|
| 113 | print |
|---|
| 114 | break |
|---|
| 115 | s_line = line.strip() |
|---|
| 116 | if not s_line: |
|---|
| 117 | continue |
|---|
| 118 | args = s_line.split() |
|---|
| 119 | if self.commands.has_key(args[0]): |
|---|
| 120 | try: |
|---|
| 121 | cmd = 'do_%s' % self.commands[args[0]] |
|---|
| 122 | getattr(self, cmd)(*args[1:]) |
|---|
| 123 | except EOFError: |
|---|
| 124 | break |
|---|
| 125 | except: |
|---|
| 126 | import traceback |
|---|
| 127 | traceback.print_exc() |
|---|
| 128 | else: |
|---|
| 129 | try: |
|---|
| 130 | self.handle_line(s_line) |
|---|
| 131 | except: |
|---|
| 132 | import traceback |
|---|
| 133 | traceback.print_exc() |
|---|
| 134 | |
|---|
| 135 | def handle_line(self, stripped_line): |
|---|
| 136 | """method to overload in the concrete class |
|---|
| 137 | |
|---|
| 138 | should handle lines wich are not command |
|---|
| 139 | """ |
|---|
| 140 | raise NotImplementedError() |
|---|
| 141 | |
|---|
| 142 | |
|---|
| 143 | # private methods ######################################################### |
|---|
| 144 | |
|---|
| 145 | def _register_commands(self): |
|---|
| 146 | """ register available commands method and return the list of |
|---|
| 147 | commands name |
|---|
| 148 | """ |
|---|
| 149 | self.commands = {} |
|---|
| 150 | self._command_help = {} |
|---|
| 151 | commands = [attr[3:] for attr in dir(self) if attr[:3] == 'do_'] |
|---|
| 152 | for command in commands: |
|---|
| 153 | topic = self.CMD_MAP[command] |
|---|
| 154 | help_method = getattr(self, 'help_do_%s' % command) |
|---|
| 155 | self._topics.setdefault(topic, []).append(help_method) |
|---|
| 156 | self.commands[self.CMD_PREFIX + command] = command |
|---|
| 157 | self._command_help[command] = help_method |
|---|
| 158 | return self.commands.keys() |
|---|
| 159 | |
|---|
| 160 | def _print_help(self, cmd, syntax, explanation): |
|---|
| 161 | print _('Command %s') % cmd |
|---|
| 162 | print _('Syntax: %s') % syntax |
|---|
| 163 | print '\t', explanation |
|---|
| 164 | print |
|---|
| 165 | |
|---|
| 166 | |
|---|
| 167 | # predefined commands ##################################################### |
|---|
| 168 | |
|---|
| 169 | def do_help(self, command=None) : |
|---|
| 170 | """base input of the help system""" |
|---|
| 171 | if self._command_help.has_key(command): |
|---|
| 172 | self._print_help(*self._command_help[command]) |
|---|
| 173 | elif command is None or not self._topics.has_key(command): |
|---|
| 174 | print _("Use help <topic> or help <command>.") |
|---|
| 175 | print _("Available topics are:") |
|---|
| 176 | topics = self._topics.keys() |
|---|
| 177 | topics.sort() |
|---|
| 178 | for topic in topics: |
|---|
| 179 | print '\t', topic |
|---|
| 180 | print |
|---|
| 181 | print _("Available commands are:") |
|---|
| 182 | commands = self.commands.keys() |
|---|
| 183 | commands.sort() |
|---|
| 184 | for command in commands: |
|---|
| 185 | print '\t', command[len(self.CMD_PREFIX):] |
|---|
| 186 | |
|---|
| 187 | else: |
|---|
| 188 | print _('Available commands about %s:') % command |
|---|
| 189 | print |
|---|
| 190 | for command_help_method in self._topics[command]: |
|---|
| 191 | try: |
|---|
| 192 | if callable(command_help_method): |
|---|
| 193 | self._print_help(*command_help_method()) |
|---|
| 194 | else: |
|---|
| 195 | self._print_help(*command_help_method) |
|---|
| 196 | except: |
|---|
| 197 | import traceback |
|---|
| 198 | traceback.print_exc() |
|---|
| 199 | print 'ERROR in help method %s'% ( |
|---|
| 200 | command_help_method.func_name) |
|---|
| 201 | |
|---|
| 202 | help_do_help = ("help", "help [topic|command]", |
|---|
| 203 | _("print help message for the given topic/command or \ |
|---|
| 204 | available topics when no argument")) |
|---|
| 205 | |
|---|
| 206 | def do_quit(self): |
|---|
| 207 | """quit the CLI""" |
|---|
| 208 | raise EOFError() |
|---|
| 209 | |
|---|
| 210 | def help_do_quit(self): |
|---|
| 211 | return ("quit", "quit", _("quit the application")) |
|---|