~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shell.py

  • Committer: Gordon Tyler
  • Date: 2009-11-16 20:47:28 UTC
  • mto: This revision was merged to the branch mainline in revision 737.
  • Revision ID: gordon@doxxx.net-20091116204728-0gotohw3tlpjhrwy
Added --directory option to shell command.

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
21
import readline
 
22
import shlex
 
23
import stat
22
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
23
31
from bzrlib.errors import BzrError
24
 
from bzrlib.commands import get_cmd_object
 
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
 
25
87
 
26
88
class PromptCmd(cmd.Cmd):
 
89
 
27
90
    def __init__(self):
28
91
        cmd.Cmd.__init__(self)
29
92
        self.prompt = "bzr> "
30
93
        try:
31
 
            self.tree = arch.tree_root(".")
 
94
            self.tree = WorkingTree.open_containing('.')[0]
32
95
        except:
33
96
            self.tree = None
34
97
        self.set_title()
35
98
        self.set_prompt()
36
99
        self.identchars += '-'
37
 
        self.history_file = os.path.expanduser("~/.bazaar/shell-history")
38
 
        readline.set_completer_delims(string.whitespace)
 
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)
39
104
        if os.access(self.history_file, os.R_OK) and \
40
105
            os.path.isfile(self.history_file):
41
106
            readline.read_history_file(self.history_file)
46
111
 
47
112
    def do_quit(self, args):
48
113
        self.write_history()
49
 
        sys.exit(0)
 
114
        raise StopIteration
50
115
 
51
116
    def do_exit(self, args):
52
117
        self.do_quit(args)
62
127
    def set_prompt(self):
63
128
        if self.tree is not None:
64
129
            try:
65
 
                prompt = pylon.alias_or_version(self.tree.tree_version, 
66
 
                                                self.tree, 
67
 
                                                full=False)
68
 
                if prompt is not None:
69
 
                    prompt = " " + prompt +":"+ pylon.tree_cwd(self.tree)
 
130
                prompt_data = (self.tree.branch.nick, self.tree.branch.revno(),
 
131
                               self.tree.relpath('.'))
 
132
                prompt = " %s:%d/%s" % prompt_data
70
133
            except:
71
134
                prompt = ""
72
135
        else:
75
138
 
76
139
    def set_title(self, command=None):
77
140
        try:
78
 
            version = pylon.alias_or_version(self.tree.tree_version, self.tree, 
79
 
                                             full=False)
 
141
            b = Branch.open_containing('.')[0]
 
142
            version = "%s:%d" % (b.nick, b.revno())
80
143
        except:
81
144
            version = "[no version]"
82
145
        if command is None:
98
161
        except Exception, e:
99
162
            print e
100
163
        try:
101
 
            self.tree = arch.tree_root(".")
 
164
            self.tree = WorkingTree.open_containing(".")[0]
102
165
        except:
103
166
            self.tree = None
104
167
 
105
168
    def do_help(self, line):
106
 
        Help()(line)
 
169
        self.default("help "+line)
107
170
 
108
171
    def default(self, line):
109
 
        args = line.split()
 
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
 
110
182
        commandname = args.pop(0)
 
183
        for char in ('|', '<', '>'):
 
184
            commandname = commandname.split(char)[0]
 
185
        if commandname[-1] in ('|', '<', '>'):
 
186
            commandname = commandname[:-1]
111
187
        try:
 
188
            if commandname in SHELL_BLACKLIST:
 
189
                raise BlackListedCommand(commandname)
112
190
            cmd_obj = get_cmd_object(commandname)
113
 
        except BzrError:
 
191
        except (BlackListedCommand, BzrError):
114
192
            return os.system(line)
115
193
 
116
 
 
117
194
        try:
118
 
            return (cmd_obj.run_argv(args) or 0)
 
195
            is_qbzr = cmd_obj.__module__.startswith('bzrlib.plugins.qbzr.')
 
196
            if too_complicated(line) or is_qbzr:
 
197
                return os.system("bzr "+line)
 
198
            else:
 
199
                return (cmd_obj.run_argv_aliases(args, alias_args) or 0)
119
200
        except BzrError, e:
120
201
            print e
121
202
        except KeyboardInterrupt, e:
126
207
 
127
208
 
128
209
    def completenames(self, text, line, begidx, endidx):
129
 
        completions = []
130
 
        iter = iter_command_names(self.fake_aba)
131
 
        try:
132
 
            if len(line) > 0:
133
 
                arg = line.split()[-1]
134
 
            else:
135
 
                arg = ""
136
 
            iter = cmdutil.iter_munged_completions(iter, arg, text)
137
 
        except Exception, e:
138
 
            print e
139
 
        return list(iter)
 
210
        return CompletionContext(text).get_completions()
140
211
 
141
212
    def completedefault(self, text, line, begidx, endidx):
142
213
        """Perform completion for native commands.
143
 
        
 
214
 
144
215
        :param text: The text to complete
145
216
        :type text: str
146
217
        :param line: The entire line to complete
150
221
        :param endidx: The end of the text in the line
151
222
        :type endidx: int
152
223
        """
153
 
        try:
154
 
            (cmd, args, foo) = self.parseline(line)
155
 
            command_obj=find_command(cmd)
156
 
            if command_obj is not None:
157
 
                return command_obj.complete(args.split(), text)
158
 
            elif not self.fake_aba.is_command(cmd) and \
159
 
                cmdutil.is_tla_command(cmd):
160
 
                iter = cmdutil.iter_supported_switches(cmd)
161
 
                if len(args) > 0:
162
 
                    arg = args.split()[-1]
163
 
                else:
164
 
                    arg = ""
165
 
                if arg.startswith("-"):
166
 
                    return list(cmdutil.iter_munged_completions(iter, arg, 
167
 
                                                                text))
168
 
                else:
169
 
                    return list(cmdutil.iter_munged_completions(
170
 
                        cmdutil.iter_file_completions(arg), arg, text))
171
 
 
172
 
 
173
 
            elif cmd == "cd":
174
 
                if len(args) > 0:
175
 
                    arg = args.split()[-1]
176
 
                else:
177
 
                    arg = ""
178
 
                iter = cmdutil.iter_dir_completions(arg)
179
 
                iter = cmdutil.iter_munged_completions(iter, arg, text)
180
 
                return list(iter)
181
 
            elif len(args)>0:
182
 
                arg = args.split()[-1]
183
 
                iter = cmdutil.iter_file_completions(arg)
184
 
                return list(cmdutil.iter_munged_completions(iter, arg, text))
185
 
            else:
186
 
                return self.completenames(text, line, begidx, endidx)
187
 
        except Exception, e:
188
 
            print e
189
 
 
190
 
def run_shell():
191
 
    prompt = PromptCmd()
 
224
        (cmd, args, foo) = self.parseline(line)
 
225
        if cmd == "bzr":
 
226
            cmd = None
 
227
        return CompletionContext(text, command=cmd).get_completions()
 
228
 
 
229
 
 
230
def run_shell(directory=None):
192
231
    try:
193
 
        prompt.cmdloop()
194
 
    finally:
195
 
        prompt.write_history()
 
232
        if not directory is None:
 
233
            os.chdir(directory)
 
234
        prompt = PromptCmd()
 
235
        while True:
 
236
            try:
 
237
                try:
 
238
                    prompt.cmdloop()
 
239
                except KeyboardInterrupt:
 
240
                    print
 
241
            finally:
 
242
                prompt.write_history()
 
243
    except StopIteration:
 
244
        pass
 
245
 
 
246
 
 
247
def iter_opt_completions(command_obj):
 
248
    for option_name, option in command_obj.options().items():
 
249
        yield "--" + option_name
 
250
        short_name = option.short_name()
 
251
        if short_name:
 
252
            yield "-" + short_name
 
253
 
196
254
 
197
255
def iter_file_completions(arg, only_dirs = False):
198
256
    """Generate an iterator that iterates through filename completions.
212
270
        listingdir = os.path.expanduser(dir)
213
271
    else:
214
272
        listingdir = cwd
215
 
    for file in iter_combine([os.listdir(listingdir), extras]):
 
273
    for file in chain(os.listdir(listingdir), extras):
216
274
        if dir != "":
217
275
            userfile = dir+'/'+file
218
276
        else:
222
280
                userfile+='/'
223
281
                yield userfile
224
282
            elif not only_dirs:
225
 
                yield userfile
 
283
                yield userfile + ' '
226
284
 
227
285
 
228
286
def iter_dir_completions(arg):
232
290
    :type arg: str
233
291
    """
234
292
    return iter_file_completions(arg, True)
 
293
 
 
294
 
 
295
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:
 
299
            continue
 
300
        for name in [real_cmd_name] + cmd_obj.aliases:
 
301
            # Don't complete on aliases that are prefixes of the canonical name
 
302
            if name == real_cmd_name or not real_cmd_name.startswith(name):
 
303
                yield name
 
304
 
 
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
def filter_completions(iter, arg):
 
318
    return (c for c in iter if c.startswith(arg))
 
319
 
 
320
 
 
321
def iter_munged_completions(iter, arg, text):
 
322
    for completion in iter:
 
323
        completion = str(completion)
 
324
        if completion.startswith(arg):
 
325
            yield completion[len(arg)-len(text):]+" "
 
326
 
 
327
 
 
328
def too_complicated(line):
 
329
    for char in '|<>*?':
 
330
        if char in line:
 
331
            return True
 
332
    return False