~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shell.py

  • Committer: Jeff Bailey
  • Date: 2005-06-08 22:30:34 UTC
  • Revision ID: jbailey@ppc64-20050608223034-3cbc9567103c4810
Add Debian directory

Show diffs side-by-side

added added

removed removed

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