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