~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shell.py

  • Committer: aaron.bentley at utoronto
  • Date: 2005-08-27 02:40:27 UTC
  • Revision ID: aaron.bentley@utoronto.ca-20050827024027-1cb9fbbcc46dda38
Added clean-tree command

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, all_command_names, 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
 
        whitespace = ''.join(c for c in string.whitespace if c < chr(127))
103
 
        readline.set_completer_delims(whitespace)
104
 
        if os.access(self.history_file, os.R_OK) and \
105
 
            os.path.isfile(self.history_file):
106
 
            readline.read_history_file(self.history_file)
107
 
        self.cwd = os.getcwd()
108
 
 
109
 
    def write_history(self):
110
 
        readline.write_history_file(self.history_file)
111
 
 
112
 
    def do_quit(self, args):
113
 
        self.write_history()
114
 
        raise StopIteration
115
 
 
116
 
    def do_exit(self, args):
117
 
        self.do_quit(args)
118
 
 
119
 
    def do_EOF(self, args):
120
 
        print
121
 
        self.do_quit(args)
122
 
 
123
 
    def postcmd(self, line, bar):
124
 
        self.set_title()
125
 
        self.set_prompt()
126
 
 
127
 
    def set_prompt(self):
128
 
        if self.tree is not None:
129
 
            try:
130
 
                prompt_data = (self.tree.branch.nick, self.tree.branch.revno(),
131
 
                               self.tree.relpath('.'))
132
 
                prompt = " %s:%d/%s" % prompt_data
133
 
            except:
134
 
                prompt = ""
135
 
        else:
136
 
            prompt = ""
137
 
        self.prompt = "bzr%s> " % prompt
138
 
 
139
 
    def set_title(self, command=None):
140
 
        try:
141
 
            b = Branch.open_containing('.')[0]
142
 
            version = "%s:%d" % (b.nick, b.revno())
143
 
        except:
144
 
            version = "[no version]"
145
 
        if command is None:
146
 
            command = ""
147
 
        sys.stdout.write(terminal.term_title("bzr %s %s" % (command, version)))
148
 
 
149
 
    def do_cd(self, line):
150
 
        if line == "":
151
 
            line = "~"
152
 
        line = os.path.expanduser(line)
153
 
        if os.path.isabs(line):
154
 
            newcwd = line
155
 
        else:
156
 
            newcwd = self.cwd+'/'+line
157
 
        newcwd = os.path.normpath(newcwd)
158
 
        try:
159
 
            os.chdir(newcwd)
160
 
            self.cwd = newcwd
161
 
        except Exception, e:
162
 
            print e
163
 
        try:
164
 
            self.tree = WorkingTree.open_containing(".")[0]
165
 
        except:
166
 
            self.tree = None
167
 
 
168
 
    def do_help(self, line):
169
 
        self.default("help "+line)
170
 
 
171
 
    def default(self, line):
172
 
        try:
173
 
            args = shlex.split(line)
174
 
        except ValueError, e:
175
 
            print 'Parse error:', e
176
 
            return
177
 
 
178
 
        alias_args = get_alias(args[0])
179
 
        if alias_args is not None:
180
 
            args[0] = alias_args.pop(0)
181
 
 
182
 
        commandname = args.pop(0)
183
 
        for char in ('|', '<', '>'):
184
 
            commandname = commandname.split(char)[0]
185
 
        if commandname[-1] in ('|', '<', '>'):
186
 
            commandname = commandname[:-1]
187
 
        try:
188
 
            if commandname in SHELL_BLACKLIST:
189
 
                raise BlackListedCommand(commandname)
190
 
            cmd_obj = get_cmd_object(commandname)
191
 
        except (BlackListedCommand, BzrError):
192
 
            return os.system(line)
193
 
 
194
 
        try:
195
 
            if too_complicated(line):
196
 
                return os.system("bzr "+line)
197
 
            else:
198
 
                return (cmd_obj.run_argv_aliases(args, alias_args) or 0)
199
 
        except BzrError, e:
200
 
            print e
201
 
        except KeyboardInterrupt, e:
202
 
            print "Interrupted"
203
 
        except Exception, e:
204
 
#            print "Unhandled error:\n%s" % errors.exception_str(e)
205
 
            print "Unhandled error:\n%s" % (e)
206
 
 
207
 
 
208
 
    def completenames(self, text, line, begidx, endidx):
209
 
        return CompletionContext(text).get_completions()
210
 
 
211
 
    def completedefault(self, text, line, begidx, endidx):
212
 
        """Perform completion for native commands.
213
 
 
214
 
        :param text: The text to complete
215
 
        :type text: str
216
 
        :param line: The entire line to complete
217
 
        :type line: str
218
 
        :param begidx: The start of the text in the line
219
 
        :type begidx: int
220
 
        :param endidx: The end of the text in the line
221
 
        :type endidx: int
222
 
        """
223
 
        (cmd, args, foo) = self.parseline(line)
224
 
        if cmd == "bzr":
225
 
            cmd = None
226
 
        return CompletionContext(text, command=cmd).get_completions()
227
 
 
228
 
 
229
 
def run_shell():
230
 
    try:
231
 
        prompt = PromptCmd()
232
 
        while True:
233
 
            try:
234
 
                try:
235
 
                    prompt.cmdloop()
236
 
                except KeyboardInterrupt:
237
 
                    print
238
 
            finally:
239
 
                prompt.write_history()
240
 
    except StopIteration:
241
 
        pass
242
 
 
243
 
 
244
 
def iter_opt_completions(command_obj):
245
 
    for option_name, option in command_obj.options().items():
246
 
        yield "--" + option_name
247
 
        short_name = option.short_name()
248
 
        if short_name:
249
 
            yield "-" + short_name
250
 
 
251
 
 
252
 
def iter_file_completions(arg, only_dirs = False):
253
 
    """Generate an iterator that iterates through filename completions.
254
 
 
255
 
    :param arg: The filename fragment to match
256
 
    :type arg: str
257
 
    :param only_dirs: If true, match only directories
258
 
    :type only_dirs: bool
259
 
    """
260
 
    cwd = os.getcwd()
261
 
    if cwd != "/":
262
 
        extras = [".", ".."]
263
 
    else:
264
 
        extras = []
265
 
    (dir, file) = os.path.split(arg)
266
 
    if dir != "":
267
 
        listingdir = os.path.expanduser(dir)
268
 
    else:
269
 
        listingdir = cwd
270
 
    for file in chain(os.listdir(listingdir), extras):
271
 
        if dir != "":
272
 
            userfile = dir+'/'+file
273
 
        else:
274
 
            userfile = file
275
 
        if userfile.startswith(arg):
276
 
            if os.path.isdir(listingdir+'/'+file):
277
 
                userfile+='/'
278
 
                yield userfile
279
 
            elif not only_dirs:
280
 
                yield userfile + ' '
281
 
 
282
 
 
283
 
def iter_dir_completions(arg):
284
 
    """Generate an iterator that iterates through directory name completions.
285
 
 
286
 
    :param arg: The directory name fragment to match
287
 
    :type arg: str
288
 
    """
289
 
    return iter_file_completions(arg, True)
290
 
 
291
 
 
292
 
def iter_command_names(hidden=False):
293
 
    for real_cmd_name in all_command_names():
294
 
        cmd_obj = get_cmd_object(real_cmd_name)
295
 
        if not hidden and cmd_obj.hidden:
296
 
            continue
297
 
        for name in [real_cmd_name] + cmd_obj.aliases:
298
 
            # Don't complete on aliases that are prefixes of the canonical name
299
 
            if name == real_cmd_name or not real_cmd_name.startswith(name):
300
 
                yield name
301
 
 
302
 
 
303
 
def iter_executables(path):
304
 
    dirname, partial = os.path.split(path)
305
 
    for filename in os.listdir(dirname):
306
 
        if not filename.startswith(partial):
307
 
            continue
308
 
        fullpath = os.path.join(dirname, filename)
309
 
        mode=os.lstat(fullpath)[stat.ST_MODE]
310
 
        if stat.S_ISREG(mode) and 0111 & mode:
311
 
            yield fullpath + ' '
312
 
 
313
 
 
314
 
def filter_completions(iter, arg):
315
 
    return (c for c in iter if c.startswith(arg))
316
 
 
317
 
 
318
 
def iter_munged_completions(iter, arg, text):
319
 
    for completion in iter:
320
 
        completion = str(completion)
321
 
        if completion.startswith(arg):
322
 
            yield completion[len(arg)-len(text):]+" "
323
 
 
324
 
 
325
 
def too_complicated(line):
326
 
    for char in '|<>*?':
327
 
        if char in line:
328
 
            return True
329
 
    return False