123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455 |
- import os
- import sys
- from ._compat import _default_text_stderr
- from ._compat import _default_text_stdout
- from ._compat import auto_wrap_for_ansi
- from ._compat import binary_streams
- from ._compat import filename_to_ui
- from ._compat import get_filesystem_encoding
- from ._compat import get_streerror
- from ._compat import is_bytes
- from ._compat import open_stream
- from ._compat import PY2
- from ._compat import should_strip_ansi
- from ._compat import string_types
- from ._compat import strip_ansi
- from ._compat import text_streams
- from ._compat import text_type
- from ._compat import WIN
- from .globals import resolve_color_default
- if not PY2:
- from ._compat import _find_binary_writer
- elif WIN:
- from ._winconsole import _get_windows_argv
- from ._winconsole import _hash_py_argv
- from ._winconsole import _initial_argv_hash
- echo_native_types = string_types + (bytes, bytearray)
- def _posixify(name):
- return "-".join(name.split()).lower()
- def safecall(func):
- """Wraps a function so that it swallows exceptions."""
- def wrapper(*args, **kwargs):
- try:
- return func(*args, **kwargs)
- except Exception:
- pass
- return wrapper
- def make_str(value):
- """Converts a value into a valid string."""
- if isinstance(value, bytes):
- try:
- return value.decode(get_filesystem_encoding())
- except UnicodeError:
- return value.decode("utf-8", "replace")
- return text_type(value)
- def make_default_short_help(help, max_length=45):
- """Return a condensed version of help string."""
- words = help.split()
- total_length = 0
- result = []
- done = False
- for word in words:
- if word[-1:] == ".":
- done = True
- new_length = 1 + len(word) if result else len(word)
- if total_length + new_length > max_length:
- result.append("...")
- done = True
- else:
- if result:
- result.append(" ")
- result.append(word)
- if done:
- break
- total_length += new_length
- return "".join(result)
- class LazyFile(object):
- """A lazy file works like a regular file but it does not fully open
- the file but it does perform some basic checks early to see if the
- filename parameter does make sense. This is useful for safely opening
- files for writing.
- """
- def __init__(
- self, filename, mode="r", encoding=None, errors="strict", atomic=False
- ):
- self.name = filename
- self.mode = mode
- self.encoding = encoding
- self.errors = errors
- self.atomic = atomic
- if filename == "-":
- self._f, self.should_close = open_stream(filename, mode, encoding, errors)
- else:
- if "r" in mode:
- # Open and close the file in case we're opening it for
- # reading so that we can catch at least some errors in
- # some cases early.
- open(filename, mode).close()
- self._f = None
- self.should_close = True
- def __getattr__(self, name):
- return getattr(self.open(), name)
- def __repr__(self):
- if self._f is not None:
- return repr(self._f)
- return "<unopened file '{}' {}>".format(self.name, self.mode)
- def open(self):
- """Opens the file if it's not yet open. This call might fail with
- a :exc:`FileError`. Not handling this error will produce an error
- that Click shows.
- """
- if self._f is not None:
- return self._f
- try:
- rv, self.should_close = open_stream(
- self.name, self.mode, self.encoding, self.errors, atomic=self.atomic
- )
- except (IOError, OSError) as e: # noqa: E402
- from .exceptions import FileError
- raise FileError(self.name, hint=get_streerror(e))
- self._f = rv
- return rv
- def close(self):
- """Closes the underlying file, no matter what."""
- if self._f is not None:
- self._f.close()
- def close_intelligently(self):
- """This function only closes the file if it was opened by the lazy
- file wrapper. For instance this will never close stdin.
- """
- if self.should_close:
- self.close()
- def __enter__(self):
- return self
- def __exit__(self, exc_type, exc_value, tb):
- self.close_intelligently()
- def __iter__(self):
- self.open()
- return iter(self._f)
- class KeepOpenFile(object):
- def __init__(self, file):
- self._file = file
- def __getattr__(self, name):
- return getattr(self._file, name)
- def __enter__(self):
- return self
- def __exit__(self, exc_type, exc_value, tb):
- pass
- def __repr__(self):
- return repr(self._file)
- def __iter__(self):
- return iter(self._file)
- def echo(message=None, file=None, nl=True, err=False, color=None):
- """Prints a message plus a newline to the given file or stdout. On
- first sight, this looks like the print function, but it has improved
- support for handling Unicode and binary data that does not fail no
- matter how badly configured the system is.
- Primarily it means that you can print binary data as well as Unicode
- data on both 2.x and 3.x to the given file in the most appropriate way
- possible. This is a very carefree function in that it will try its
- best to not fail. As of Click 6.0 this includes support for unicode
- output on the Windows console.
- In addition to that, if `colorama`_ is installed, the echo function will
- also support clever handling of ANSI codes. Essentially it will then
- do the following:
- - add transparent handling of ANSI color codes on Windows.
- - hide ANSI codes automatically if the destination file is not a
- terminal.
- .. _colorama: https://pypi.org/project/colorama/
- .. versionchanged:: 6.0
- As of Click 6.0 the echo function will properly support unicode
- output on the windows console. Not that click does not modify
- the interpreter in any way which means that `sys.stdout` or the
- print statement or function will still not provide unicode support.
- .. versionchanged:: 2.0
- Starting with version 2.0 of Click, the echo function will work
- with colorama if it's installed.
- .. versionadded:: 3.0
- The `err` parameter was added.
- .. versionchanged:: 4.0
- Added the `color` flag.
- :param message: the message to print
- :param file: the file to write to (defaults to ``stdout``)
- :param err: if set to true the file defaults to ``stderr`` instead of
- ``stdout``. This is faster and easier than calling
- :func:`get_text_stderr` yourself.
- :param nl: if set to `True` (the default) a newline is printed afterwards.
- :param color: controls if the terminal supports ANSI colors or not. The
- default is autodetection.
- """
- if file is None:
- if err:
- file = _default_text_stderr()
- else:
- file = _default_text_stdout()
- # Convert non bytes/text into the native string type.
- if message is not None and not isinstance(message, echo_native_types):
- message = text_type(message)
- if nl:
- message = message or u""
- if isinstance(message, text_type):
- message += u"\n"
- else:
- message += b"\n"
- # If there is a message, and we're in Python 3, and the value looks
- # like bytes, we manually need to find the binary stream and write the
- # message in there. This is done separately so that most stream
- # types will work as you would expect. Eg: you can write to StringIO
- # for other cases.
- if message and not PY2 and is_bytes(message):
- binary_file = _find_binary_writer(file)
- if binary_file is not None:
- file.flush()
- binary_file.write(message)
- binary_file.flush()
- return
- # ANSI-style support. If there is no message or we are dealing with
- # bytes nothing is happening. If we are connected to a file we want
- # to strip colors. If we are on windows we either wrap the stream
- # to strip the color or we use the colorama support to translate the
- # ansi codes to API calls.
- if message and not is_bytes(message):
- color = resolve_color_default(color)
- if should_strip_ansi(file, color):
- message = strip_ansi(message)
- elif WIN:
- if auto_wrap_for_ansi is not None:
- file = auto_wrap_for_ansi(file)
- elif not color:
- message = strip_ansi(message)
- if message:
- file.write(message)
- file.flush()
- def get_binary_stream(name):
- """Returns a system stream for byte processing. This essentially
- returns the stream from the sys module with the given name but it
- solves some compatibility issues between different Python versions.
- Primarily this function is necessary for getting binary streams on
- Python 3.
- :param name: the name of the stream to open. Valid names are ``'stdin'``,
- ``'stdout'`` and ``'stderr'``
- """
- opener = binary_streams.get(name)
- if opener is None:
- raise TypeError("Unknown standard stream '{}'".format(name))
- return opener()
- def get_text_stream(name, encoding=None, errors="strict"):
- """Returns a system stream for text processing. This usually returns
- a wrapped stream around a binary stream returned from
- :func:`get_binary_stream` but it also can take shortcuts on Python 3
- for already correctly configured streams.
- :param name: the name of the stream to open. Valid names are ``'stdin'``,
- ``'stdout'`` and ``'stderr'``
- :param encoding: overrides the detected default encoding.
- :param errors: overrides the default error mode.
- """
- opener = text_streams.get(name)
- if opener is None:
- raise TypeError("Unknown standard stream '{}'".format(name))
- return opener(encoding, errors)
- def open_file(
- filename, mode="r", encoding=None, errors="strict", lazy=False, atomic=False
- ):
- """This is similar to how the :class:`File` works but for manual
- usage. Files are opened non lazy by default. This can open regular
- files as well as stdin/stdout if ``'-'`` is passed.
- If stdin/stdout is returned the stream is wrapped so that the context
- manager will not close the stream accidentally. This makes it possible
- to always use the function like this without having to worry to
- accidentally close a standard stream::
- with open_file(filename) as f:
- ...
- .. versionadded:: 3.0
- :param filename: the name of the file to open (or ``'-'`` for stdin/stdout).
- :param mode: the mode in which to open the file.
- :param encoding: the encoding to use.
- :param errors: the error handling for this file.
- :param lazy: can be flipped to true to open the file lazily.
- :param atomic: in atomic mode writes go into a temporary file and it's
- moved on close.
- """
- if lazy:
- return LazyFile(filename, mode, encoding, errors, atomic=atomic)
- f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic)
- if not should_close:
- f = KeepOpenFile(f)
- return f
- def get_os_args():
- """This returns the argument part of sys.argv in the most appropriate
- form for processing. What this means is that this return value is in
- a format that works for Click to process but does not necessarily
- correspond well to what's actually standard for the interpreter.
- On most environments the return value is ``sys.argv[:1]`` unchanged.
- However if you are on Windows and running Python 2 the return value
- will actually be a list of unicode strings instead because the
- default behavior on that platform otherwise will not be able to
- carry all possible values that sys.argv can have.
- .. versionadded:: 6.0
- """
- # We can only extract the unicode argv if sys.argv has not been
- # changed since the startup of the application.
- if PY2 and WIN and _initial_argv_hash == _hash_py_argv():
- return _get_windows_argv()
- return sys.argv[1:]
- def format_filename(filename, shorten=False):
- """Formats a filename for user display. The main purpose of this
- function is to ensure that the filename can be displayed at all. This
- will decode the filename to unicode if necessary in a way that it will
- not fail. Optionally, it can shorten the filename to not include the
- full path to the filename.
- :param filename: formats a filename for UI display. This will also convert
- the filename into unicode without failing.
- :param shorten: this optionally shortens the filename to strip of the
- path that leads up to it.
- """
- if shorten:
- filename = os.path.basename(filename)
- return filename_to_ui(filename)
- def get_app_dir(app_name, roaming=True, force_posix=False):
- r"""Returns the config folder for the application. The default behavior
- is to return whatever is most appropriate for the operating system.
- To give you an idea, for an app called ``"Foo Bar"``, something like
- the following folders could be returned:
- Mac OS X:
- ``~/Library/Application Support/Foo Bar``
- Mac OS X (POSIX):
- ``~/.foo-bar``
- Unix:
- ``~/.config/foo-bar``
- Unix (POSIX):
- ``~/.foo-bar``
- Win XP (roaming):
- ``C:\Documents and Settings\<user>\Local Settings\Application Data\Foo Bar``
- Win XP (not roaming):
- ``C:\Documents and Settings\<user>\Application Data\Foo Bar``
- Win 7 (roaming):
- ``C:\Users\<user>\AppData\Roaming\Foo Bar``
- Win 7 (not roaming):
- ``C:\Users\<user>\AppData\Local\Foo Bar``
- .. versionadded:: 2.0
- :param app_name: the application name. This should be properly capitalized
- and can contain whitespace.
- :param roaming: controls if the folder should be roaming or not on Windows.
- Has no affect otherwise.
- :param force_posix: if this is set to `True` then on any POSIX system the
- folder will be stored in the home folder with a leading
- dot instead of the XDG config home or darwin's
- application support folder.
- """
- if WIN:
- key = "APPDATA" if roaming else "LOCALAPPDATA"
- folder = os.environ.get(key)
- if folder is None:
- folder = os.path.expanduser("~")
- return os.path.join(folder, app_name)
- if force_posix:
- return os.path.join(os.path.expanduser("~/.{}".format(_posixify(app_name))))
- if sys.platform == "darwin":
- return os.path.join(
- os.path.expanduser("~/Library/Application Support"), app_name
- )
- return os.path.join(
- os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")),
- _posixify(app_name),
- )
- class PacifyFlushWrapper(object):
- """This wrapper is used to catch and suppress BrokenPipeErrors resulting
- from ``.flush()`` being called on broken pipe during the shutdown/final-GC
- of the Python interpreter. Notably ``.flush()`` is always called on
- ``sys.stdout`` and ``sys.stderr``. So as to have minimal impact on any
- other cleanup code, and the case where the underlying file is not a broken
- pipe, all calls and attributes are proxied.
- """
- def __init__(self, wrapped):
- self.wrapped = wrapped
- def flush(self):
- try:
- self.wrapped.flush()
- except IOError as e:
- import errno
- if e.errno != errno.EPIPE:
- raise
- def __getattr__(self, attr):
- return getattr(self.wrapped, attr)
|