~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shell.py

  • Committer: Aaron Bentley
  • Date: 2011-04-27 00:06:36 UTC
  • Revision ID: aaron@aaronbentley.com-20110427000636-od4xzn1u465mqzfm
Fix long line.

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
 
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
36
from bzrlib.errors import BzrError
24
 
from bzrlib.commands import get_cmd_object
 
37
from bzrlib.workingtree import WorkingTree
 
38
 
 
39
import terminal
 
40
 
 
41
 
 
42
SHELL_BLACKLIST = set(['rm', 'ls'])
 
43
COMPLETION_BLACKLIST = set(['shell'])
 
44
 
 
45
 
 
46
class BlackListedCommand(BzrError):
 
47
    def __init__(self, command):
 
48
        BzrError.__init__(self, "The command %s is blacklisted for shell use" %
 
49
                          command)
 
50
 
 
51
 
 
52
class CompletionContext(object):
 
53
    def __init__(self, text, command=None, prev_opt=None, arg_pos=None):
 
54
        self.text = text
 
55
        self.command = command
 
56
        self.prev_opt = prev_opt
 
57
        self.arg_pos = None
 
58
 
 
59
    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
 
91
 
25
92
 
26
93
class PromptCmd(cmd.Cmd):
 
94
 
27
95
    def __init__(self):
28
96
        cmd.Cmd.__init__(self)
29
97
        self.prompt = "bzr> "
30
98
        try:
31
 
            self.tree = arch.tree_root(".")
 
99
            self.tree = WorkingTree.open_containing('.')[0]
32
100
        except:
33
101
            self.tree = None
34
102
        self.set_title()
35
103
        self.set_prompt()
36
104
        self.identchars += '-'
37
 
        self.history_file = os.path.expanduser("~/.bazaar/shell-history")
38
 
        readline.set_completer_delims(string.whitespace)
39
 
        if os.access(self.history_file, os.R_OK) and \
40
 
            os.path.isfile(self.history_file):
41
 
            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)
42
113
        self.cwd = os.getcwd()
43
114
 
44
115
    def write_history(self):
45
 
        readline.write_history_file(self.history_file)
 
116
        if _has_readline:
 
117
            readline.write_history_file(self.history_file)
46
118
 
47
119
    def do_quit(self, args):
48
120
        self.write_history()
49
 
        sys.exit(0)
 
121
        raise StopIteration
50
122
 
51
123
    def do_exit(self, args):
52
124
        self.do_quit(args)
62
134
    def set_prompt(self):
63
135
        if self.tree is not None:
64
136
            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)
 
137
                prompt_data = (self.tree.branch.nick, self.tree.branch.revno(),
 
138
                               self.tree.relpath('.'))
 
139
                prompt = " %s:%d/%s" % prompt_data
70
140
            except:
71
141
                prompt = ""
72
142
        else:
75
145
 
76
146
    def set_title(self, command=None):
77
147
        try:
78
 
            version = pylon.alias_or_version(self.tree.tree_version, self.tree, 
79
 
                                             full=False)
 
148
            b = Branch.open_containing('.')[0]
 
149
            version = "%s:%d" % (b.nick, b.revno())
80
150
        except:
81
151
            version = "[no version]"
82
152
        if command is None:
98
168
        except Exception, e:
99
169
            print e
100
170
        try:
101
 
            self.tree = arch.tree_root(".")
 
171
            self.tree = WorkingTree.open_containing(".")[0]
102
172
        except:
103
173
            self.tree = None
104
174
 
105
175
    def do_help(self, line):
106
 
        Help()(line)
 
176
        self.default("help "+line)
107
177
 
108
178
    def default(self, line):
109
 
        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
 
110
189
        commandname = args.pop(0)
 
190
        for char in ('|', '<', '>'):
 
191
            commandname = commandname.split(char)[0]
 
192
        if commandname[-1] in ('|', '<', '>'):
 
193
            commandname = commandname[:-1]
111
194
        try:
 
195
            if commandname in SHELL_BLACKLIST:
 
196
                raise BlackListedCommand(commandname)
112
197
            cmd_obj = get_cmd_object(commandname)
113
 
        except BzrError:
 
198
        except (BlackListedCommand, BzrError):
114
199
            return os.system(line)
115
200
 
116
 
 
117
201
        try:
118
 
            return (cmd_obj.run_argv(args) or 0)
 
202
            is_qbzr = cmd_obj.__module__.startswith('bzrlib.plugins.qbzr.')
 
203
            if too_complicated(line) or is_qbzr:
 
204
                return os.system("bzr "+line)
 
205
            else:
 
206
                return (cmd_obj.run_argv_aliases(args, alias_args) or 0)
119
207
        except BzrError, e:
 
208
            trace.log_exception_quietly()
120
209
            print e
121
210
        except KeyboardInterrupt, e:
122
211
            print "Interrupted"
123
212
        except Exception, e:
124
 
#            print "Unhandled error:\n%s" % errors.exception_str(e)
 
213
            trace.log_exception_quietly()
125
214
            print "Unhandled error:\n%s" % (e)
126
215
 
127
216
 
128
217
    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)
 
218
        return CompletionContext(text).get_completions()
140
219
 
141
220
    def completedefault(self, text, line, begidx, endidx):
142
221
        """Perform completion for native commands.
143
 
        
 
222
 
144
223
        :param text: The text to complete
145
224
        :type text: str
146
225
        :param line: The entire line to complete
150
229
        :param endidx: The end of the text in the line
151
230
        :type endidx: int
152
231
        """
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()
 
232
        (cmd, args, foo) = self.parseline(line)
 
233
        if cmd == "bzr":
 
234
            cmd = None
 
235
        return CompletionContext(text, command=cmd).get_completions()
 
236
 
 
237
 
 
238
def run_shell(directory=None):
192
239
    try:
193
 
        prompt.cmdloop()
194
 
    finally:
195
 
        prompt.write_history()
 
240
        if not directory is None:
 
241
            os.chdir(directory)
 
242
        prompt = PromptCmd()
 
243
        while True:
 
244
            try:
 
245
                try:
 
246
                    prompt.cmdloop()
 
247
                except KeyboardInterrupt:
 
248
                    print
 
249
            finally:
 
250
                prompt.write_history()
 
251
    except StopIteration:
 
252
        pass
 
253
 
 
254
 
 
255
def iter_opt_completions(command_obj):
 
256
    for option_name, option in command_obj.options().items():
 
257
        yield "--" + option_name
 
258
        short_name = option.short_name()
 
259
        if short_name:
 
260
            yield "-" + short_name
 
261
 
196
262
 
197
263
def iter_file_completions(arg, only_dirs = False):
198
264
    """Generate an iterator that iterates through filename completions.
212
278
        listingdir = os.path.expanduser(dir)
213
279
    else:
214
280
        listingdir = cwd
215
 
    for file in iter_combine([os.listdir(listingdir), extras]):
 
281
    for file in chain(os.listdir(listingdir), extras):
216
282
        if dir != "":
217
283
            userfile = dir+'/'+file
218
284
        else:
222
288
                userfile+='/'
223
289
                yield userfile
224
290
            elif not only_dirs:
225
 
                yield userfile
 
291
                yield userfile + ' '
226
292
 
227
293
 
228
294
def iter_dir_completions(arg):
232
298
    :type arg: str
233
299
    """
234
300
    return iter_file_completions(arg, True)
 
301
 
 
302
 
 
303
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:
 
307
            continue
 
308
        for name in [real_cmd_name] + cmd_obj.aliases:
 
309
            # Don't complete on aliases that are prefixes of the canonical name
 
310
            if name == real_cmd_name or not real_cmd_name.startswith(name):
 
311
                yield name
 
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
 
 
325
def filter_completions(iter, arg):
 
326
    return (c for c in iter if c.startswith(arg))
 
327
 
 
328
 
 
329
def iter_munged_completions(iter, arg, text):
 
330
    for completion in iter:
 
331
        completion = str(completion)
 
332
        if completion.startswith(arg):
 
333
            yield completion[len(arg)-len(text):]+" "
 
334
 
 
335
 
 
336
def too_complicated(line):
 
337
    for char in '|<>*?':
 
338
        if char in line:
 
339
            return True
 
340
    return False