~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shell.py

  • Committer: Aaron Bentley
  • Date: 2005-10-27 04:45:06 UTC
  • Revision ID: aaron.bentley@utoronto.ca-20051027044506-45f616c07537a1da
Got the shell basics working properly

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
36
23
from bzrlib.errors import BzrError
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
 
 
 
24
from bzrlib.commands import get_cmd_object
92
25
 
93
26
class PromptCmd(cmd.Cmd):
94
 
 
95
27
    def __init__(self):
96
28
        cmd.Cmd.__init__(self)
97
29
        self.prompt = "bzr> "
98
30
        try:
99
 
            self.tree = WorkingTree.open_containing('.')[0]
 
31
            self.tree = arch.tree_root(".")
100
32
        except:
101
33
            self.tree = None
102
34
        self.set_title()
103
35
        self.set_prompt()
104
36
        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)
 
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)
113
42
        self.cwd = os.getcwd()
114
43
 
115
44
    def write_history(self):
116
 
        if _has_readline:
117
 
            readline.write_history_file(self.history_file)
 
45
        readline.write_history_file(self.history_file)
118
46
 
119
47
    def do_quit(self, args):
120
48
        self.write_history()
121
 
        raise StopIteration
 
49
        sys.exit(0)
122
50
 
123
51
    def do_exit(self, args):
124
52
        self.do_quit(args)
134
62
    def set_prompt(self):
135
63
        if self.tree is not None:
136
64
            try:
137
 
                prompt_data = (self.tree.branch.nick, self.tree.branch.revno(),
138
 
                               self.tree.relpath('.'))
139
 
                prompt = " %s:%d/%s" % prompt_data
 
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)
140
70
            except:
141
71
                prompt = ""
142
72
        else:
145
75
 
146
76
    def set_title(self, command=None):
147
77
        try:
148
 
            b = Branch.open_containing('.')[0]
149
 
            version = "%s:%d" % (b.nick, b.revno())
 
78
            version = pylon.alias_or_version(self.tree.tree_version, self.tree, 
 
79
                                             full=False)
150
80
        except:
151
81
            version = "[no version]"
152
82
        if command is None:
168
98
        except Exception, e:
169
99
            print e
170
100
        try:
171
 
            self.tree = WorkingTree.open_containing(".")[0]
 
101
            self.tree = arch.tree_root(".")
172
102
        except:
173
103
            self.tree = None
174
104
 
175
105
    def do_help(self, line):
176
 
        self.default("help "+line)
 
106
        Help()(line)
177
107
 
178
108
    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
 
 
 
109
        args = line.split()
189
110
        commandname = args.pop(0)
190
 
        for char in ('|', '<', '>'):
191
 
            commandname = commandname.split(char)[0]
192
 
        if commandname[-1] in ('|', '<', '>'):
193
 
            commandname = commandname[:-1]
194
111
        try:
195
 
            if commandname in SHELL_BLACKLIST:
196
 
                raise BlackListedCommand(commandname)
197
112
            cmd_obj = get_cmd_object(commandname)
198
 
        except (BlackListedCommand, BzrError):
 
113
        except BzrError:
199
114
            return os.system(line)
200
115
 
 
116
 
201
117
        try:
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)
 
118
            return (cmd_obj.run_argv(args) or 0)
207
119
        except BzrError, e:
208
 
            trace.log_exception_quietly()
209
120
            print e
210
121
        except KeyboardInterrupt, e:
211
122
            print "Interrupted"
212
123
        except Exception, e:
213
 
            trace.log_exception_quietly()
 
124
#            print "Unhandled error:\n%s" % errors.exception_str(e)
214
125
            print "Unhandled error:\n%s" % (e)
215
126
 
216
127
 
217
128
    def completenames(self, text, line, begidx, endidx):
218
 
        return CompletionContext(text).get_completions()
 
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)
219
140
 
220
141
    def completedefault(self, text, line, begidx, endidx):
221
142
        """Perform completion for native commands.
222
 
 
 
143
        
223
144
        :param text: The text to complete
224
145
        :type text: str
225
146
        :param line: The entire line to complete
229
150
        :param endidx: The end of the text in the line
230
151
        :type endidx: int
231
152
        """
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):
 
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()
239
192
    try:
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
 
 
 
193
        prompt.cmdloop()
 
194
    finally:
 
195
        prompt.write_history()
262
196
 
263
197
def iter_file_completions(arg, only_dirs = False):
264
198
    """Generate an iterator that iterates through filename completions.
278
212
        listingdir = os.path.expanduser(dir)
279
213
    else:
280
214
        listingdir = cwd
281
 
    for file in chain(os.listdir(listingdir), extras):
 
215
    for file in iter_combine([os.listdir(listingdir), extras]):
282
216
        if dir != "":
283
217
            userfile = dir+'/'+file
284
218
        else:
288
222
                userfile+='/'
289
223
                yield userfile
290
224
            elif not only_dirs:
291
 
                yield userfile + ' '
 
225
                yield userfile
292
226
 
293
227
 
294
228
def iter_dir_completions(arg):
298
232
    :type arg: str
299
233
    """
300
234
    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