~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shell.py

  • Committer: Aaron Bentley
  • Date: 2005-11-10 20:15:27 UTC
  • Revision ID: aaron.bentley@utoronto.ca-20051110201527-8caa84ae529a1871
Added Daniel Silverstone to credits

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2004, 2005 Aaron Bentley
2
 
# <aaron@aaronbentley.com>
 
2
# <aaron.bentley@utoronto.ca>
3
3
#
4
4
#    This program is free software; you can redistribute it and/or modify
5
5
#    it under the terms of the GNU General Public License as published by
14
14
#    You should have received a copy of the GNU General Public License
15
15
#    along with this program; if not, write to the Free Software
16
16
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
 
 
18
17
import cmd
19
 
from itertools import chain
 
18
import sys
20
19
import os
 
20
import terminal
21
21
import readline
22
 
import shlex
23
 
import stat
24
22
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
 
23
from itertools import chain
31
24
from bzrlib.errors import BzrError
32
 
from bzrlib.workingtree import WorkingTree
33
 
 
34
 
import terminal
35
 
 
 
25
from bzrlib.commands import get_cmd_object, get_all_cmds
36
26
 
37
27
SHELL_BLACKLIST = set(['rm', 'ls'])
38
28
COMPLETION_BLACKLIST = set(['shell'])
39
29
 
40
 
 
41
30
class BlackListedCommand(BzrError):
42
31
    def __init__(self, command):
43
32
        BzrError.__init__(self, "The command %s is blacklisted for shell use" %
44
33
                          command)
45
34
 
46
 
 
47
35
class CompletionContext(object):
48
36
    def __init__(self, text, command=None, prev_opt=None, arg_pos=None):
49
37
        self.text = text
52
40
        self.arg_pos = None
53
41
 
54
42
    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
 
43
        if not command:
 
44
            iter = (c for c in iter_command_names() if
 
45
                    c not in COMPLETION_BLACKLIST)
 
46
            try:
 
47
                iter = list(filter_completions(iter, text))
 
48
            except Exception, e:
 
49
                print e, type(e)
 
50
 
86
51
 
87
52
 
88
53
class PromptCmd(cmd.Cmd):
89
 
 
90
54
    def __init__(self):
91
55
        cmd.Cmd.__init__(self)
92
56
        self.prompt = "bzr> "
93
57
        try:
94
 
            self.tree = WorkingTree.open_containing('.')[0]
 
58
            self.tree = arch.tree_root(".")
95
59
        except:
96
60
            self.tree = None
97
61
        self.set_title()
98
62
        self.set_prompt()
99
63
        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)
 
64
        self.history_file = os.path.expanduser("~/.bazaar/shell-history")
 
65
        readline.set_completer_delims(string.whitespace)
104
66
        if os.access(self.history_file, os.R_OK) and \
105
67
            os.path.isfile(self.history_file):
106
68
            readline.read_history_file(self.history_file)
127
89
    def set_prompt(self):
128
90
        if self.tree is not None:
129
91
            try:
130
 
                prompt_data = (self.tree.branch.nick, self.tree.branch.revno(),
131
 
                               self.tree.relpath('.'))
132
 
                prompt = " %s:%d/%s" % prompt_data
 
92
                prompt = pylon.alias_or_version(self.tree.tree_version, 
 
93
                                                self.tree, 
 
94
                                                full=False)
 
95
                if prompt is not None:
 
96
                    prompt = " " + prompt +":"+ pylon.tree_cwd(self.tree)
133
97
            except:
134
98
                prompt = ""
135
99
        else:
138
102
 
139
103
    def set_title(self, command=None):
140
104
        try:
141
 
            b = Branch.open_containing('.')[0]
142
 
            version = "%s:%d" % (b.nick, b.revno())
 
105
            version = pylon.alias_or_version(self.tree.tree_version, self.tree, 
 
106
                                             full=False)
143
107
        except:
144
108
            version = "[no version]"
145
109
        if command is None:
161
125
        except Exception, e:
162
126
            print e
163
127
        try:
164
 
            self.tree = WorkingTree.open_containing(".")[0]
 
128
            self.tree = arch.tree_root(".")
165
129
        except:
166
130
            self.tree = None
167
131
 
169
133
        self.default("help "+line)
170
134
 
171
135
    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
 
 
 
136
        args = line.split()
182
137
        commandname = args.pop(0)
183
138
        for char in ('|', '<', '>'):
184
139
            commandname = commandname.split(char)[0]
192
147
            return os.system(line)
193
148
 
194
149
        try:
195
 
            is_qbzr = cmd_obj.__module__.startswith('bzrlib.plugins.qbzr.')
196
 
            if too_complicated(line) or is_qbzr:
 
150
            if too_complicated(line):
197
151
                return os.system("bzr "+line)
198
152
            else:
199
 
                return (cmd_obj.run_argv_aliases(args, alias_args) or 0)
 
153
                return (cmd_obj.run_argv(args) or 0)
200
154
        except BzrError, e:
201
155
            print e
202
156
        except KeyboardInterrupt, e:
207
161
 
208
162
 
209
163
    def completenames(self, text, line, begidx, endidx):
210
 
        return CompletionContext(text).get_completions()
 
164
        from bzrlib.trace import mutter
 
165
        completions = []
 
166
        iter = (c for c in iter_command_names() if
 
167
                c not in COMPLETION_BLACKLIST)
 
168
        try:
 
169
            if len(line) > 0:
 
170
                arg = line.split()[-1]
 
171
            else:
 
172
                arg = ""
 
173
            iter = filter_completions(iter, arg)
 
174
        except Exception, e:
 
175
            print e, type(e)
 
176
        return list(iter)
211
177
 
212
178
    def completedefault(self, text, line, begidx, endidx):
213
179
        """Perform completion for native commands.
214
 
 
 
180
        
215
181
        :param text: The text to complete
216
182
        :type text: str
217
183
        :param line: The entire line to complete
223
189
        """
224
190
        (cmd, args, foo) = self.parseline(line)
225
191
        if cmd == "bzr":
226
 
            cmd = None
227
 
        return CompletionContext(text, command=cmd).get_completions()
228
 
 
229
 
 
230
 
def run_shell(directory=None):
 
192
            try:
 
193
                return self.completenames(text, line, begidx, endidx)
 
194
            except Exception, e:
 
195
                print e
 
196
        try:
 
197
            command_obj = get_cmd_object(cmd)
 
198
        except BzrError:
 
199
            command_obj = None
 
200
        try:
 
201
            if command_obj is not None:
 
202
                opts = list(iter_opt_completions(command_obj))
 
203
                files = list(iter_file_completions(text))
 
204
                return list(filter_completions(opts+files, text))
 
205
            elif cmd == "cd":
 
206
                if len(args) > 0:
 
207
                    arg = args.split()[-1]
 
208
                else:
 
209
                    arg = ""
 
210
                iter = iter_dir_completions(text)
 
211
                iter = filter_completions(iter, text)
 
212
                return list(iter)
 
213
            elif len(args)>0:
 
214
                arg = args.split()[-1]
 
215
                iter = iter_file_completions(arg)
 
216
                return list(iter_munged_completions(iter, arg, text))
 
217
            else:
 
218
                return self.completenames(text, line, begidx, endidx)
 
219
        except Exception, e:
 
220
            print e
 
221
 
 
222
def run_shell():
231
223
    try:
232
 
        if not directory is None:
233
 
            os.chdir(directory)
234
224
        prompt = PromptCmd()
235
 
        while True:
236
 
            try:
237
 
                try:
238
 
                    prompt.cmdloop()
239
 
                except KeyboardInterrupt:
240
 
                    print
241
 
            finally:
242
 
                prompt.write_history()
 
225
        try:
 
226
            prompt.cmdloop()
 
227
        finally:
 
228
            prompt.write_history()
243
229
    except StopIteration:
244
230
        pass
245
231
 
246
 
 
247
232
def iter_opt_completions(command_obj):
248
233
    for option_name, option in command_obj.options().items():
249
234
        yield "--" + option_name
251
236
        if short_name:
252
237
            yield "-" + short_name
253
238
 
254
 
 
255
239
def iter_file_completions(arg, only_dirs = False):
256
240
    """Generate an iterator that iterates through filename completions.
257
241
 
280
264
                userfile+='/'
281
265
                yield userfile
282
266
            elif not only_dirs:
283
 
                yield userfile + ' '
 
267
                yield userfile
284
268
 
285
269
 
286
270
def iter_dir_completions(arg):
291
275
    """
292
276
    return iter_file_completions(arg, True)
293
277
 
294
 
 
295
278
def iter_command_names(hidden=False):
296
 
    for real_cmd_name in all_command_names():
297
 
        cmd_obj = get_cmd_object(real_cmd_name)
298
 
        if not hidden and cmd_obj.hidden:
 
279
    for real_cmd_name, cmd_class in get_all_cmds():
 
280
        if not hidden and cmd_class.hidden:
299
281
            continue
300
 
        for name in [real_cmd_name] + cmd_obj.aliases:
 
282
        for name in [real_cmd_name] + cmd_class.aliases:
301
283
            # Don't complete on aliases that are prefixes of the canonical name
302
284
            if name == real_cmd_name or not real_cmd_name.startswith(name):
303
285
                yield name
304
286
 
305
 
 
306
 
def iter_executables(path):
307
 
    dirname, partial = os.path.split(path)
308
 
    for filename in os.listdir(dirname):
309
 
        if not filename.startswith(partial):
310
 
            continue
311
 
        fullpath = os.path.join(dirname, filename)
312
 
        mode=os.lstat(fullpath)[stat.ST_MODE]
313
 
        if stat.S_ISREG(mode) and 0111 & mode:
314
 
            yield fullpath + ' '
315
 
 
316
 
 
317
287
def filter_completions(iter, arg):
318
288
    return (c for c in iter if c.startswith(arg))
319
289
 
320
 
 
321
290
def iter_munged_completions(iter, arg, text):
322
291
    for completion in iter:
323
292
        completion = str(completion)
324
293
        if completion.startswith(arg):
325
294
            yield completion[len(arg)-len(text):]+" "
326
295
 
327
 
 
328
296
def too_complicated(line):
329
 
    for char in '|<>*?':
 
297
    for char in '|<>"\"*?':
330
298
        if char in line:
331
299
            return True
332
300
    return False