~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shell.py

  • Committer: Aaron Bentley
  • Date: 2005-11-10 21:04:19 UTC
  • Revision ID: aaron.bentley@utoronto.ca-20051110210419-a402638d94693825
Handled whitespace branch names better

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