Test Failed
Pull Request — master (#2)
by Heiko 'riot'
06:45
created

isomer.misc.std_human_uid()   A

Complexity

Conditions 3

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 12
nop 1
dl 0
loc 18
rs 9.8
c 0
b 0
f 0
1
#!/usr/bin/env python
2
# -*- coding: UTF-8 -*-
3
4
# Isomer - The distributed application framework
5
# ==============================================
6
# Copyright (C) 2011-2020 Heiko 'riot' Weinen <[email protected]> and others.
7
#
8
# This program is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU Affero General Public License as published by
10
# the Free Software Foundation, either version 3 of the License, or
11
# (at your option) any later version.
12
#
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
# GNU Affero General Public License for more details.
17
#
18
# You should have received a copy of the GNU Affero General Public License
19
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
21
"""
22
Miscellaneous utility functions for Isomer
23
"""
24
25
import gettext
26
import json
27
import os
28
import copy
29
import re
30
31
from isomer.logger import isolog, verbose, warn
32
33
localedir = os.path.abspath(
34
    os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "..", "locale")
35
)
36
37
38
def l10n_log(*args, **kwargs):
39
    """Log as L10N emitter"""
40
41
    kwargs.update({"emitter": "L10N", "frame_ref": 2})
42
    isolog(*args, **kwargs)
43
44
45
def all_languages():
46
    """Compile a list of all available language translations"""
47
48
    rv = []
49
50
    for lang in os.listdir(localedir):
51
        base = str(lang).split("_")[0].split(".")[0].split("@")[0]
52
        if 2 <= len(base) <= 3 and all(c.islower() for c in base):
53
            if base != "all":
54
                rv.append(lang)
55
    rv.sort()
56
    rv.append("en")
57
    l10n_log("Registered languages:", rv, lvl=verbose)
58
59
    return rv
60
61
62
def language_token_to_name(languages):
63
    """Get a descriptive title for all languages"""
64
65
    result = {}
66
67
    with open(os.path.join(localedir, "languages.json"), "r") as f:
68
        language_lookup = json.load(f)
69
70
    for language in languages:
71
        language = language.lower()
72
        try:
73
            result[language] = language_lookup[language]
74
        except KeyError:
75
            l10n_log("Language token lookup not found:", language, lvl=warn)
76
            result[language] = language
77
78
    return result
79
80
81
class Domain:
82
    """Gettext domain capable of translating into all registered languages"""
83
84
    def __init__(self, domain):
85
        self._domain = domain
86
        self._translations = {}
87
88
    def _get_translation(self, lang):
89
        """Add a new translation language to the live gettext translator"""
90
91
        try:
92
            return self._translations[lang]
93
        except KeyError:
94
            # The fact that `fallback=True` is not the default is a serious design flaw.
95
            rv = self._translations[lang] = gettext.translation(
96
                self._domain, localedir=localedir, languages=[lang], fallback=True
97
            )
98
            return rv
99
100
    def get(self, lang, msg):
101
        """Return a message translated to a specified language"""
102
103
        return self._get_translation(lang).gettext(msg)
104
105
106
def print_messages(domain, msg):
107
    """Debugging function to print all message language variants"""
108
109
    domain = Domain(domain)
110
    for lang in all_languages():
111
        l10n_log(lang, ":", domain.get(lang, msg))
112
113
114
def i18n(msg, event=None, lang="en", domain="backend"):
115
    """Gettext function wrapper to return a message in a specified language by
116
    domain
117
118
    To use internationalization (i18n) on your messages, import it as '_' and
119
    use as usual. Do not forget to supply the client's language setting."""
120
121
    if event is not None:
122
        language = event.client.language
123
    else:
124
        language = lang
125
126
    domain = Domain(domain)
127
    return domain.get(language, msg)
128
129
130
def nested_map_find(d, keys):
131
    """Looks up a nested dictionary by traversing a list of keys"""
132
133
    if isinstance(keys, str):
134
        keys = keys.split(".")
135
    rv = d
136
    for key in keys:
137
        rv = rv[key]
138
    return rv
139
140
141
def nested_map_update(d, u, *keys):
142
    """Modifies a nested dictionary by traversing a list of keys"""
143
    d = copy.deepcopy(d)
144
    keys = keys[0]
145
    if len(keys) > 1:
146
        d[keys[0]] = nested_map_update(d[keys[0]], u, keys[1:])
147
    else:
148
        if u is not None:
149
            d[keys[0]] = u
150
        else:
151
            del d[keys[0]]
152
    return d
153
154
155
# TODO: Somehow these two fail the tests, although they seem to work fine
156
#   in non-test situations.
157
158
# def nested_map_find(data_dict, map_list):
159
#     """Looks up a nested dictionary by traversing a list of keys
160
#
161
#     :param dict data_dict: Nested dictionary to traverse
162
#     :param list map_list: List of keys to traverse along
163
#     :return object: The resulting value or None if not found
164
#     """
165
#
166
#     return reduce(operator.getitem, map_list, data_dict)
167
#
168
#
169
# def nested_map_update(data_dict, value, map_list):
170
#     """Modifies a nested dictionary by traversing a list of keys
171
#
172
#     :param dict data_dict: Nested dictionary to traverse
173
#     :param object value: New value to set at found key
174
#     :param list map_list: List of keys to traverse along
175
#     :return object: The resulting value or None if not found
176
#     """
177
#
178
#     nested_map_find(data_dict, map_list[:-1])[map_list[-1]] = value
179
180
181
def sorted_alphanumerical(l, reverse=False):
182
    """ Sort the given iterable in the way that humans expect."""
183
184
    # From: http://stackoverflow.com/questions/2669059/ddg#2669120
185
186
    converted = lambda text: int(text) if text.isdigit() else text
187
    alphanumerical_key = lambda key: [converted(c) for c in re.split('([0-9]+)', key)]
188
    return sorted(l, key=alphanumerical_key, reverse=reverse)
189
190
191
logo = """
192
                   .'::'.
193
                .':cccccc:'.
194
             .':cccccccccccc:'.
195
          .':ccccccc;..;ccccccc:'.
196
       .':ccccccc;.      .;ccccccc:'.
197
    .':ccccccc;.            .;ccccccc:'.
198
   ;cccccccc:.                .:cccccccc;
199
     .:ccccccc;.            .;ccccccc:'
200
        .:ccccccc;.      .;ccccccc:.
201
           .:ccccccc;..;ccccccc:.
202
              .;cccccccccccc:.
203
                 .;cccccc:.
204
                    .;;.
205
                                             """
206