~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shell.py

  • Committer: Aaron Bentley
  • Date: 2008-01-11 03:01:54 UTC
  • Revision ID: aaron.bentley@utoronto.ca-20080111030154-apm50v0b0tu93prh
Support branch6 formats in rspush

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
18
import cmd
 
19
from itertools import chain
 
20
import os
 
21
import readline
 
22
import shlex
 
23
import stat
 
24
import string
 
25
import sys
 
26
 
 
27
from bzrlib.branch import Branch
 
28
from bzrlib.commands import get_cmd_object, get_all_cmds, get_alias
 
29
from bzrlib.errors import BzrError
 
30
from bzrlib.workingtree import WorkingTree
 
31
 
 
32
import terminal
 
33
 
 
34
 
 
35
SHELL_BLACKLIST = set(['rm', 'ls'])
 
36
COMPLETION_BLACKLIST = set(['shell'])
 
37
 
 
38
 
 
39
class BlackListedCommand(BzrError):
 
40
    def __init__(self, command):
 
41
        BzrError.__init__(self, "The command %s is blacklisted for shell use" %
 
42
                          command)
 
43
 
 
44
 
 
45
class CompletionContext(object):
 
46
    def __init__(self, text, command=None, prev_opt=None, arg_pos=None):
 
47
        self.text = text
 
48
        self.command = command
 
49
        self.prev_opt = prev_opt
 
50
        self.arg_pos = None
 
51
 
 
52
    def get_completions(self):
 
53
        try:
 
54
            return self.get_completions_or_raise()
 
55
        except Exception, e:
 
56
            print e, type(e)
 
57
            return []
 
58
 
 
59
    def get_option_completions(self):
 
60
        try:
 
61
            command_obj = get_cmd_object(self.command)
 
62
        except BzrError:
 
63
            return []
 
64
        opts = [o+" " for o in iter_opt_completions(command_obj)]
 
65
        return list(filter_completions(opts, self.text))
 
66
 
 
67
    def get_completions_or_raise(self):
 
68
        if self.command is None:
 
69
            if '/' in self.text:
 
70
                iter = iter_executables(self.text)
 
71
            else:
 
72
                iter = (c+" " for c in iter_command_names() if
 
73
                        c not in COMPLETION_BLACKLIST)
 
74
            return list(filter_completions(iter, self.text))
 
75
        if self.prev_opt is None:
 
76
            completions = self.get_option_completions()
 
77
            if self.command == "cd":
 
78
                iter = iter_dir_completions(self.text)
 
79
                completions.extend(list(filter_completions(iter, self.text)))
 
80
            else:
 
81
                iter = iter_file_completions(self.text)
 
82
                completions.extend(filter_completions(iter, self.text))
 
83
            return completions
 
84
 
 
85
 
 
86
class PromptCmd(cmd.Cmd):
 
87
    def __init__(self):
 
88
        cmd.Cmd.__init__(self)
 
89
        self.prompt = "bzr> "
 
90
        try:
 
91
            self.tree = WorkingTree.open_containing('.')[0]
 
92
        except:
 
93
            self.tree = None
 
94
        self.set_title()
 
95
        self.set_prompt()
 
96
        self.identchars += '-'
 
97
        self.history_file = os.path.expanduser("~/.bazaar/shell-history")
 
98
        readline.set_completer_delims(string.whitespace)
 
99
        if os.access(self.history_file, os.R_OK) and \
 
100
            os.path.isfile(self.history_file):
 
101
            readline.read_history_file(self.history_file)
 
102
        self.cwd = os.getcwd()
 
103
 
 
104
    def write_history(self):
 
105
        readline.write_history_file(self.history_file)
 
106
 
 
107
    def do_quit(self, args):
 
108
        self.write_history()
 
109
        raise StopIteration
 
110
 
 
111
    def do_exit(self, args):
 
112
        self.do_quit(args)
 
113
 
 
114
    def do_EOF(self, args):
 
115
        print
 
116
        self.do_quit(args)
 
117
 
 
118
    def postcmd(self, line, bar):
 
119
        self.set_title()
 
120
        self.set_prompt()
 
121
 
 
122
    def set_prompt(self):
 
123
        if self.tree is not None:
 
124
            try:
 
125
                prompt_data = (self.tree.branch.nick, self.tree.branch.revno(),
 
126
                               self.tree.relpath('.'))
 
127
                prompt = " %s:%d/%s" % prompt_data
 
128
            except:
 
129
                prompt = ""
 
130
        else:
 
131
            prompt = ""
 
132
        self.prompt = "bzr%s> " % prompt
 
133
 
 
134
    def set_title(self, command=None):
 
135
        try:
 
136
            b = Branch.open_containing('.')[0]
 
137
            version = "%s:%d" % (b.nick, b.revno())
 
138
        except:
 
139
            version = "[no version]"
 
140
        if command is None:
 
141
            command = ""
 
142
        sys.stdout.write(terminal.term_title("bzr %s %s" % (command, version)))
 
143
 
 
144
    def do_cd(self, line):
 
145
        if line == "":
 
146
            line = "~"
 
147
        line = os.path.expanduser(line)
 
148
        if os.path.isabs(line):
 
149
            newcwd = line
 
150
        else:
 
151
            newcwd = self.cwd+'/'+line
 
152
        newcwd = os.path.normpath(newcwd)
 
153
        try:
 
154
            os.chdir(newcwd)
 
155
            self.cwd = newcwd
 
156
        except Exception, e:
 
157
            print e
 
158
        try:
 
159
            self.tree = WorkingTree.open_containing(".")[0]
 
160
        except:
 
161
            self.tree = None
 
162
 
 
163
    def do_help(self, line):
 
164
        self.default("help "+line)
 
165
 
 
166
    def default(self, line):
 
167
        args = shlex.split(line)
 
168
        alias_args = get_alias(args[0])
 
169
        if alias_args is not None:
 
170
            args[0] = alias_args.pop(0)
 
171
 
 
172
        commandname = args.pop(0)
 
173
        for char in ('|', '<', '>'):
 
174
            commandname = commandname.split(char)[0]
 
175
        if commandname[-1] in ('|', '<', '>'):
 
176
            commandname = commandname[:-1]
 
177
        try:
 
178
            if commandname in SHELL_BLACKLIST:
 
179
                raise BlackListedCommand(commandname)
 
180
            cmd_obj = get_cmd_object(commandname)
 
181
        except (BlackListedCommand, BzrError):
 
182
            return os.system(line)
 
183
 
 
184
        try:
 
185
            if too_complicated(line):
 
186
                return os.system("bzr "+line)
 
187
            else:
 
188
                return (cmd_obj.run_argv_aliases(args, alias_args) or 0)
 
189
        except BzrError, e:
 
190
            print e
 
191
        except KeyboardInterrupt, e:
 
192
            print "Interrupted"
 
193
        except Exception, e:
 
194
#            print "Unhandled error:\n%s" % errors.exception_str(e)
 
195
            print "Unhandled error:\n%s" % (e)
 
196
 
 
197
 
 
198
    def completenames(self, text, line, begidx, endidx):
 
199
        return CompletionContext(text).get_completions()
 
200
 
 
201
    def completedefault(self, text, line, begidx, endidx):
 
202
        """Perform completion for native commands.
 
203
 
 
204
        :param text: The text to complete
 
205
        :type text: str
 
206
        :param line: The entire line to complete
 
207
        :type line: str
 
208
        :param begidx: The start of the text in the line
 
209
        :type begidx: int
 
210
        :param endidx: The end of the text in the line
 
211
        :type endidx: int
 
212
        """
 
213
        (cmd, args, foo) = self.parseline(line)
 
214
        if cmd == "bzr":
 
215
            cmd = None
 
216
        return CompletionContext(text, command=cmd).get_completions()
 
217
 
 
218
 
 
219
def run_shell():
 
220
    try:
 
221
        prompt = PromptCmd()
 
222
        try:
 
223
            prompt.cmdloop()
 
224
        finally:
 
225
            prompt.write_history()
 
226
    except StopIteration:
 
227
        pass
 
228
 
 
229
 
 
230
def iter_opt_completions(command_obj):
 
231
    for option_name, option in command_obj.options().items():
 
232
        yield "--" + option_name
 
233
        short_name = option.short_name()
 
234
        if short_name:
 
235
            yield "-" + short_name
 
236
 
 
237
 
 
238
def iter_file_completions(arg, only_dirs = False):
 
239
    """Generate an iterator that iterates through filename completions.
 
240
 
 
241
    :param arg: The filename fragment to match
 
242
    :type arg: str
 
243
    :param only_dirs: If true, match only directories
 
244
    :type only_dirs: bool
 
245
    """
 
246
    cwd = os.getcwd()
 
247
    if cwd != "/":
 
248
        extras = [".", ".."]
 
249
    else:
 
250
        extras = []
 
251
    (dir, file) = os.path.split(arg)
 
252
    if dir != "":
 
253
        listingdir = os.path.expanduser(dir)
 
254
    else:
 
255
        listingdir = cwd
 
256
    for file in chain(os.listdir(listingdir), extras):
 
257
        if dir != "":
 
258
            userfile = dir+'/'+file
 
259
        else:
 
260
            userfile = file
 
261
        if userfile.startswith(arg):
 
262
            if os.path.isdir(listingdir+'/'+file):
 
263
                userfile+='/'
 
264
                yield userfile
 
265
            elif not only_dirs:
 
266
                yield userfile + ' '
 
267
 
 
268
 
 
269
def iter_dir_completions(arg):
 
270
    """Generate an iterator that iterates through directory name completions.
 
271
 
 
272
    :param arg: The directory name fragment to match
 
273
    :type arg: str
 
274
    """
 
275
    return iter_file_completions(arg, True)
 
276
 
 
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:
 
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
 
286
 
 
287
 
 
288
def iter_executables(path):
 
289
    dirname, partial = os.path.split(path)
 
290
    for filename in os.listdir(dirname):
 
291
        if not filename.startswith(partial):
 
292
            continue
 
293
        fullpath = os.path.join(dirname, filename)
 
294
        mode=os.lstat(fullpath)[stat.ST_MODE]
 
295
        if stat.S_ISREG(mode) and 0111 & mode:
 
296
            yield fullpath + ' '
 
297
 
 
298
 
 
299
def filter_completions(iter, arg):
 
300
    return (c for c in iter if c.startswith(arg))
 
301
 
 
302
 
 
303
def iter_munged_completions(iter, arg, text):
 
304
    for completion in iter:
 
305
        completion = str(completion)
 
306
        if completion.startswith(arg):
 
307
            yield completion[len(arg)-len(text):]+" "
 
308
 
 
309
 
 
310
def too_complicated(line):
 
311
    for char in '|<>*?':
 
312
        if char in line:
 
313
            return True
 
314
    return False