~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shell.py

  • Committer: Aaron Bentley
  • Date: 2012-01-20 02:05:50 UTC
  • Revision ID: aaron@aaronbentley.com-20120120020550-itufdyymm5j1t1w2
RemoveĀ unusedĀ imports

Show diffs side-by-side

added added

removed removed

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