_unicodefun.py 4.1 KB
Newer Older
Vladislav Rykov's avatar
Vladislav Rykov committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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)
    )