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