~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shell.py

  • Committer: Aaron Bentley
  • Date: 2005-10-30 03:51:19 UTC
  • mfrom: (257.1.3)
  • Revision ID: aaron.bentley@utoronto.ca-20051030035119-7cda4f9164ea6449
Merged latest changes

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