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