~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shell.py

  • Committer: Aaron Bentley
  • Date: 2006-03-13 00:13:56 UTC
  • Revision ID: aaron.bentley@utoronto.ca-20060313001356-6aadd11700e1ac1f
MarkedĀ deprecationĀ bugs

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