~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
248 by Aaron Bentley
Initial import of Fai shell command
35
class PromptCmd(cmd.Cmd):
36
    def __init__(self):
37
        cmd.Cmd.__init__(self)
249 by Aaron Bentley
Got the shell basics working properly
38
        self.prompt = "bzr> "
248 by Aaron Bentley
Initial import of Fai shell command
39
        try:
40
            self.tree = arch.tree_root(".")
41
        except:
42
            self.tree = None
43
        self.set_title()
44
        self.set_prompt()
45
        self.identchars += '-'
249 by Aaron Bentley
Got the shell basics working properly
46
        self.history_file = os.path.expanduser("~/.bazaar/shell-history")
248 by Aaron Bentley
Initial import of Fai shell command
47
        readline.set_completer_delims(string.whitespace)
48
        if os.access(self.history_file, os.R_OK) and \
49
            os.path.isfile(self.history_file):
50
            readline.read_history_file(self.history_file)
51
        self.cwd = os.getcwd()
52
53
    def write_history(self):
54
        readline.write_history_file(self.history_file)
55
56
    def do_quit(self, args):
57
        self.write_history()
255 by Aaron Bentley
Fixed system.exit printing 0 bug
58
        raise StopIteration
248 by Aaron Bentley
Initial import of Fai shell command
59
60
    def do_exit(self, args):
61
        self.do_quit(args)
62
63
    def do_EOF(self, args):
64
        print
65
        self.do_quit(args)
66
67
    def postcmd(self, line, bar):
68
        self.set_title()
69
        self.set_prompt()
70
71
    def set_prompt(self):
72
        if self.tree is not None:
73
            try:
74
                prompt = pylon.alias_or_version(self.tree.tree_version, 
75
                                                self.tree, 
76
                                                full=False)
77
                if prompt is not None:
78
                    prompt = " " + prompt +":"+ pylon.tree_cwd(self.tree)
79
            except:
80
                prompt = ""
81
        else:
82
            prompt = ""
249 by Aaron Bentley
Got the shell basics working properly
83
        self.prompt = "bzr%s> " % prompt
248 by Aaron Bentley
Initial import of Fai shell command
84
85
    def set_title(self, command=None):
86
        try:
87
            version = pylon.alias_or_version(self.tree.tree_version, self.tree, 
88
                                             full=False)
89
        except:
90
            version = "[no version]"
91
        if command is None:
92
            command = ""
249 by Aaron Bentley
Got the shell basics working properly
93
        sys.stdout.write(terminal.term_title("bzr %s %s" % (command, version)))
248 by Aaron Bentley
Initial import of Fai shell command
94
95
    def do_cd(self, line):
96
        if line == "":
97
            line = "~"
98
        line = os.path.expanduser(line)
99
        if os.path.isabs(line):
100
            newcwd = line
101
        else:
102
            newcwd = self.cwd+'/'+line
103
        newcwd = os.path.normpath(newcwd)
104
        try:
105
            os.chdir(newcwd)
106
            self.cwd = newcwd
107
        except Exception, e:
108
            print e
109
        try:
110
            self.tree = arch.tree_root(".")
111
        except:
112
            self.tree = None
113
114
    def do_help(self, line):
251 by Aaron Bentley
Got support for option completion
115
        self.default("help "+line)
248 by Aaron Bentley
Initial import of Fai shell command
116
117
    def default(self, line):
118
        args = line.split()
249 by Aaron Bentley
Got the shell basics working properly
119
        commandname = args.pop(0)
254 by Aaron Bentley
Added fallback to shell for lines with quotes and IO redirection
120
        for char in ('|', '<', '>'):
121
            commandname = commandname.split(char)[0]
122
        if commandname[-1] in ('|', '<', '>'):
123
            commandname = commandname[:-1]
249 by Aaron Bentley
Got the shell basics working properly
124
        try:
253 by Aaron Bentley
Prevented bzr's rm and ls from being invoked in the shell
125
            if commandname in SHELL_BLACKLIST:
126
                raise BlackListedCommand(commandname)
249 by Aaron Bentley
Got the shell basics working properly
127
            cmd_obj = get_cmd_object(commandname)
253 by Aaron Bentley
Prevented bzr's rm and ls from being invoked in the shell
128
        except (BlackListedCommand, BzrError):
249 by Aaron Bentley
Got the shell basics working properly
129
            return os.system(line)
130
131
        try:
254 by Aaron Bentley
Added fallback to shell for lines with quotes and IO redirection
132
            if too_complicated(line):
133
                return os.system("bzr "+line)
134
            else:
135
                return (cmd_obj.run_argv(args) or 0)
249 by Aaron Bentley
Got the shell basics working properly
136
        except BzrError, e:
137
            print e
138
        except KeyboardInterrupt, e:
139
            print "Interrupted"
140
        except Exception, e:
141
#            print "Unhandled error:\n%s" % errors.exception_str(e)
142
            print "Unhandled error:\n%s" % (e)
143
248 by Aaron Bentley
Initial import of Fai shell command
144
145
    def completenames(self, text, line, begidx, endidx):
146
        completions = []
257 by Aaron Bentley
blacklisted 'shell' from completion
147
        iter = (c for c in iter_command_names() if
148
                c not in COMPLETION_BLACKLIST)
248 by Aaron Bentley
Initial import of Fai shell command
149
        try:
150
            if len(line) > 0:
151
                arg = line.split()[-1]
152
            else:
153
                arg = ""
250 by Aaron Bentley
Got command completion working
154
            iter = list(iter_munged_completions(iter, arg, text))
248 by Aaron Bentley
Initial import of Fai shell command
155
        except Exception, e:
250 by Aaron Bentley
Got command completion working
156
            print e, type(e)
248 by Aaron Bentley
Initial import of Fai shell command
157
        return list(iter)
158
159
    def completedefault(self, text, line, begidx, endidx):
160
        """Perform completion for native commands.
161
        
162
        :param text: The text to complete
163
        :type text: str
164
        :param line: The entire line to complete
165
        :type line: str
166
        :param begidx: The start of the text in the line
167
        :type begidx: int
168
        :param endidx: The end of the text in the line
169
        :type endidx: int
170
        """
251 by Aaron Bentley
Got support for option completion
171
        (cmd, args, foo) = self.parseline(line)
256 by Aaron Bentley
Enhanced shell completion
172
        if cmd == "bzr":
173
            try:
174
                return self.completenames(text, line, begidx, endidx)
175
            except Exception, e:
176
                print e
251 by Aaron Bentley
Got support for option completion
177
        try:
178
            command_obj = get_cmd_object(cmd)
179
        except BzrError:
180
            command_obj = None
181
        try:
248 by Aaron Bentley
Initial import of Fai shell command
182
            if command_obj is not None:
251 by Aaron Bentley
Got support for option completion
183
                opts = []
184
                for option_name, option in command_obj.options().items():
185
                    opts.append("--" + option_name)
186
                    short_name = option.short_name()
187
                    if short_name:
188
                        opts.append("-" + short_name)
189
                q = list(iter_munged_completions(opts, args, text))
190
                return list(iter_munged_completions(opts, args, text))
248 by Aaron Bentley
Initial import of Fai shell command
191
            elif cmd == "cd":
192
                if len(args) > 0:
193
                    arg = args.split()[-1]
194
                else:
195
                    arg = ""
252 by Aaron Bentley
Fixed dirctory completion in shell
196
                iter = iter_dir_completions(arg)
197
                iter = iter_munged_completions(iter, arg, text)
248 by Aaron Bentley
Initial import of Fai shell command
198
                return list(iter)
199
            elif len(args)>0:
200
                arg = args.split()[-1]
252 by Aaron Bentley
Fixed dirctory completion in shell
201
                iter = iter_file_completions(arg)
202
                return list(iter_munged_completions(iter, arg, text))
248 by Aaron Bentley
Initial import of Fai shell command
203
            else:
204
                return self.completenames(text, line, begidx, endidx)
205
        except Exception, e:
206
            print e
207
208
def run_shell():
209
    try:
255 by Aaron Bentley
Fixed system.exit printing 0 bug
210
        prompt = PromptCmd()
211
        try:
212
            prompt.cmdloop()
213
        finally:
214
            prompt.write_history()
215
    except StopIteration:
216
        pass
248 by Aaron Bentley
Initial import of Fai shell command
217
218
def iter_file_completions(arg, only_dirs = False):
219
    """Generate an iterator that iterates through filename completions.
220
221
    :param arg: The filename fragment to match
222
    :type arg: str
223
    :param only_dirs: If true, match only directories
224
    :type only_dirs: bool
225
    """
226
    cwd = os.getcwd()
227
    if cwd != "/":
228
        extras = [".", ".."]
229
    else:
230
        extras = []
231
    (dir, file) = os.path.split(arg)
232
    if dir != "":
233
        listingdir = os.path.expanduser(dir)
234
    else:
235
        listingdir = cwd
252 by Aaron Bentley
Fixed dirctory completion in shell
236
    for file in chain(os.listdir(listingdir), extras):
248 by Aaron Bentley
Initial import of Fai shell command
237
        if dir != "":
238
            userfile = dir+'/'+file
239
        else:
240
            userfile = file
241
        if userfile.startswith(arg):
242
            if os.path.isdir(listingdir+'/'+file):
243
                userfile+='/'
244
                yield userfile
245
            elif not only_dirs:
246
                yield userfile
247
248
249
def iter_dir_completions(arg):
250
    """Generate an iterator that iterates through directory name completions.
251
252
    :param arg: The directory name fragment to match
253
    :type arg: str
254
    """
255
    return iter_file_completions(arg, True)
250 by Aaron Bentley
Got command completion working
256
257
def iter_command_names(hidden=False):
258
    for real_cmd_name, cmd_class in get_all_cmds():
259
        if not hidden and cmd_class.hidden:
260
            continue
261
        for name in [real_cmd_name] + cmd_class.aliases:
256 by Aaron Bentley
Enhanced shell completion
262
            # Don't complete on aliases that are prefixes of the canonical name
263
            if name == real_cmd_name or not real_cmd_name.startswith(name):
264
                yield name
250 by Aaron Bentley
Got command completion working
265
266
def iter_munged_completions(iter, arg, text):
267
    for completion in iter:
268
        completion = str(completion)
269
        if completion.startswith(arg):
256 by Aaron Bentley
Enhanced shell completion
270
            yield completion[len(arg)-len(text):]+" "
250 by Aaron Bentley
Got command completion working
271
254 by Aaron Bentley
Added fallback to shell for lines with quotes and IO redirection
272
def too_complicated(line):
261 by Aaron Bentley
Added *? to the list of tricky characters.
273
    for char in '|<>"\"*?':
254 by Aaron Bentley
Added fallback to shell for lines with quotes and IO redirection
274
        if char in line:
275
            return True
276
    return False