summaryrefslogtreecommitdiff
path: root/cli.py
blob: 42837329020b30bc3405c6f93c036af59d238b7d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# 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()
"""

__docformat__ = "restructuredtext en"

from logilab.common.compat import raw_input, builtins
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 = raw_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.func_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"))