]> arthur.barton.de Git - bup.git/blob - lib/cmd/ftp-cmd.py
Bypass Python 3 glibc argv problems by routing args through env
[bup.git] / lib / cmd / ftp-cmd.py
1 #!/bin/sh
2 """": # -*-python-*-
3 # https://sourceware.org/bugzilla/show_bug.cgi?id=26034
4 export "BUP_ARGV_0"="$0"
5 arg_i=1
6 for arg in "$@"; do
7     export "BUP_ARGV_${arg_i}"="$arg"
8     shift
9     arg_i=$((arg_i + 1))
10 done
11 # Here to end of preamble replaced during install
12 bup_python="$(dirname "$0")/bup-python" || exit $?
13 exec "$bup_python" "$0"
14 """
15 # end of bup preamble
16
17 # For now, this completely relies on the assumption that the current
18 # encoding (LC_CTYPE, etc.) is ASCII compatible, and that it returns
19 # the exact same bytes from a decode/encode round-trip (or the reverse
20 # (e.g. ISO-8859-1).
21
22 from __future__ import absolute_import, print_function
23 import sys, os, stat, fnmatch
24
25 from bup import _helpers, compat, options, git, shquote, ls, vfs
26 from bup.compat import argv_bytes
27 from bup.helpers import chunkyreader, handle_ctrl_c, log
28 from bup.io import byte_stream, path_msg
29 from bup.repo import LocalRepo
30
31 handle_ctrl_c()
32
33
34 class OptionError(Exception):
35     pass
36
37
38 def do_ls(repo, args, out):
39     try:
40         opt = ls.opts_from_cmdline(args, onabort=OptionError)
41     except OptionError as e:
42         log('error: %s' % e)
43         return
44     return ls.within_repo(repo, opt, out)
45
46
47 def write_to_file(inf, outf):
48     for blob in chunkyreader(inf):
49         outf.write(blob)
50
51
52 def inputiter():
53     if os.isatty(stdin.fileno()):
54         while 1:
55             try:
56                 yield _helpers.readline(b'bup> ')
57             except EOFError:
58                 print()  # Clear the line for the terminal's next prompt
59                 break
60     else:
61         for line in stdin:
62             yield line
63
64
65 def _completer_get_subs(repo, line):
66     (qtype, lastword) = shquote.unfinished_word(line)
67     dir, name = os.path.split(lastword)
68     dir_path = vfs.resolve(repo, dir or b'/')
69     _, dir_item = dir_path[-1]
70     if not dir_item:
71         subs = tuple()
72     else:
73         subs = tuple(dir_path + (entry,)
74                      for entry in vfs.contents(repo, dir_item)
75                      if (entry[0] != b'.' and entry[0].startswith(name)))
76     return qtype, lastword, subs
77
78
79 _attempt_start = None
80 _attempt_end = None
81 def attempt_completion(text, start, end):
82     global _attempt_start, _attempt_end
83     _attempt_start = start
84     _attempt_end = end
85     return None
86
87 _last_line = None
88 _last_res = None
89 def enter_completion(text, iteration):
90     global repo
91     global _attempt_end
92     global _last_line
93     global _last_res
94     try:
95         line = _helpers.get_line_buffer()[:_attempt_end]
96         if _last_line != line:
97             _last_res = _completer_get_subs(repo, line)
98             _last_line = line
99         qtype, lastword, subs = _last_res
100         if iteration < len(subs):
101             path = subs[iteration]
102             leaf_name, leaf_item = path[-1]
103             res = vfs.try_resolve(repo, leaf_name, parent=path[:-1])
104             leaf_name, leaf_item = res[-1]
105             fullname = os.path.join(*(name for name, item in res))
106             if stat.S_ISDIR(vfs.item_mode(leaf_item)):
107                 ret = shquote.what_to_add(qtype, lastword, fullname + b'/',
108                                           terminate=False)
109             else:
110                 ret = shquote.what_to_add(qtype, lastword, fullname,
111                                           terminate=True) + b' '
112             return text + ret
113     except Exception as e:
114         log('\n')
115         try:
116             import traceback
117             traceback.print_tb(sys.exc_traceback)
118         except Exception as e2:
119             log('Error printing traceback: %s\n' % e2)
120         log('\nError in completion: %s\n' % e)
121
122
123 optspec = """
124 bup ftp [commands...]
125 """
126 o = options.Options(optspec)
127 opt, flags, extra = o.parse(compat.argv[1:])
128
129 git.check_repo_or_die()
130
131 sys.stdout.flush()
132 out = byte_stream(sys.stdout)
133 stdin = byte_stream(sys.stdin)
134 repo = LocalRepo()
135 pwd = vfs.resolve(repo, b'/')
136 rv = 0
137
138
139
140 if extra:
141     lines = (argv_bytes(arg) for arg in extra)
142 else:
143     if hasattr(_helpers, 'readline'):
144         _helpers.set_completer_word_break_characters(b' \t\n\r/')
145         _helpers.set_attempted_completion_function(attempt_completion)
146         _helpers.set_completion_entry_function(enter_completion)
147         if sys.platform.startswith('darwin'):
148             # MacOS uses a slightly incompatible clone of libreadline
149             _helpers.parse_and_bind(b'bind ^I rl_complete')
150         _helpers.parse_and_bind(b'tab: complete')
151     lines = inputiter()
152
153 for line in lines:
154     if not line.strip():
155         continue
156     words = [word for (wordstart,word) in shquote.quotesplit(line)]
157     cmd = words[0].lower()
158     #log('execute: %r %r\n' % (cmd, parm))
159     try:
160         if cmd == b'ls':
161             # FIXME: respect pwd (perhaps via ls accepting resolve path/parent)
162             do_ls(repo, words[1:], out)
163             out.flush()
164         elif cmd == b'cd':
165             np = pwd
166             for parm in words[1:]:
167                 res = vfs.resolve(repo, parm, parent=np)
168                 _, leaf_item = res[-1]
169                 if not leaf_item:
170                     raise Exception('%s does not exist'
171                                     % path_msg(b'/'.join(name for name, item
172                                                          in res)))
173                 if not stat.S_ISDIR(vfs.item_mode(leaf_item)):
174                     raise Exception('%s is not a directory' % path_msg(parm))
175                 np = res
176             pwd = np
177         elif cmd == b'pwd':
178             if len(pwd) == 1:
179                 out.write(b'/')
180             out.write(b'/'.join(name for name, item in pwd) + b'\n')
181             out.flush()
182         elif cmd == b'cat':
183             for parm in words[1:]:
184                 res = vfs.resolve(repo, parm, parent=pwd)
185                 _, leaf_item = res[-1]
186                 if not leaf_item:
187                     raise Exception('%s does not exist' %
188                                     path_msg(b'/'.join(name for name, item
189                                                        in res)))
190                 with vfs.fopen(repo, leaf_item) as srcfile:
191                     write_to_file(srcfile, out)
192             out.flush()
193         elif cmd == b'get':
194             if len(words) not in [2,3]:
195                 rv = 1
196                 raise Exception('Usage: get <filename> [localname]')
197             rname = words[1]
198             (dir,base) = os.path.split(rname)
199             lname = len(words) > 2 and words[2] or base
200             res = vfs.resolve(repo, rname, parent=pwd)
201             _, leaf_item = res[-1]
202             if not leaf_item:
203                 raise Exception('%s does not exist' %
204                                 path_msg(b'/'.join(name for name, item in res)))
205             with vfs.fopen(repo, leaf_item) as srcfile:
206                 with open(lname, 'wb') as destfile:
207                     log('Saving %s\n' % path_msg(lname))
208                     write_to_file(srcfile, destfile)
209         elif cmd == b'mget':
210             for parm in words[1:]:
211                 dir, base = os.path.split(parm)
212
213                 res = vfs.resolve(repo, dir, parent=pwd)
214                 _, dir_item = res[-1]
215                 if not dir_item:
216                     raise Exception('%s does not exist' % path_msg(dir))
217                 for name, item in vfs.contents(repo, dir_item):
218                     if name == b'.':
219                         continue
220                     if fnmatch.fnmatch(name, base):
221                         if stat.S_ISLNK(vfs.item_mode(item)):
222                             deref = vfs.resolve(repo, name, parent=res)
223                             deref_name, deref_item = deref[-1]
224                             if not deref_item:
225                                 raise Exception('%s does not exist' %
226                                                 path_msg('/'.join(name for name, item
227                                                                   in deref)))
228                             item = deref_item
229                         with vfs.fopen(repo, item) as srcfile:
230                             with open(name, 'wb') as destfile:
231                                 log('Saving %s\n' % path_msg(name))
232                                 write_to_file(srcfile, destfile)
233         elif cmd == b'help' or cmd == b'?':
234             out.write(b'Commands: ls cd pwd cat get mget help quit\n')
235             out.flush()
236         elif cmd in (b'quit', b'exit', b'bye'):
237             break
238         else:
239             rv = 1
240             raise Exception('no such command %r' % cmd)
241     except Exception as e:
242         rv = 1
243         log('error: %s\n' % e)
244         raise
245
246 sys.exit(rv)