_unicodefun.py 4.1 KB
import codecs
import os
import sys

from ._compat import PY2


def _find_unicode_literals_frame():
    import __future__

    if not hasattr(sys, "_getframe"):  # not all Python implementations have it
        return 0
    frm = sys._getframe(1)
    idx = 1
    while frm is not None:
        if frm.f_globals.get("__name__", "").startswith("click."):
            frm = frm.f_back
            idx += 1
        elif frm.f_code.co_flags & __future__.unicode_literals.compiler_flag:
            return idx
        else:
            break
    return 0


def _check_for_unicode_literals():
    if not __debug__:
        return

    from . import disable_unicode_literals_warning

    if not PY2 or disable_unicode_literals_warning:
        return
    bad_frame = _find_unicode_literals_frame()
    if bad_frame <= 0:
        return
    from warnings import warn

    warn(
        Warning(
            "Click detected the use of the unicode_literals __future__"
            " import. This is heavily discouraged because it can"
            " introduce subtle bugs in your code. You should instead"
            ' use explicit u"" literals for your unicode strings. For'
            " more information see"
            " https://click.palletsprojects.com/python3/"
        ),
        stacklevel=bad_frame,
    )


def _verify_python3_env():
    """Ensures that the environment is good for unicode on Python 3."""
    if PY2:
        return
    try:
        import locale

        fs_enc = codecs.lookup(locale.getpreferredencoding()).name
    except Exception:
        fs_enc = "ascii"
    if fs_enc != "ascii":
        return

    extra = ""
    if os.name == "posix":
        import subprocess

        try:
            rv = subprocess.Popen(
                ["locale", "-a"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
            ).communicate()[0]
        except OSError:
            rv = b""
        good_locales = set()
        has_c_utf8 = False

        # Make sure we're operating on text here.
        if isinstance(rv, bytes):
            rv = rv.decode("ascii", "replace")

        for line in rv.splitlines():
            locale = line.strip()
            if locale.lower().endswith((".utf-8", ".utf8")):
                good_locales.add(locale)
                if locale.lower() in ("c.utf8", "c.utf-8"):
                    has_c_utf8 = True

        extra += "\n\n"
        if not good_locales:
            extra += (
                "Additional information: on this system no suitable"
                " UTF-8 locales were discovered. This most likely"
                " requires resolving by reconfiguring the locale"
                " system."
            )
        elif has_c_utf8:
            extra += (
                "This system supports the C.UTF-8 locale which is"
                " recommended. You might be able to resolve your issue"
                " by exporting the following environment variables:\n\n"
                "    export LC_ALL=C.UTF-8\n"
                "    export LANG=C.UTF-8"
            )
        else:
            extra += (
                "This system lists a couple of UTF-8 supporting locales"
                " that you can pick from. The following suitable"
                " locales were discovered: {}".format(", ".join(sorted(good_locales)))
            )

        bad_locale = None
        for locale in os.environ.get("LC_ALL"), os.environ.get("LANG"):
            if locale and locale.lower().endswith((".utf-8", ".utf8")):
                bad_locale = locale
            if locale is not None:
                break
        if bad_locale is not None:
            extra += (
                "\n\nClick discovered that you exported a UTF-8 locale"
                " but the locale system could not pick up from it"
                " because it does not exist. The exported locale is"
                " '{}' but it is not supported".format(bad_locale)
            )

    raise RuntimeError(
        "Click will abort further execution because Python 3 was"
        " configured to use ASCII as encoding for the environment."
        " Consult https://click.palletsprojects.com/python3/ for"
        " mitigation steps.{}".format(extra)
    )