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