~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shell.py

  • Committer: Aaron Bentley
  • Date: 2005-11-11 17:43:12 UTC
  • Revision ID: aaron.bentley@utoronto.ca-20051111174312-1c627d82a07bf8fd
Added patch for tab-in-patch-filename support

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
 
 
27
SHELL_BLACKLIST = set(['rm', 'ls'])
 
28
COMPLETION_BLACKLIST = set(['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
 
 
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:
 
47
                iter = list(filter_completions(iter, text))
 
48
            except Exception, e:
 
49
                print e, type(e)
 
50
 
 
51
 
 
52
 
 
53
class PromptCmd(cmd.Cmd):
 
54
    def __init__(self):
 
55
        cmd.Cmd.__init__(self)
 
56
        self.prompt = "bzr> "
 
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 += '-'
 
64
        self.history_file = os.path.expanduser("~/.bazaar/shell-history")
 
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()
 
76
        raise StopIteration
 
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 = ""
 
101
        self.prompt = "bzr%s> " % prompt
 
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 = ""
 
111
        sys.stdout.write(terminal.term_title("bzr %s %s" % (command, version)))
 
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):
 
133
        self.default("help "+line)
 
134
 
 
135
    def default(self, line):
 
136
        args = line.split()
 
137
        commandname = args.pop(0)
 
138
        for char in ('|', '<', '>'):
 
139
            commandname = commandname.split(char)[0]
 
140
        if commandname[-1] in ('|', '<', '>'):
 
141
            commandname = commandname[:-1]
 
142
        try:
 
143
            if commandname in SHELL_BLACKLIST:
 
144
                raise BlackListedCommand(commandname)
 
145
            cmd_obj = get_cmd_object(commandname)
 
146
        except (BlackListedCommand, BzrError):
 
147
            return os.system(line)
 
148
 
 
149
        try:
 
150
            if too_complicated(line):
 
151
                return os.system("bzr "+line)
 
152
            else:
 
153
                return (cmd_obj.run_argv(args) or 0)
 
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
 
 
162
 
 
163
    def completenames(self, text, line, begidx, endidx):
 
164
        from bzrlib.trace import mutter
 
165
        completions = []
 
166
        iter = (c for c in iter_command_names() if
 
167
                c not in COMPLETION_BLACKLIST)
 
168
        try:
 
169
            if len(line) > 0:
 
170
                arg = line.split()[-1]
 
171
            else:
 
172
                arg = ""
 
173
            iter = filter_completions(iter, arg)
 
174
        except Exception, e:
 
175
            print e, type(e)
 
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
        """
 
190
        (cmd, args, foo) = self.parseline(line)
 
191
        if cmd == "bzr":
 
192
            try:
 
193
                return self.completenames(text, line, begidx, endidx)
 
194
            except Exception, e:
 
195
                print e
 
196
        try:
 
197
            command_obj = get_cmd_object(cmd)
 
198
        except BzrError:
 
199
            command_obj = None
 
200
        try:
 
201
            if command_obj is not None:
 
202
                opts = list(iter_opt_completions(command_obj))
 
203
                files = list(iter_file_completions(text))
 
204
                return list(filter_completions(opts+files, text))
 
205
            elif cmd == "cd":
 
206
                if len(args) > 0:
 
207
                    arg = args.split()[-1]
 
208
                else:
 
209
                    arg = ""
 
210
                iter = iter_dir_completions(text)
 
211
                iter = filter_completions(iter, text)
 
212
                return list(iter)
 
213
            elif len(args)>0:
 
214
                arg = args.split()[-1]
 
215
                iter = iter_file_completions(arg)
 
216
                return list(iter_munged_completions(iter, arg, text))
 
217
            else:
 
218
                return self.completenames(text, line, begidx, endidx)
 
219
        except Exception, e:
 
220
            print e
 
221
 
 
222
def run_shell():
 
223
    try:
 
224
        prompt = PromptCmd()
 
225
        try:
 
226
            prompt.cmdloop()
 
227
        finally:
 
228
            prompt.write_history()
 
229
    except StopIteration:
 
230
        pass
 
231
 
 
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
 
 
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
 
257
    for file in chain(os.listdir(listingdir), extras):
 
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)
 
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
def filter_completions(iter, arg):
 
288
    return (c for c in iter if c.startswith(arg))
 
289
 
 
290
def iter_munged_completions(iter, arg, text):
 
291
    for completion in iter:
 
292
        completion = str(completion)
 
293
        if completion.startswith(arg):
 
294
            yield completion[len(arg)-len(text):]+" "
 
295
 
 
296
def too_complicated(line):
 
297
    for char in '|<>"\"*?':
 
298
        if char in line:
 
299
            return True
 
300
    return False