~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shell.py

  • Committer: Aaron Bentley
  • Date: 2006-03-24 19:51:54 UTC
  • Revision ID: abentley@panoramicfeedback.com-20060324195154-e183244265faeb50
Handled more hrefs

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, get_alias
 
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
        alias_args = get_alias(args[0])
 
158
        if alias_args is not None:
 
159
            args[0] = alias_args.pop(0)
 
160
            
 
161
        commandname = args.pop(0)
 
162
        for char in ('|', '<', '>'):
 
163
            commandname = commandname.split(char)[0]
 
164
        if commandname[-1] in ('|', '<', '>'):
 
165
            commandname = commandname[:-1]
 
166
        try:
 
167
            if commandname in SHELL_BLACKLIST:
 
168
                raise BlackListedCommand(commandname)
 
169
            cmd_obj = get_cmd_object(commandname)
 
170
        except (BlackListedCommand, BzrError):
 
171
            return os.system(line)
 
172
 
 
173
        try:
 
174
            if too_complicated(line):
 
175
                return os.system("bzr "+line)
 
176
            else:
 
177
                return (cmd_obj.run_argv_aliases(args, alias_args) or 0)
 
178
        except BzrError, e:
 
179
            print e
 
180
        except KeyboardInterrupt, e:
 
181
            print "Interrupted"
 
182
        except Exception, e:
 
183
#            print "Unhandled error:\n%s" % errors.exception_str(e)
 
184
            print "Unhandled error:\n%s" % (e)
 
185
 
 
186
 
 
187
    def completenames(self, text, line, begidx, endidx):
 
188
        return CompletionContext(text).get_completions()
 
189
 
 
190
    def completedefault(self, text, line, begidx, endidx):
 
191
        """Perform completion for native commands.
 
192
        
 
193
        :param text: The text to complete
 
194
        :type text: str
 
195
        :param line: The entire line to complete
 
196
        :type line: str
 
197
        :param begidx: The start of the text in the line
 
198
        :type begidx: int
 
199
        :param endidx: The end of the text in the line
 
200
        :type endidx: int
 
201
        """
 
202
        (cmd, args, foo) = self.parseline(line)
 
203
        if cmd == "bzr":
 
204
            cmd = None
 
205
        return CompletionContext(text, command=cmd).get_completions()
 
206
 
 
207
def run_shell():
 
208
    try:
 
209
        prompt = PromptCmd()
 
210
        try:
 
211
            prompt.cmdloop()
 
212
        finally:
 
213
            prompt.write_history()
 
214
    except StopIteration:
 
215
        pass
 
216
 
 
217
def iter_opt_completions(command_obj):
 
218
    for option_name, option in command_obj.options().items():
 
219
        yield "--" + option_name
 
220
        short_name = option.short_name()
 
221
        if short_name:
 
222
            yield "-" + short_name
 
223
 
 
224
def iter_file_completions(arg, only_dirs = False):
 
225
    """Generate an iterator that iterates through filename completions.
 
226
 
 
227
    :param arg: The filename fragment to match
 
228
    :type arg: str
 
229
    :param only_dirs: If true, match only directories
 
230
    :type only_dirs: bool
 
231
    """
 
232
    cwd = os.getcwd()
 
233
    if cwd != "/":
 
234
        extras = [".", ".."]
 
235
    else:
 
236
        extras = []
 
237
    (dir, file) = os.path.split(arg)
 
238
    if dir != "":
 
239
        listingdir = os.path.expanduser(dir)
 
240
    else:
 
241
        listingdir = cwd
 
242
    for file in chain(os.listdir(listingdir), extras):
 
243
        if dir != "":
 
244
            userfile = dir+'/'+file
 
245
        else:
 
246
            userfile = file
 
247
        if userfile.startswith(arg):
 
248
            if os.path.isdir(listingdir+'/'+file):
 
249
                userfile+='/'
 
250
                yield userfile
 
251
            elif not only_dirs:
 
252
                yield userfile
 
253
 
 
254
 
 
255
def iter_dir_completions(arg):
 
256
    """Generate an iterator that iterates through directory name completions.
 
257
 
 
258
    :param arg: The directory name fragment to match
 
259
    :type arg: str
 
260
    """
 
261
    return iter_file_completions(arg, True)
 
262
 
 
263
def iter_command_names(hidden=False):
 
264
    for real_cmd_name, cmd_class in get_all_cmds():
 
265
        if not hidden and cmd_class.hidden:
 
266
            continue
 
267
        for name in [real_cmd_name] + cmd_class.aliases:
 
268
            # Don't complete on aliases that are prefixes of the canonical name
 
269
            if name == real_cmd_name or not real_cmd_name.startswith(name):
 
270
                yield name
 
271
 
 
272
def filter_completions(iter, arg):
 
273
    return (c for c in iter if c.startswith(arg))
 
274
 
 
275
def iter_munged_completions(iter, arg, text):
 
276
    for completion in iter:
 
277
        completion = str(completion)
 
278
        if completion.startswith(arg):
 
279
            yield completion[len(arg)-len(text):]+" "
 
280
 
 
281
def too_complicated(line):
 
282
    for char in '|<>"\"*?':
 
283
        if char in line:
 
284
            return True
 
285
    return False