Correct the Bash completion script behaviour.
[dput.git] / dput / helper / dputhelper.py
blob0f02fcc27dd85a64bcfda312d4c616933f8aca90
1 # -*- coding: utf-8; -*-
3 # dput/helper/dputhelper.py
4 # Part of ‘dput’, a Debian package upload toolkit.
6 # This is free software, and you are welcome to redistribute it under
7 # certain conditions; see the end of this file for copyright
8 # information, grant of license, and disclaimer of warranty.
10 """ Helper code for Dput. """
12 import io
13 import locale
14 import os
15 import subprocess
16 import sys
17 import time
19 import pkg_resources
22 class DputException(Exception):
23 pass
26 class DputUploadFatalException(DputException):
27 pass
30 EXIT_STATUS_SUCCESS = 0
31 EXIT_STATUS_FAILURE = 1
32 EXIT_STATUS_COMMAND_NOT_FOUND = 127
35 # This wrapper is intended as a migration target for the prior
36 # `spawnv` wrapper, and will produce the same output for now.
37 def check_call(args, *posargs, **kwargs):
38 """ Wrap `subprocess.check_call` with error output. """
39 command_file_path = args[0]
40 try:
41 subprocess.check_call(args, *posargs, **kwargs)
42 exit_status = EXIT_STATUS_SUCCESS
43 except subprocess.CalledProcessError as exc:
44 exit_status = exc.returncode
45 if exit_status == EXIT_STATUS_COMMAND_NOT_FOUND:
46 sys.stderr.write(
47 "Error: Failed to execute '{path}'.\n"
48 " "
49 "The file may not exist or not be executable.\n".format(
50 path=command_file_path))
51 else:
52 sys.stderr.write(
53 "Warning: The execution of '{path}' as\n"
54 " '{command}'\n"
55 " returned a nonzero exit code.\n".format(
56 path=command_file_path, command=" ".join(args)))
57 return exit_status
60 class TimestampFile:
62 def __init__(self, f):
63 self.f = f
64 self.buf = ""
66 def __getattr__(self, name):
67 return getattr(self.f, name)
69 def write(self, s):
70 self.buf += s
71 idx = self.buf.find('\n')
72 while idx >= 0:
73 self.f.write(str(time.time()) + ': ' + self.buf[:idx + 1])
74 self.buf = self.buf[idx + 1:]
75 idx = self.buf.find('\n')
77 def close(self):
78 if self.buf:
79 self.f.write(str(time.time()) + ': ' + self.buf)
80 self.buf = ""
81 f.close()
84 class FileWithProgress:
85 """ Mimics a file (passed as f, an open file), but with progress.
87 FileWithProgress(f, args)
89 args:
90 * ptype = 1,2 is the type ("|/-\" or numeric), default 0 (no progress)
91 * progressf = file to output progress to (default sys.stdout)
92 * size = size of file (or -1, the default, to ignore)
93 for numeric output
94 * step = stepsize (default 1024)
96 """
98 def __init__(self, f, ptype=0, progressf=sys.stdout, size=-1, step=1024):
99 self.f = f
100 self.count = 0
101 self.lastupdate = 0
102 self.ptype = ptype
103 self.ppos = 0
104 self.progresschars = ['|', '/', '-', '\\']
105 self.progressf = progressf
106 self.size = size
107 self.step = step
108 self.closed = 0
110 def __getattr__(self, name):
111 return getattr(self.f, name)
113 def read(self, size=-1):
114 a = self.f.read(size)
115 self.count = self.count + len(a)
116 if (self.count - self.lastupdate) > 1024:
117 if self.ptype == 1:
118 self.ppos = (self.ppos + 1) % len(self.progresschars)
119 self.progressf.write(
120 (self.lastupdate != 0) * "\b" +
121 self.progresschars[self.ppos])
122 self.progressf.flush()
123 self.lastupdate = self.count
124 elif self.ptype == 2:
125 s = str(self.count // self.step) + "k"
126 if self.size >= 0:
127 s += (
128 '/' + str((self.size + self.step - 1) // self.step)
129 + 'k')
130 s += min(self.ppos - len(s), 0) * ' '
131 self.progressf.write(self.ppos * "\b" + s)
132 self.progressf.flush()
133 self.ppos = len(s)
134 return a
136 def close(self):
137 if not self.closed:
138 self.f.close()
139 self.closed = 1
140 if self.ptype == 1:
141 if self.lastupdate:
142 self.progressf.write("\b \b")
143 self.progressf.flush()
144 elif self.ptype == 2:
145 self.progressf.write(
146 self.ppos * "\b" + self.ppos * " " + self.ppos * "\b")
147 self.progressf.flush()
149 def __del__(self):
150 self.close()
153 def make_text_stream(stream):
154 """ Make a text stream from the specified stream.
156 :param stream: An open file-like object.
157 :return: A stream object providing text I/O.
159 In the normal case, the specified stream is a stream providing
160 bytes I/O. We create an `io.TextIOWrapper` with the
161 appropriate encoding for the byte stream, and return that
162 wrapper stream.
164 The text encoding is determined by interrogating the file
165 object. If the file object has no declared encoding, the
166 default `locale.getpreferredencoding(False)` is used.
168 If the stream is a `io.TextIOBase` instance, it is already
169 providing text I/O. In this case, the stream is returned as
173 result = None
175 if hasattr(stream, 'encoding'):
176 encoding = stream.encoding
177 else:
178 encoding = locale.getpreferredencoding(False)
180 global file
181 if sys.version_info >= (3, 0):
182 # We will never match an instance of this type, since it is
183 # not implemented in this Python version.
184 file = type(NotImplemented)
186 if isinstance(stream, file):
187 # Python 2 `file` type doesn't work with the `io` types. Open
188 # the file again with a type we can use.
189 if stream.mode.endswith('b'):
190 text_mode = stream.mode[:-1]
191 else:
192 text_mode = stream.mode
193 fileno = os.dup(stream.fileno())
194 result = io.open(fileno, mode=text_mode, encoding=encoding)
195 elif isinstance(stream, io.TextIOBase):
196 result = stream
197 else:
198 result = io.TextIOWrapper(stream, encoding=encoding)
200 return result
203 def get_progname(argv=None):
204 """ Get the program name from the command line arguments.
206 :param argv: Sequence of command-line arguments.
207 Defaults to `sys.argv`.
208 :return: The program name used to invoke this program.
211 if argv is None:
212 argv = sys.argv
213 progname = os.path.basename(argv[0])
214 return progname
217 def get_distribution_version():
218 """ Get the version string for this distribution. """
219 distribution = pkg_resources.get_distribution("dput")
220 return distribution.version
223 def getopt(args, shortopts, longopts):
224 args = args[:]
225 optlist = []
226 while args and args[0].startswith('-'):
227 if args[0] == '--':
228 args = args[1:]
229 break
230 if args[0] == '-':
231 break
232 if args[0].startswith('--'):
233 opt = args.pop(0)[2:]
234 if '=' in opt:
235 opt, optarg = opt.split('=', 1)
236 else:
237 optarg = None
238 prefixmatch = [x for x in longopts if x.startswith(opt)]
239 if len(prefixmatch) == 0:
240 raise DputException('unknown option --%s' % opt)
241 elif len(prefixmatch) > 1:
242 raise DputException('non-unique prefix --%s' % opt)
243 opt = prefixmatch[0]
244 if opt.endswith('=='):
245 opt = opt[:-2]
246 optarg = optarg or ''
247 elif opt.endswith('='):
248 opt = opt[:-1]
249 if not optarg:
250 if not args:
251 raise DputException(
252 'option --%s requires argument' % opt)
253 optarg = args.pop(0)
254 else:
255 if optarg is not None:
256 raise DputException(
257 'option --%s does not take arguments' % opt)
258 optarg = ''
259 optlist.append(('--' + opt, optarg))
260 else:
261 s = args.pop(0)[1:]
262 while s:
263 pos = shortopts.find(s[0])
264 if pos == -1:
265 raise DputException('option -%s unknown' % s[0])
266 if pos + 1 >= len(shortopts) or shortopts[pos + 1] != ':':
267 optlist.append(('-' + s[0], ''))
268 s = s[1:]
269 elif len(s) > 1:
270 optlist.append(('-' + s[0], s[1:]))
271 s = ''
272 elif args:
273 optlist.append(('-' + s, args.pop(0)))
274 s = ''
275 else:
276 raise DputException('option -%s requires argument' % s)
277 return optlist, args
280 if __name__ == '__main__':
281 file_name = "dput.py"
282 file_path = os.path.join(os.path.dirname(__file__), os.pardir, file_name)
283 file_size = os.stat(file_path).st_size
284 for i in range(1, 3):
285 sys.stdout.write("Reading %s " % file_name)
286 sys.stdout.flush()
287 a = FileWithProgress(open(file_path), ptype=i, size=file_size)
288 b = ' '
289 while b:
290 b = a.read(4096)
291 time.sleep(1)
292 a.close()
293 print
296 # Copyright © 2015–2016 Ben Finney <bignose@debian.org>
297 # Copyright © 2009–2010 Y Giridhar Appaji Nag <appaji@debian.org>
298 # Copyright © 2007–2008 Thomas Viehmann <tv@beamnet.de>
300 # This is free software: you may copy, modify, and/or distribute this work
301 # under the terms of the GNU General Public License as published by the
302 # Free Software Foundation; version 2 of that license or any later version.
303 # No warranty expressed or implied. See the file ‘LICENSE.GPL-2’ for details.
306 # Local variables:
307 # coding: utf-8
308 # mode: python
309 # End:
310 # vim: fileencoding=utf-8 filetype=python :