Passed
Push — develop ( 2798d2...d1b463 )
by Dean
02:48
created

Environment.setup_locale()   D

Complexity

Conditions 8

Size

Total Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 65.3873

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 42
rs 4
ccs 1
cts 28
cp 0.0357
cc 8
crap 65.3873
1 1
from plugin.core.constants import PLUGIN_IDENTIFIER
2
3 1
import gettext
4 1
import locale
5 1
import logging
6 1
import os
7 1
import platform
8
9 1
DEFAULT_LOCALE = 'en_US'
10
11 1
log = logging.getLogger(__name__)
12
13
14 1
class PathEnvironment(object):
15
    # TODO confirm validity of this on *nix and OS X
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
16
17 1
    def __init__(self, core):
18 1
        self._core = core
19
20 1
        self._plugin_support = None
21
22 1
    @property
23
    def contents(self):
24 1
        return os.path.abspath(os.path.join(self.code, '..'))
25
26 1
    @property
27
    def code(self):
28 1
        return self._core.code_path
29
30 1
    @property
31
    def home(self):
32
        return os.path.abspath(os.path.join(self.plugin_support, '..'))
33
34 1
    @property
35
    def libraries(self):
36 1
        return os.path.join(self.contents, 'Libraries')
37
38 1
    @property
39
    def locale(self):
40
        return os.path.join(self.contents, 'Locale')
41
42 1
    @property
43
    def plugin_caches(self):
44 1
        return os.path.join(self.plugin_support, 'Caches', PLUGIN_IDENTIFIER)
45
46 1
    @property
47
    def plugin_data(self):
48 1
        return os.path.join(self.plugin_support, 'Data', PLUGIN_IDENTIFIER)
49
50 1
    @property
51
    def plugin_database(self):
52 1
        return os.path.join(self.plugin_support, 'Databases', '%s.db' % PLUGIN_IDENTIFIER)
53
54 1
    @property
55
    def plugin_support(self):
56 1
        if self._plugin_support is not None:
57 1
            return self._plugin_support
58
59
        base_path = self.code[:self.code.index(os.path.sep + 'Plug-ins')]
60
61
        return os.path.join(base_path, 'Plug-in Support')
62
63 1
    @plugin_support.setter
64
    def plugin_support(self, path):
65 1
        self._plugin_support = path
66
67
68 1
class PlatformEnvironment(object):
69 1
    def __init__(self, platform):
0 ignored issues
show
Comprehensibility Bug introduced by
platform is re-defining a name which is already available in the outer-scope (previously defined on line 7).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
70 1
        self._platform = platform
71
72 1
    @property
73
    def machine_identifier(self):
74
        return self._platform.MachineIdentifier
75
76 1
    @property
77
    def server_version(self):
78
        return self._platform.ServerVersion
79
80
81 1
class Environment(object):
82 1
    dict = None
83 1
    path = None
84 1
    platform = None
85 1
    prefs = None
86
87 1
    language = None
88 1
    translation = None
89
90 1
    @classmethod
91
    def setup(cls, core, dict, platform, prefs):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in dict.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
Comprehensibility Bug introduced by
platform is re-defining a name which is already available in the outer-scope (previously defined on line 7).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
92 1
        cls.path = PathEnvironment(core)
93 1
        cls.dict = dict
94 1
        cls.platform = PlatformEnvironment(platform)
95 1
        cls.prefs = prefs
96
97 1
    @classmethod
98
    def setup_locale(cls):
99
        # Use language defined in preferences (if available)
100
        language = cls.get_pref('language') or ''
101
102
        # Initialize locale
103
        try:
104
            locale.setlocale(locale.LC_ALL, language)
105
        except ValueError as ex:
106
            # Ignore locale emulation exceptions
107
            if ex.message == '_locale emulation only supports "C" locale':
0 ignored issues
show
Bug introduced by
The Instance of ValueError does not seem to have a member named message.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
108
                log.info('Locale extension not available, using the "C" locale')
109
                return False
110
111
            # Unknown exception
112
            log.warn('Unable to set locale to %r: %s', language, ex, exc_info=True)
113
            return False
114
        except Exception as ex:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
115
            log.warn('Unable to set locale to %r: %s', language, ex, exc_info=True)
116
            return False
117
118
        # Default to the "en_US" locale
119
        code, _ = locale.getlocale()
120
121
        if not code:
122
            try:
123
                locale.setlocale(locale.LC_ALL, DEFAULT_LOCALE)
124
            except ValueError as ex:
125
                # Ignore locale emulation exceptions
126
                if ex.message == '_locale emulation only supports "C" locale':
0 ignored issues
show
Bug introduced by
The Instance of ValueError does not seem to have a member named message.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
Bug introduced by
The Instance of Exception does not seem to have a member named message.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
127
                    log.info('Locale extension not available, using the "C" locale')
128
                    return False
129
130
                # Unknown exception
131
                log.warn('Unable to set locale to %r: %s', language, ex, exc_info=True)
132
                return False
133
            except Exception as ex:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
134
                log.warn('Unable to set locale to %r: %s', DEFAULT_LOCALE, ex, exc_info=True)
135
                return False
136
137
        log.info('Using locale: %r', locale.getlocale())
138
        return True
139
140 1
    @classmethod
141
    def setup_translation(cls):
142
        # Retrieve preferred language
143
        try:
144
            cls.language = cls._get_language()
145
        except Exception as ex:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
146
            log.warn('Unable to retrieve preferred language: %s', ex, exc_info=True)
147
            cls.language = None
148
            return
149
150
        if not cls.language:
151
            log.warn('Unable to determine preferred language (system: %r)', platform.system())
152
            return
153
154
        # Build list of languages
155
        languages = [cls.language]
156
157
        if '_' in cls.language:
158
            languages.append(cls.language.split('_', 1)[0])
159
160
        # Check if language exists
161
        found = False
162
163
        for lang in languages:
164
            if os.path.exists(os.path.join(cls.path.locale, lang)):
165
                found = True
166
                break
167
168
        if not found:
169
            log.info('No translation available for %r', languages)
170
            return
171
172
        # Setup gettext
173
        try:
174
            cls.translation = gettext.translation(
175
                domain='channel',
176
                localedir=os.path.join(cls.path.locale),
177
                languages=languages
178
            )
179
        except Exception as ex:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
180
            log.warn('Unable to initialize languages: %r - %s', languages, ex, exc_info=True)
181
            return
182
183
        log.info('Using languages: %r (translation: %r)', languages, cls.translation)
184
185 1
    @classmethod
186
    def get_pref(cls, key):
187 1
        try:
188 1
            return cls.prefs[key]
189
        except:
0 ignored issues
show
Coding Style Best Practice introduced by
General except handlers without types should be used sparingly.

Typically, you would use general except handlers when you intend to specifically handle all types of errors, f.e. when logging. Otherwise, such general error handlers can mask errors in your application that you want to know of.

Loading history...
190
            return None
191
192 1
    @classmethod
193
    def _get_language(cls):
194
        # Use language defined in preferences (if available)
195
        language = cls.get_pref('language')
196
197
        if language:
198
            return language.lower()
199
200
        # Use system language
201
        if platform.system() == 'Windows':
202
            # Retrieve windows user language
203
            return cls._get_windows_default_language()
204
205
        # Retrieve current locale
206
        code, _ = locale.getdefaultlocale()
207
208
        # Ensure language code is valid
209
        if not code or type(code) is not str:
210
            log.info('Unable to detect system language, defaulting to the "%s" locale', DEFAULT_LOCALE)
211
            return DEFAULT_LOCALE.lower()
212
213
        # Parse language code
214
        if len(code) == 2:
215
            return code.lower()
216
        elif len(code) > 2 and code[2] == '_':
217
            return code[:5].lower()
218
219
        log.warn('Unknown language code: %r', code)
220
        return None
221
222 1
    @classmethod
223
    def _get_windows_default_language(cls):
224
        try:
225
            import ctypes
226
            lang_id = ctypes.windll.kernel32.GetUserDefaultUILanguage()
227
        except Exception as ex:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
228
            log.warn('Unable to determine preferred language: %s', ex, exc_info=True)
229
            return None
230
231
        if lang_id not in locale.windows_locale:
232
            log.warn('Unknown language: %r', lang_id)
233
            return None
234
235
        return locale.windows_locale[lang_id].lower()
236
237
238 1
def translate(message):
239 1
    if Environment.translation:
240
        return Environment.translation.ugettext(message)
0 ignored issues
show
Bug introduced by
The Instance of NullTranslations does not seem to have a member named ugettext.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
241
242
    return message
243