~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shell.py

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