aboutsummaryrefslogtreecommitdiffstats
path: root/python/click/_unicodefun.py
blob: 620edff37e260e244108bdabddd0b3434a6d3d31 (plain)
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
import os
import sys
import codecs

from ._compat import PY2


# If someone wants to vendor click, we want to ensure the
# correct package is discovered.  Ideally we could use a
# relative import here but unfortunately Python does not
# support that.
click = sys.modules[__name__.rsplit('.', 1)[0]]


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
    if not PY2 or click.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\n'
                'locales were discovered.  This most likely requires resolving\n'
                'by reconfiguring the locale system.'
            )
        elif has_c_utf8:
            extra += (
                'This system supports the C.UTF-8 locale which is recommended.\n'
                'You might be able to resolve your issue by exporting the\n'
                '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\n'
                'you can pick from.  The following suitable locales were\n'
                'discovered: %s'
            ) % ', '.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\n'
                'but the locale system could not pick up from it because\n'
                'it does not exist.  The exported locale is "%s" but it\n'
                'is not supported'
            ) % 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/en/7.x/python3/ for'
        ' mitigation steps.' + extra
    )