comparison venv/lib/python2.7/site-packages/click/utils.py @ 0:d67268158946 draft

planemo upload commit a3f181f5f126803c654b3a66dd4e83a48f7e203b
author bcclaywell
date Mon, 12 Oct 2015 17:43:33 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:d67268158946
1 import os
2 import sys
3 from collections import deque
4
5 from .globals import resolve_color_default
6
7 from ._compat import text_type, open_stream, get_filesystem_encoding, \
8 get_streerror, string_types, PY2, binary_streams, text_streams, \
9 filename_to_ui, auto_wrap_for_ansi, strip_ansi, should_strip_ansi, \
10 _default_text_stdout, _default_text_stderr, is_bytes, WIN
11
12 if not PY2:
13 from ._compat import _find_binary_writer
14
15
16 echo_native_types = string_types + (bytes, bytearray)
17
18
19 def _posixify(name):
20 return '-'.join(name.split()).lower()
21
22
23 def unpack_args(args, nargs_spec):
24 """Given an iterable of arguments and an iterable of nargs specifications,
25 it returns a tuple with all the unpacked arguments at the first index
26 and all remaining arguments as the second.
27
28 The nargs specification is the number of arguments that should be consumed
29 or `-1` to indicate that this position should eat up all the remainders.
30
31 Missing items are filled with `None`.
32
33 Examples:
34
35 >>> unpack_args(range(6), [1, 2, 1, -1])
36 ((0, (1, 2), 3, (4, 5)), [])
37 >>> unpack_args(range(6), [1, 2, 1])
38 ((0, (1, 2), 3), [4, 5])
39 >>> unpack_args(range(6), [-1])
40 (((0, 1, 2, 3, 4, 5),), [])
41 >>> unpack_args(range(6), [1, 1])
42 ((0, 1), [2, 3, 4, 5])
43 >>> unpack_args(range(6), [-1,1,1,1,1])
44 (((0, 1), 2, 3, 4, 5), [])
45 """
46 args = deque(args)
47 nargs_spec = deque(nargs_spec)
48 rv = []
49 spos = None
50
51 def _fetch(c):
52 try:
53 if spos is None:
54 return c.popleft()
55 else:
56 return c.pop()
57 except IndexError:
58 return None
59
60 while nargs_spec:
61 nargs = _fetch(nargs_spec)
62 if nargs == 1:
63 rv.append(_fetch(args))
64 elif nargs > 1:
65 x = [_fetch(args) for _ in range(nargs)]
66 # If we're reversed, we're pulling in the arguments in reverse,
67 # so we need to turn them around.
68 if spos is not None:
69 x.reverse()
70 rv.append(tuple(x))
71 elif nargs < 0:
72 if spos is not None:
73 raise TypeError('Cannot have two nargs < 0')
74 spos = len(rv)
75 rv.append(None)
76
77 # spos is the position of the wildcard (star). If it's not `None`,
78 # we fill it with the remainder.
79 if spos is not None:
80 rv[spos] = tuple(args)
81 args = []
82 rv[spos + 1:] = reversed(rv[spos + 1:])
83
84 return tuple(rv), list(args)
85
86
87 def safecall(func):
88 """Wraps a function so that it swallows exceptions."""
89 def wrapper(*args, **kwargs):
90 try:
91 return func(*args, **kwargs)
92 except Exception:
93 pass
94 return wrapper
95
96
97 def make_str(value):
98 """Converts a value into a valid string."""
99 if isinstance(value, bytes):
100 try:
101 return value.decode(get_filesystem_encoding())
102 except UnicodeError:
103 return value.decode('utf-8', 'replace')
104 return text_type(value)
105
106
107 def make_default_short_help(help, max_length=45):
108 words = help.split()
109 total_length = 0
110 result = []
111 done = False
112
113 for word in words:
114 if word[-1:] == '.':
115 done = True
116 new_length = result and 1 + len(word) or len(word)
117 if total_length + new_length > max_length:
118 result.append('...')
119 done = True
120 else:
121 if result:
122 result.append(' ')
123 result.append(word)
124 if done:
125 break
126 total_length += new_length
127
128 return ''.join(result)
129
130
131 class LazyFile(object):
132 """A lazy file works like a regular file but it does not fully open
133 the file but it does perform some basic checks early to see if the
134 filename parameter does make sense. This is useful for safely opening
135 files for writing.
136 """
137
138 def __init__(self, filename, mode='r', encoding=None, errors='strict',
139 atomic=False):
140 self.name = filename
141 self.mode = mode
142 self.encoding = encoding
143 self.errors = errors
144 self.atomic = atomic
145
146 if filename == '-':
147 self._f, self.should_close = open_stream(filename, mode,
148 encoding, errors)
149 else:
150 if 'r' in mode:
151 # Open and close the file in case we're opening it for
152 # reading so that we can catch at least some errors in
153 # some cases early.
154 open(filename, mode).close()
155 self._f = None
156 self.should_close = True
157
158 def __getattr__(self, name):
159 return getattr(self.open(), name)
160
161 def __repr__(self):
162 if self._f is not None:
163 return repr(self._f)
164 return '<unopened file %r %s>' % (self.name, self.mode)
165
166 def open(self):
167 """Opens the file if it's not yet open. This call might fail with
168 a :exc:`FileError`. Not handling this error will produce an error
169 that Click shows.
170 """
171 if self._f is not None:
172 return self._f
173 try:
174 rv, self.should_close = open_stream(self.name, self.mode,
175 self.encoding,
176 self.errors,
177 atomic=self.atomic)
178 except (IOError, OSError) as e:
179 from .exceptions import FileError
180 raise FileError(self.name, hint=get_streerror(e))
181 self._f = rv
182 return rv
183
184 def close(self):
185 """Closes the underlying file, no matter what."""
186 if self._f is not None:
187 self._f.close()
188
189 def close_intelligently(self):
190 """This function only closes the file if it was opened by the lazy
191 file wrapper. For instance this will never close stdin.
192 """
193 if self.should_close:
194 self.close()
195
196 def __enter__(self):
197 return self
198
199 def __exit__(self, exc_type, exc_value, tb):
200 self.close_intelligently()
201
202 def __iter__(self):
203 self.open()
204 return iter(self._f)
205
206
207 class KeepOpenFile(object):
208
209 def __init__(self, file):
210 self._file = file
211
212 def __getattr__(self, name):
213 return getattr(self._file, name)
214
215 def __enter__(self):
216 return self
217
218 def __exit__(self, exc_type, exc_value, tb):
219 pass
220
221 def __repr__(self):
222 return repr(self._file)
223
224 def __iter__(self):
225 return iter(self._file)
226
227
228 def echo(message=None, file=None, nl=True, err=False, color=None):
229 """Prints a message plus a newline to the given file or stdout. On
230 first sight, this looks like the print function, but it has improved
231 support for handling Unicode and binary data that does not fail no
232 matter how badly configured the system is.
233
234 Primarily it means that you can print binary data as well as Unicode
235 data on both 2.x and 3.x to the given file in the most appropriate way
236 possible. This is a very carefree function as in that it will try its
237 best to not fail.
238
239 In addition to that, if `colorama`_ is installed, the echo function will
240 also support clever handling of ANSI codes. Essentially it will then
241 do the following:
242
243 - add transparent handling of ANSI color codes on Windows.
244 - hide ANSI codes automatically if the destination file is not a
245 terminal.
246
247 .. _colorama: http://pypi.python.org/pypi/colorama
248
249 .. versionchanged:: 2.0
250 Starting with version 2.0 of Click, the echo function will work
251 with colorama if it's installed.
252
253 .. versionadded:: 3.0
254 The `err` parameter was added.
255
256 .. versionchanged:: 4.0
257 Added the `color` flag.
258
259 :param message: the message to print
260 :param file: the file to write to (defaults to ``stdout``)
261 :param err: if set to true the file defaults to ``stderr`` instead of
262 ``stdout``. This is faster and easier than calling
263 :func:`get_text_stderr` yourself.
264 :param nl: if set to `True` (the default) a newline is printed afterwards.
265 :param color: controls if the terminal supports ANSI colors or not. The
266 default is autodetection.
267 """
268 if file is None:
269 if err:
270 file = _default_text_stderr()
271 else:
272 file = _default_text_stdout()
273
274 # Convert non bytes/text into the native string type.
275 if message is not None and not isinstance(message, echo_native_types):
276 message = text_type(message)
277
278 if nl:
279 message = message or u''
280 if isinstance(message, text_type):
281 message += u'\n'
282 else:
283 message += b'\n'
284
285 # If there is a message, and we're in Python 3, and the value looks
286 # like bytes, we manually need to find the binary stream and write the
287 # message in there. This is done separately so that most stream
288 # types will work as you would expect. Eg: you can write to StringIO
289 # for other cases.
290 if message and not PY2 and is_bytes(message):
291 binary_file = _find_binary_writer(file)
292 if binary_file is not None:
293 file.flush()
294 binary_file.write(message)
295 binary_file.flush()
296 return
297
298 # ANSI-style support. If there is no message or we are dealing with
299 # bytes nothing is happening. If we are connected to a file we want
300 # to strip colors. If we are on windows we either wrap the stream
301 # to strip the color or we use the colorama support to translate the
302 # ansi codes to API calls.
303 if message and not is_bytes(message):
304 color = resolve_color_default(color)
305 if should_strip_ansi(file, color):
306 message = strip_ansi(message)
307 elif WIN:
308 if auto_wrap_for_ansi is not None:
309 file = auto_wrap_for_ansi(file)
310 elif not color:
311 message = strip_ansi(message)
312
313 if message:
314 file.write(message)
315 file.flush()
316
317
318 def get_binary_stream(name):
319 """Returns a system stream for byte processing. This essentially
320 returns the stream from the sys module with the given name but it
321 solves some compatibility issues between different Python versions.
322 Primarily this function is necessary for getting binary streams on
323 Python 3.
324
325 :param name: the name of the stream to open. Valid names are ``'stdin'``,
326 ``'stdout'`` and ``'stderr'``
327 """
328 opener = binary_streams.get(name)
329 if opener is None:
330 raise TypeError('Unknown standard stream %r' % name)
331 return opener()
332
333
334 def get_text_stream(name, encoding=None, errors='strict'):
335 """Returns a system stream for text processing. This usually returns
336 a wrapped stream around a binary stream returned from
337 :func:`get_binary_stream` but it also can take shortcuts on Python 3
338 for already correctly configured streams.
339
340 :param name: the name of the stream to open. Valid names are ``'stdin'``,
341 ``'stdout'`` and ``'stderr'``
342 :param encoding: overrides the detected default encoding.
343 :param errors: overrides the default error mode.
344 """
345 opener = text_streams.get(name)
346 if opener is None:
347 raise TypeError('Unknown standard stream %r' % name)
348 return opener(encoding, errors)
349
350
351 def open_file(filename, mode='r', encoding=None, errors='strict',
352 lazy=False, atomic=False):
353 """This is similar to how the :class:`File` works but for manual
354 usage. Files are opened non lazy by default. This can open regular
355 files as well as stdin/stdout if ``'-'`` is passed.
356
357 If stdin/stdout is returned the stream is wrapped so that the context
358 manager will not close the stream accidentally. This makes it possible
359 to always use the function like this without having to worry to
360 accidentally close a standard stream::
361
362 with open_file(filename) as f:
363 ...
364
365 .. versionadded:: 3.0
366
367 :param filename: the name of the file to open (or ``'-'`` for stdin/stdout).
368 :param mode: the mode in which to open the file.
369 :param encoding: the encoding to use.
370 :param errors: the error handling for this file.
371 :param lazy: can be flipped to true to open the file lazily.
372 :param atomic: in atomic mode writes go into a temporary file and it's
373 moved on close.
374 """
375 if lazy:
376 return LazyFile(filename, mode, encoding, errors, atomic=atomic)
377 f, should_close = open_stream(filename, mode, encoding, errors,
378 atomic=atomic)
379 if not should_close:
380 f = KeepOpenFile(f)
381 return f
382
383
384 def format_filename(filename, shorten=False):
385 """Formats a filename for user display. The main purpose of this
386 function is to ensure that the filename can be displayed at all. This
387 will decode the filename to unicode if necessary in a way that it will
388 not fail. Optionally, it can shorten the filename to not include the
389 full path to the filename.
390
391 :param filename: formats a filename for UI display. This will also convert
392 the filename into unicode without failing.
393 :param shorten: this optionally shortens the filename to strip of the
394 path that leads up to it.
395 """
396 if shorten:
397 filename = os.path.basename(filename)
398 return filename_to_ui(filename)
399
400
401 def get_app_dir(app_name, roaming=True, force_posix=False):
402 r"""Returns the config folder for the application. The default behavior
403 is to return whatever is most appropriate for the operating system.
404
405 To give you an idea, for an app called ``"Foo Bar"``, something like
406 the following folders could be returned:
407
408 Mac OS X:
409 ``~/Library/Application Support/Foo Bar``
410 Mac OS X (POSIX):
411 ``~/.foo-bar``
412 Unix:
413 ``~/.config/foo-bar``
414 Unix (POSIX):
415 ``~/.foo-bar``
416 Win XP (roaming):
417 ``C:\Documents and Settings\<user>\Local Settings\Application Data\Foo Bar``
418 Win XP (not roaming):
419 ``C:\Documents and Settings\<user>\Application Data\Foo Bar``
420 Win 7 (roaming):
421 ``C:\Users\<user>\AppData\Roaming\Foo Bar``
422 Win 7 (not roaming):
423 ``C:\Users\<user>\AppData\Local\Foo Bar``
424
425 .. versionadded:: 2.0
426
427 :param app_name: the application name. This should be properly capitalized
428 and can contain whitespace.
429 :param roaming: controls if the folder should be roaming or not on Windows.
430 Has no affect otherwise.
431 :param force_posix: if this is set to `True` then on any POSIX system the
432 folder will be stored in the home folder with a leading
433 dot instead of the XDG config home or darwin's
434 application support folder.
435 """
436 if WIN:
437 key = roaming and 'APPDATA' or 'LOCALAPPDATA'
438 folder = os.environ.get(key)
439 if folder is None:
440 folder = os.path.expanduser('~')
441 return os.path.join(folder, app_name)
442 if force_posix:
443 return os.path.join(os.path.expanduser('~/.' + _posixify(app_name)))
444 if sys.platform == 'darwin':
445 return os.path.join(os.path.expanduser(
446 '~/Library/Application Support'), app_name)
447 return os.path.join(
448 os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')),
449 _posixify(app_name))