2 # For now, this completely relies on the assumption that the current
3 # encoding (LC_CTYPE, etc.) is ASCII compatible, and that it returns
4 # the exact same bytes from a decode/encode round-trip (or the reverse
7 from __future__ import absolute_import, print_function
8 import os, fnmatch, stat, sys, traceback
10 from bup import _helpers, options, git, shquote, ls, vfs
11 from bup.compat import argv_bytes
12 from bup.helpers import chunkyreader, log, saved_errors
13 from bup.io import byte_stream, path_msg
14 from bup.repo import LocalRepo
20 class CommandError(Exception):
23 class OptionError(Exception):
27 def do_ls(repo, pwd, args, out):
28 pwd_str = b'/'.join(name for name, item in pwd) or b'/'
30 opt = ls.opts_from_cmdline(args, onabort=OptionError, pwd=pwd_str)
31 except OptionError as e:
33 return ls.within_repo(repo, opt, out, pwd_str)
36 def write_to_file(inf, outf):
37 for blob in chunkyreader(inf):
41 def _completer_get_subs(repo, line):
42 (qtype, lastword) = shquote.unfinished_word(line)
43 dir, name = os.path.split(lastword)
44 dir_path = vfs.resolve(repo, dir or b'/')
45 _, dir_item = dir_path[-1]
49 subs = tuple(dir_path + (entry,)
50 for entry in vfs.contents(repo, dir_item)
51 if (entry[0] != b'.' and entry[0].startswith(name)))
52 return qtype, lastword, subs
57 def attempt_completion(text, start, end):
58 global _attempt_start, _attempt_end
59 _attempt_start = start
64 def enter_completion(text, iteration):
70 line = _helpers.get_line_buffer()[:_attempt_end]
71 if _last_line != line:
72 _last_res = _completer_get_subs(repo, line)
74 qtype, lastword, subs = _last_res
75 if iteration < len(subs):
76 path = subs[iteration]
77 leaf_name, leaf_item = path[-1]
78 res = vfs.try_resolve(repo, leaf_name, parent=path[:-1])
79 leaf_name, leaf_item = res[-1]
80 fullname = os.path.join(*(name for name, item in res))
81 if stat.S_ISDIR(vfs.item_mode(leaf_item)):
82 ret = shquote.what_to_add(qtype, lastword, fullname + b'/',
85 ret = shquote.what_to_add(qtype, lastword, fullname,
86 terminate=True) + b' '
88 except Exception as e:
90 _, _, tb = sys.exc_info()
91 traceback.print_tb(tb)
92 log('\nError in completion: %s\n' % e)
101 def inputiter(f, pwd, out):
102 if os.isatty(f.fileno()):
104 prompt = b'bup %s> ' % (b'/'.join(name for name, item in pwd) or b'/', )
105 if hasattr(_helpers, 'readline'):
107 yield _helpers.readline(prompt)
109 print() # Clear the line for the terminal's next prompt
114 read_line = f.readline()
124 """Return a path_msg for the resolved path res."""
125 return path_msg(b'/'.join(name for name, item in res))
127 def present_interface(stdin, out, extra, repo):
128 pwd = vfs.resolve(repo, b'/')
131 lines = (argv_bytes(arg) for arg in extra)
133 if hasattr(_helpers, 'readline'):
134 _helpers.set_completer_word_break_characters(b' \t\n\r/')
135 _helpers.set_attempted_completion_function(attempt_completion)
136 _helpers.set_completion_entry_function(enter_completion)
137 if sys.platform.startswith('darwin'):
138 # MacOS uses a slightly incompatible clone of libreadline
139 _helpers.parse_and_bind(b'bind ^I rl_complete')
140 _helpers.parse_and_bind(b'tab: complete')
141 lines = inputiter(stdin, pwd, out)
146 words = [word for (wordstart,word) in shquote.quotesplit(line)]
147 cmd = words[0].lower()
148 #log('execute: %r %r\n' % (cmd, parm))
151 do_ls(repo, pwd, words[1:], out)
155 for parm in words[1:]:
156 res = vfs.resolve(repo, parm, parent=np)
157 _, leaf_item = res[-1]
159 raise CommandError('path does not exist: ' + rpath_msg(res))
160 if not stat.S_ISDIR(vfs.item_mode(leaf_item)):
161 raise CommandError('path is not a directory: ' + path_msg(parm))
167 out.write(b'/'.join(name for name, item in pwd) + b'\n')
170 for parm in words[1:]:
171 res = vfs.resolve(repo, parm, parent=pwd)
172 _, leaf_item = res[-1]
174 raise CommandError('path does not exist: ' + rpath_msg(res))
175 with vfs.fopen(repo, leaf_item) as srcfile:
176 write_to_file(srcfile, out)
179 if len(words) not in [2,3]:
180 raise CommandError('Usage: get <filename> [localname]')
182 (dir,base) = os.path.split(rname)
183 lname = len(words) > 2 and words[2] or base
184 res = vfs.resolve(repo, rname, parent=pwd)
185 _, leaf_item = res[-1]
187 raise CommandError('path does not exist: ' + rpath_msg(res))
188 with vfs.fopen(repo, leaf_item) as srcfile:
189 with open(lname, 'wb') as destfile:
190 log('Saving %s\n' % path_msg(lname))
191 write_to_file(srcfile, destfile)
193 for parm in words[1:]:
194 dir, base = os.path.split(parm)
196 res = vfs.resolve(repo, dir, parent=pwd)
197 _, dir_item = res[-1]
199 raise CommandError('path does not exist: ' + path_msg(dir))
200 for name, item in vfs.contents(repo, dir_item):
203 if fnmatch.fnmatch(name, base):
204 if stat.S_ISLNK(vfs.item_mode(item)):
205 deref = vfs.resolve(repo, name, parent=res)
206 deref_name, deref_item = deref[-1]
208 raise CommandError('path does not exist: '
211 with vfs.fopen(repo, item) as srcfile:
212 with open(name, 'wb') as destfile:
213 log('Saving %s\n' % path_msg(name))
214 write_to_file(srcfile, destfile)
215 elif cmd in (b'help', b'?'):
216 out.write(b'Commands: ls cd pwd cat get mget help quit\n')
218 elif cmd in (b'quit', b'exit', b'bye'):
221 raise CommandError('no such command: '
222 + cmd.encode(errors='backslashreplace'))
223 except CommandError as ex:
224 out.write(b'error: %s\n' % str(ex).encode(errors='backslashreplace'))
230 o = options.Options(optspec)
231 opt, flags, extra = o.parse_bytes(argv[1:])
233 git.check_repo_or_die()
235 out = byte_stream(sys.stdout)
236 stdin = byte_stream(sys.stdin)
237 with LocalRepo() as repo:
238 present_interface(stdin, out, extra, repo)
240 log('warning: %d errors encountered\n' % len(saved_errors))