~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shell.py

  • Committer: Aaron Bentley
  • Date: 2006-06-27 14:36:32 UTC
  • Revision ID: abentley@panoramicfeedback.com-20060627143632-0f4114d7b0a8d7d9
Fix zap for checkouts of branches with no parents

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