~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shell.py

  • Committer: Aaron Bentley
  • Date: 2005-09-23 03:08:27 UTC
  • Revision ID: aaron.bentley@utoronto.ca-20050923030827-bd5a4ebd3440daff
prevented accidental overwrites from push

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