~abentley/bzrtools/bzrtools.dev

249 by Aaron Bentley
Got the shell basics working properly
1
# Copyright (C) 2004, 2005 Aaron Bentley
2
# <aaron.bentley@utoronto.ca>
3
#
4
#    This program is free software; you can redistribute it and/or modify
5
#    it under the terms of the GNU General Public License as published by
6
#    the Free Software Foundation; either version 2 of the License, or
7
#    (at your option) any later version.
8
#
9
#    This program is distributed in the hope that it will be useful,
10
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
11
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
#    GNU General Public License for more details.
13
#
14
#    You should have received a copy of the GNU General Public License
15
#    along with this program; if not, write to the Free Software
16
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
import cmd
18
import sys
19
import os
20
import terminal
21
import readline
22
import string
252 by Aaron Bentley
Fixed dirctory completion in shell
23
from itertools import chain
249 by Aaron Bentley
Got the shell basics working properly
24
from bzrlib.errors import BzrError
250 by Aaron Bentley
Got command completion working
25
from bzrlib.commands import get_cmd_object, get_all_cmds
249 by Aaron Bentley
Got the shell basics working properly
26
253 by Aaron Bentley
Prevented bzr's rm and ls from being invoked in the shell
27
SHELL_BLACKLIST = set(['rm', 'ls'])
257 by Aaron Bentley
blacklisted 'shell' from completion
28
COMPLETION_BLACKLIST = set(['shell'])
253 by Aaron Bentley
Prevented bzr's rm and ls from being invoked in the shell
29
30
class BlackListedCommand(BzrError):
31
    def __init__(self, command):
32
        BzrError.__init__(self, "The command %s is blacklisted for shell use" %
33
                          command)
34
262 by Aaron Bentley
Release 0.6
35
class CompletionContext(object):
36
    def __init__(self, text, command=None, prev_opt=None, arg_pos=None):
37
        self.text = text
38
        self.command = command
39
        self.prev_opt = prev_opt
40
        self.arg_pos = None
41
42
    def get_completions(self):
43
        if not command:
44
            iter = (c for c in iter_command_names() if
45
                    c not in COMPLETION_BLACKLIST)
46
            try:
47
                if len(line) > 0:
48
                    arg = line.split()[-1]
49
                else:
50
                    arg = ""
51
                iter = list(iter_munged_completions(iter, arg, text))
52
            except Exception, e:
53
                print e, type(e)
54
55
56
248 by Aaron Bentley
Initial import of Fai shell command
57
class PromptCmd(cmd.Cmd):
58
    def __init__(self):
59
        cmd.Cmd.__init__(self)
249 by Aaron Bentley
Got the shell basics working properly
60
        self.prompt = "bzr> "
248 by Aaron Bentley
Initial import of Fai shell command
61
        try:
62
            self.tree = arch.tree_root(".")
63
        except:
64
            self.tree = None
65
        self.set_title()
66
        self.set_prompt()
67
        self.identchars += '-'
249 by Aaron Bentley
Got the shell basics working properly
68
        self.history_file = os.path.expanduser("~/.bazaar/shell-history")
248 by Aaron Bentley
Initial import of Fai shell command
69
        readline.set_completer_delims(string.whitespace)
70
        if os.access(self.history_file, os.R_OK) and \
71
            os.path.isfile(self.history_file):
72
            readline.read_history_file(self.history_file)
73
        self.cwd = os.getcwd()
74
75
    def write_history(self):
76
        readline.write_history_file(self.history_file)
77
78
    def do_quit(self, args):
79
        self.write_history()
255 by Aaron Bentley
Fixed system.exit printing 0 bug
80
        raise StopIteration
248 by Aaron Bentley
Initial import of Fai shell command
81
82
    def do_exit(self, args):
83
        self.do_quit(args)
84
85
    def do_EOF(self, args):
86
        print
87
        self.do_quit(args)
88
89
    def postcmd(self, line, bar):
90
        self.set_title()
91
        self.set_prompt()
92
93
    def set_prompt(self):
94
        if self.tree is not None:
95
            try:
96
                prompt = pylon.alias_or_version(self.tree.tree_version, 
97
                                                self.tree, 
98
                                                full=False)
99
                if prompt is not None:
100
                    prompt = " " + prompt +":"+ pylon.tree_cwd(self.tree)
101
            except:
102
                prompt = ""
103
        else:
104
            prompt = ""
249 by Aaron Bentley
Got the shell basics working properly
105
        self.prompt = "bzr%s> " % prompt
248 by Aaron Bentley
Initial import of Fai shell command
106
107
    def set_title(self, command=None):
108
        try:
109
            version = pylon.alias_or_version(self.tree.tree_version, self.tree, 
110
                                             full=False)
111
        except:
112
            version = "[no version]"
113
        if command is None:
114
            command = ""
249 by Aaron Bentley
Got the shell basics working properly
115
        sys.stdout.write(terminal.term_title("bzr %s %s" % (command, version)))
248 by Aaron Bentley
Initial import of Fai shell command
116
117
    def do_cd(self, line):
118
        if line == "":
119
            line = "~"
120
        line = os.path.expanduser(line)
121
        if os.path.isabs(line):
122
            newcwd = line
123
        else:
124
            newcwd = self.cwd+'/'+line
125
        newcwd = os.path.normpath(newcwd)
126
        try:
127
            os.chdir(newcwd)
128
            self.cwd = newcwd
129
        except Exception, e:
130
            print e
131
        try:
132
            self.tree = arch.tree_root(".")
133
        except:
134
            self.tree = None
135
136
    def do_help(self, line):
251 by Aaron Bentley
Got support for option completion
137
        self.default("help "+line)
248 by Aaron Bentley
Initial import of Fai shell command
138
139
    def default(self, line):
140
        args = line.split()
249 by Aaron Bentley
Got the shell basics working properly
141
        commandname = args.pop(0)
254 by Aaron Bentley
Added fallback to shell for lines with quotes and IO redirection
142
        for char in ('|', '<', '>'):
143
            commandname = commandname.split(char)[0]
144
        if commandname[-1] in ('|', '<', '>'):
145
            commandname = commandname[:-1]
249 by Aaron Bentley
Got the shell basics working properly
146
        try:
253 by Aaron Bentley
Prevented bzr's rm and ls from being invoked in the shell
147
            if commandname in SHELL_BLACKLIST:
148
                raise BlackListedCommand(commandname)
249 by Aaron Bentley
Got the shell basics working properly
149
            cmd_obj = get_cmd_object(commandname)
253 by Aaron Bentley
Prevented bzr's rm and ls from being invoked in the shell
150
        except (BlackListedCommand, BzrError):
249 by Aaron Bentley
Got the shell basics working properly
151
            return os.system(line)
152
153
        try:
254 by Aaron Bentley
Added fallback to shell for lines with quotes and IO redirection
154
            if too_complicated(line):
155
                return os.system("bzr "+line)
156
            else:
157
                return (cmd_obj.run_argv(args) or 0)
249 by Aaron Bentley
Got the shell basics working properly
158
        except BzrError, e:
159
            print e
160
        except KeyboardInterrupt, e:
161
            print "Interrupted"
162
        except Exception, e:
163
#            print "Unhandled error:\n%s" % errors.exception_str(e)
164
            print "Unhandled error:\n%s" % (e)
165
248 by Aaron Bentley
Initial import of Fai shell command
166
167
    def completenames(self, text, line, begidx, endidx):
168
        completions = []
257 by Aaron Bentley
blacklisted 'shell' from completion
169
        iter = (c for c in iter_command_names() if
170
                c not in COMPLETION_BLACKLIST)
248 by Aaron Bentley
Initial import of Fai shell command
171
        try:
172
            if len(line) > 0:
173
                arg = line.split()[-1]
174
            else:
175
                arg = ""
250 by Aaron Bentley
Got command completion working
176
            iter = list(iter_munged_completions(iter, arg, text))
248 by Aaron Bentley
Initial import of Fai shell command
177
        except Exception, e:
250 by Aaron Bentley
Got command completion working
178
            print e, type(e)
248 by Aaron Bentley
Initial import of Fai shell command
179
        return list(iter)
180
181
    def completedefault(self, text, line, begidx, endidx):
182
        """Perform completion for native commands.
183
        
184
        :param text: The text to complete
185
        :type text: str
186
        :param line: The entire line to complete
187
        :type line: str
188
        :param begidx: The start of the text in the line
189
        :type begidx: int
190
        :param endidx: The end of the text in the line
191
        :type endidx: int
192
        """
251 by Aaron Bentley
Got support for option completion
193
        (cmd, args, foo) = self.parseline(line)
256 by Aaron Bentley
Enhanced shell completion
194
        if cmd == "bzr":
195
            try:
196
                return self.completenames(text, line, begidx, endidx)
197
            except Exception, e:
198
                print e
251 by Aaron Bentley
Got support for option completion
199
        try:
200
            command_obj = get_cmd_object(cmd)
201
        except BzrError:
202
            command_obj = None
203
        try:
248 by Aaron Bentley
Initial import of Fai shell command
204
            if command_obj is not None:
251 by Aaron Bentley
Got support for option completion
205
                opts = []
206
                for option_name, option in command_obj.options().items():
207
                    opts.append("--" + option_name)
208
                    short_name = option.short_name()
209
                    if short_name:
210
                        opts.append("-" + short_name)
211
                q = list(iter_munged_completions(opts, args, text))
212
                return list(iter_munged_completions(opts, args, text))
248 by Aaron Bentley
Initial import of Fai shell command
213
            elif cmd == "cd":
214
                if len(args) > 0:
215
                    arg = args.split()[-1]
216
                else:
217
                    arg = ""
252 by Aaron Bentley
Fixed dirctory completion in shell
218
                iter = iter_dir_completions(arg)
219
                iter = iter_munged_completions(iter, arg, text)
248 by Aaron Bentley
Initial import of Fai shell command
220
                return list(iter)
221
            elif len(args)>0:
222
                arg = args.split()[-1]
252 by Aaron Bentley
Fixed dirctory completion in shell
223
                iter = iter_file_completions(arg)
224
                return list(iter_munged_completions(iter, arg, text))
248 by Aaron Bentley
Initial import of Fai shell command
225
            else:
226
                return self.completenames(text, line, begidx, endidx)
227
        except Exception, e:
228
            print e
229
230
def run_shell():
231
    try:
255 by Aaron Bentley
Fixed system.exit printing 0 bug
232
        prompt = PromptCmd()
233
        try:
234
            prompt.cmdloop()
235
        finally:
236
            prompt.write_history()
237
    except StopIteration:
238
        pass
248 by Aaron Bentley
Initial import of Fai shell command
239
240
def iter_file_completions(arg, only_dirs = False):
241
    """Generate an iterator that iterates through filename completions.
242
243
    :param arg: The filename fragment to match
244
    :type arg: str
245
    :param only_dirs: If true, match only directories
246
    :type only_dirs: bool
247
    """
248
    cwd = os.getcwd()
249
    if cwd != "/":
250
        extras = [".", ".."]
251
    else:
252
        extras = []
253
    (dir, file) = os.path.split(arg)
254
    if dir != "":
255
        listingdir = os.path.expanduser(dir)
256
    else:
257
        listingdir = cwd
252 by Aaron Bentley
Fixed dirctory completion in shell
258
    for file in chain(os.listdir(listingdir), extras):
248 by Aaron Bentley
Initial import of Fai shell command
259
        if dir != "":
260
            userfile = dir+'/'+file
261
        else:
262
            userfile = file
263
        if userfile.startswith(arg):
264
            if os.path.isdir(listingdir+'/'+file):
265
                userfile+='/'
266
                yield userfile
267
            elif not only_dirs:
268
                yield userfile
269
270
271
def iter_dir_completions(arg):
272
    """Generate an iterator that iterates through directory name completions.
273
274
    :param arg: The directory name fragment to match
275
    :type arg: str
276
    """
277
    return iter_file_completions(arg, True)
250 by Aaron Bentley
Got command completion working
278
279
def iter_command_names(hidden=False):
280
    for real_cmd_name, cmd_class in get_all_cmds():
281
        if not hidden and cmd_class.hidden:
282
            continue
283
        for name in [real_cmd_name] + cmd_class.aliases:
256 by Aaron Bentley
Enhanced shell completion
284
            # Don't complete on aliases that are prefixes of the canonical name
285
            if name == real_cmd_name or not real_cmd_name.startswith(name):
286
                yield name
250 by Aaron Bentley
Got command completion working
287
288
def iter_munged_completions(iter, arg, text):
289
    for completion in iter:
290
        completion = str(completion)
291
        if completion.startswith(arg):
256 by Aaron Bentley
Enhanced shell completion
292
            yield completion[len(arg)-len(text):]+" "
250 by Aaron Bentley
Got command completion working
293
254 by Aaron Bentley
Added fallback to shell for lines with quotes and IO redirection
294
def too_complicated(line):
261 by Aaron Bentley
Added *? to the list of tricky characters.
295
    for char in '|<>"\"*?':
254 by Aaron Bentley
Added fallback to shell for lines with quotes and IO redirection
296
        if char in line:
297
            return True
298
    return False