blob: cdeef974f482eb8258625ed9b82db307dc24a677 [file] [log] [blame]
# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of logilab-common.
#
# logilab-common is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option) any
# later version.
#
# logilab-common is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with logilab-common. If not, see <http://www.gnu.org/licenses/>.
"""Command line interface helper classes.
It provides some default commands, a help system, a default readline
configuration with completion and persistent history.
Example::
class BookShell(CLIHelper):
def __init__(self):
# quit and help are builtins
# CMD_MAP keys are commands, values are topics
self.CMD_MAP['pionce'] = _("Sommeil")
self.CMD_MAP['ronfle'] = _("Sommeil")
CLIHelper.__init__(self)
help_do_pionce = ("pionce", "pionce duree", _("met ton corps en veille"))
def do_pionce(self):
print('nap is good')
help_do_ronfle = ("ronfle", "ronfle volume", _("met les autres en veille"))
def do_ronfle(self):
print('fuuuuuuuuuuuu rhhhhhrhrhrrh')
cl = BookShell()
"""
from __future__ import print_function
__docformat__ = "restructuredtext en"
from six.moves import builtins, input
if not hasattr(builtins, '_'):
builtins._ = str
def init_readline(complete_method, histfile=None):
"""Init the readline library if available."""
try:
import readline
readline.parse_and_bind("tab: complete")
readline.set_completer(complete_method)
string = readline.get_completer_delims().replace(':', '')
readline.set_completer_delims(string)
if histfile is not None:
try:
readline.read_history_file(histfile)
except IOError:
pass
import atexit
atexit.register(readline.write_history_file, histfile)
except:
print('readline is not available :-(')
class Completer :
"""Readline completer."""
def __init__(self, commands):
self.list = commands
def complete(self, text, state):
"""Hook called by readline when <tab> is pressed."""
n = len(text)
matches = []
for cmd in self.list :
if cmd[:n] == text :
matches.append(cmd)
try:
return matches[state]
except IndexError:
return None
class CLIHelper:
"""An abstract command line interface client which recognize commands
and provide an help system.
"""
CMD_MAP = {'help': _("Others"),
'quit': _("Others"),
}
CMD_PREFIX = ''
def __init__(self, histfile=None) :
self._topics = {}
self.commands = None
self._completer = Completer(self._register_commands())
init_readline(self._completer.complete, histfile)
def run(self):
"""loop on user input, exit on EOF"""
while True:
try:
line = input('>>> ')
except EOFError:
print
break
s_line = line.strip()
if not s_line:
continue
args = s_line.split()
if args[0] in self.commands:
try:
cmd = 'do_%s' % self.commands[args[0]]
getattr(self, cmd)(*args[1:])
except EOFError:
break
except:
import traceback
traceback.print_exc()
else:
try:
self.handle_line(s_line)
except:
import traceback
traceback.print_exc()
def handle_line(self, stripped_line):
"""Method to overload in the concrete class (should handle
lines which are not commands).
"""
raise NotImplementedError()
# private methods #########################################################
def _register_commands(self):
""" register available commands method and return the list of
commands name
"""
self.commands = {}
self._command_help = {}
commands = [attr[3:] for attr in dir(self) if attr[:3] == 'do_']
for command in commands:
topic = self.CMD_MAP[command]
help_method = getattr(self, 'help_do_%s' % command)
self._topics.setdefault(topic, []).append(help_method)
self.commands[self.CMD_PREFIX + command] = command
self._command_help[command] = help_method
return self.commands.keys()
def _print_help(self, cmd, syntax, explanation):
print(_('Command %s') % cmd)
print(_('Syntax: %s') % syntax)
print('\t', explanation)
print()
# predefined commands #####################################################
def do_help(self, command=None) :
"""base input of the help system"""
if command in self._command_help:
self._print_help(*self._command_help[command])
elif command is None or command not in self._topics:
print(_("Use help <topic> or help <command>."))
print(_("Available topics are:"))
topics = sorted(self._topics.keys())
for topic in topics:
print('\t', topic)
print()
print(_("Available commands are:"))
commands = self.commands.keys()
commands.sort()
for command in commands:
print('\t', command[len(self.CMD_PREFIX):])
else:
print(_('Available commands about %s:') % command)
print
for command_help_method in self._topics[command]:
try:
if callable(command_help_method):
self._print_help(*command_help_method())
else:
self._print_help(*command_help_method)
except:
import traceback
traceback.print_exc()
print('ERROR in help method %s'% (
command_help_method.__name__))
help_do_help = ("help", "help [topic|command]",
_("print help message for the given topic/command or \
available topics when no argument"))
def do_quit(self):
"""quit the CLI"""
raise EOFError()
def help_do_quit(self):
return ("quit", "quit", _("quit the application"))