blob: cdeef974f482eb8258625ed9b82db307dc24a677 [file] [log] [blame]
[email protected]267d6592012-06-19 19:23:311# 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
20It provides some default commands, a help system, a default readline
21configuration with completion and persistent history.
22
23Example::
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]9f1f0402014-12-11 21:40:5236 print('nap is good')
[email protected]267d6592012-06-19 19:23:3137
38 help_do_ronfle = ("ronfle", "ronfle volume", _("met les autres en veille"))
39 def do_ronfle(self):
[email protected]9f1f0402014-12-11 21:40:5240 print('fuuuuuuuuuuuu rhhhhhrhrhrrh')
[email protected]267d6592012-06-19 19:23:3141
42 cl = BookShell()
43"""
44
[email protected]9f1f0402014-12-11 21:40:5245from __future__ import print_function
46
[email protected]267d6592012-06-19 19:23:3147__docformat__ = "restructuredtext en"
48
[email protected]e3b32af2014-11-20 22:41:0749from six.moves import builtins, input
50
[email protected]267d6592012-06-19 19:23:3151if not hasattr(builtins, '_'):
52 builtins._ = str
53
54
55def 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]9f1f0402014-12-11 21:40:5271 print('readline is not available :-(')
[email protected]267d6592012-06-19 19:23:3172
73
74class 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
93class 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]e3b32af2014-11-20 22:41:07113 line = input('>>> ')
[email protected]267d6592012-06-19 19:23:31114 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]9f1f0402014-12-11 21:40:52162 print(_('Command %s') % cmd)
163 print(_('Syntax: %s') % syntax)
164 print('\t', explanation)
165 print()
[email protected]267d6592012-06-19 19:23:31166
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]9f1f0402014-12-11 21:40:52175 print(_("Use help <topic> or help <command>."))
176 print(_("Available topics are:"))
[email protected]267d6592012-06-19 19:23:31177 topics = sorted(self._topics.keys())
178 for topic in topics:
[email protected]9f1f0402014-12-11 21:40:52179 print('\t', topic)
180 print()
181 print(_("Available commands are:"))
[email protected]267d6592012-06-19 19:23:31182 commands = self.commands.keys()
183 commands.sort()
184 for command in commands:
[email protected]9f1f0402014-12-11 21:40:52185 print('\t', command[len(self.CMD_PREFIX):])
[email protected]267d6592012-06-19 19:23:31186
187 else:
[email protected]9f1f0402014-12-11 21:40:52188 print(_('Available commands about %s:') % command)
[email protected]267d6592012-06-19 19:23:31189 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]9f1f0402014-12-11 21:40:52199 print('ERROR in help method %s'% (
200 command_help_method.__name__))
[email protected]267d6592012-06-19 19:23:31201
202 help_do_help = ("help", "help [topic|command]",
203 _("print help message for the given topic/command or \
204available 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"))