-
Notifications
You must be signed in to change notification settings - Fork 79
/
Copy pathinternal.py
775 lines (641 loc) · 28.7 KB
/
internal.py
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
import os.path
from functools import wraps
from importlib import import_module
from inspect import signature
from warnings import warn
from datetime import datetime
from lingua_franca import config
from lingua_franca.time import to_local
_SUPPORTED_LANGUAGES = ("ca", "cs", "da", "de", "en", "es", "fr", "hu",
"it", "nl", "pl", "pt", "ru", "sl", "sv", "fa", "eu")
_SUPPORTED_FULL_LOCALIZATIONS = ("ca-es", "cs-cz", "da-dk", "de-de",
"en-au", "en-us", "es-es", "fr-fr",
"hu-hu", "it-it", "nl-nl", "pl-pl",
"fa-ir", "pt-pt", "ru-ru", "sl-si",
"sv-se", "tr-tr", "eu-eu")
_DEFAULT_FULL_LANG_CODES = {'ca': 'ca-es',
'cs': 'cs-cz',
'da': 'da-dk',
'de': 'de-de',
'en': 'en-us',
'es': 'es-es',
'eu': 'eu-eu',
'fa': 'fa-ir',
'fr': 'fr-fr',
'hu': 'hu-hu',
'it': 'it-it',
'nl': 'nl-nl',
'pl': 'pl-pl',
'pt': 'pt-pt',
'ru': 'ru-ru',
'sl': 'sl-si',
'sv': 'sv-se',
'tr': 'tr-tr'}
__default_lang = None
__active_lang_code = None
__loaded_langs = []
_localized_functions = {}
# TODO the deprecation of 'lang=None' and 'lang=<invalid>' can refer to
# commit 35efd0661a178e82f6745ad17e10e607c0d83472 for the "proper" state
# of affairs, raising the errors below instead of deprecation warnings
# Once the deprecation is complete, functions which have had their default
# parameter changed from lang=None to lang='' should be switched back
class UnsupportedLanguageError(NotImplementedError):
pass
class FunctionNotLocalizedError(NotImplementedError):
pass
NoneLangWarning = \
DeprecationWarning("Lingua Franca is dropping support"
" for 'lang=None' as an explicit"
" argument.")
InvalidLangWarning = \
DeprecationWarning("Invalid language code detected. Falling back on "
"default.\nThis behavior is deprecated. The 'lang' "
"parameter is optional, and only accepts supported "
"language codes, beginning with Lingua Franca 0.3.0")
def _raise_unsupported_language(language):
"""
Raise an error when a language is unsupported
Arguments:
language: str
The language that was supplied.
"""
supported = ' '.join(_SUPPORTED_LANGUAGES)
raise UnsupportedLanguageError("\nLanguage '{language}' is not yet "
"supported by Lingua Franca. "
"Supported language codes "
"include the following:\n{supported}"
.format(language=language, supported=supported))
def get_supported_langs():
"""
Returns:
list(str)
"""
return _SUPPORTED_LANGUAGES
def get_active_langs():
""" Get the list of currently-loaded language codes
Returns:
list(str)
"""
return __loaded_langs
def _set_active_langs(langs=None, override_default=True):
""" Set the list of languages to load.
Unloads previously-loaded languages which are not specified here.
If the input list does not contain the current default language,
langs[0] will become the new default language. This behavior
can be overridden.
Arguments:
langs: {list(str) or str} -- a list of language codes to load
Keyword Arguments:
override_default (bool) -- Change default language to first entry if
the current default is no longer present
(default: True)
"""
if isinstance(langs, str):
langs = [langs]
if not isinstance(langs, list):
raise(TypeError("lingua_franca.internal._set_active_langs expects"
" 'str' or 'list'"))
global __loaded_langs, __default_lang
__loaded_langs = list(dict.fromkeys(langs))
if __default_lang:
if override_default or get_primary_lang_code(__default_lang) \
not in __loaded_langs:
if len(__loaded_langs):
set_default_lang(get_full_lang_code(__loaded_langs[0]))
else:
__default_lang = None
_refresh_function_dict()
def _refresh_function_dict():
for mod in _localized_functions.keys():
populate_localized_function_dict(mod, langs=__loaded_langs)
def is_supported_lang(lang):
try:
return lang.lower() in _SUPPORTED_LANGUAGES
except AttributeError:
return False
def is_supported_full_lang(lang):
"""
Arguments:
lang (str): a full language code, such as "en-US" (case insensitive)
Returns:
bool - does Lingua Franca support this language code?
"""
try:
return lang.lower() in _SUPPORTED_FULL_LOCALIZATIONS
except AttributeError:
return False
def load_language(lang):
"""Load `lang` and its functions into memory. Will only import those
functions which belong to a loaded module. In other words, if you have
lingua_franca.parse loaded, but *not* lingua_franca.format,
running `load_language('es') will only import the Spanish-language
parsers, and not the formatters.
The reverse is also true: importing a module, such as
`import lingua_franca.parse`, will only import those functions
which belong to currently-loaded languages.
Arguments:
lang (str): the language code to load (any supported lang code,
whether 'primary' or 'full')
Case-insensitive.
"""
if not isinstance(lang, str):
raise TypeError("lingua_franca.load_language expects 'str' "
"(got " + type(lang) + ")")
if lang not in _SUPPORTED_LANGUAGES:
if lang in _SUPPORTED_FULL_LOCALIZATIONS:
lang = get_primary_lang_code(lang)
if lang not in __loaded_langs:
__loaded_langs.append(lang)
if not __default_lang:
set_default_lang(lang)
_set_active_langs(__loaded_langs)
def load_languages(langs):
"""Load multiple languages at once
Simple for loop using load_language()
Args:
langs (list[str])
"""
for lang in langs:
load_language(lang)
def unload_language(lang):
"""Opposite of load_language()
Unloading the default causes the next language in
`lingua_franca.get_active_langs()` to become the default.
Will not stop you from unloading the last language, as this may be
desirable for some applications.
Args:
lang (str): language code to unload
"""
if lang in __loaded_langs:
__loaded_langs.remove(lang)
_set_active_langs(__loaded_langs)
def unload_languages(langs):
"""Opposite of load_languages()
Simple for loop using unload_language()
Args:
langs (list[str])
"""
for lang in langs:
__loaded_langs.remove(lang)
_set_active_langs(__loaded_langs)
def get_default_lang():
""" Return the current default language.
This returns the active BCP-47 code, such as 'en' or 'es'.
For the current localization/full language code,
such as 'en-US' or 'es-ES', call `get_default_loc()`
See:
https://en.wikipedia.org/wiki/IETF_language_tag
Returns:
str: A primary language code, e.g. ("en", or "pt")
"""
return __default_lang
def get_default_loc():
""" Return the current, localized BCP-47 language code, such as 'en-US'
or 'es-ES'. For the default language *family* - which is passed to
most parsers and formatters - call `get_default_lang`
The 'localized' portion conforms to ISO 3166-1 alpha-2
https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
"""
return __active_lang_code
def set_default_lang(lang_code):
""" Set the active BCP-47 language code to be used in formatting/parsing
Will choose a default localization if passed a primary language family
(ex: `set_default_lang("en")` will default to "en-US")
Will respect localization when passed a full lang code.
For more information about valid lang codes, see get_default_lang()
and get_default_loc()
Args:
lang(str): BCP-47 language code, e.g. "en-us" or "es-mx"
"""
global __default_lang, __active_lang_code
lang_code = lang_code.lower()
primary_lang_code = get_primary_lang_code(lang_code)
if primary_lang_code not in _SUPPORTED_LANGUAGES:
_raise_unsupported_language(lang_code)
else:
__default_lang = primary_lang_code
# make sure the default language is loaded.
# also make sure the default language is at the front.
# position doesn't matter here, but it clarifies things while debugging.
if __default_lang in __loaded_langs:
__loaded_langs.remove(__default_lang)
__loaded_langs.insert(0, __default_lang)
_refresh_function_dict()
if is_supported_full_lang(lang_code):
__active_lang_code = lang_code
else:
__active_lang_code = get_full_lang_code(__default_lang)
# TODO remove this when invalid lang codes are removed (currently deprecated)
def get_primary_lang_code(lang=''):
if not lang:
if lang is None:
warn(NoneLangWarning)
lang = get_default_loc()
# if not (lang):
try:
lang = __get_primary_lang_code_deprecation_warning(lang)
except UnsupportedLanguageError:
warn(InvalidLangWarning)
lang = get_default_loc()
return lang
def __get_primary_lang_code_deprecation_warning(lang=''):
""" Get the primary language code
Args:
lang(str, optional): A BCP-47 language code
(If omitted, equivalent to
`lingua_franca.get_default_lang()`)
Returns:
str: A primary language family, such as "en", "de" or "pt"
"""
# split on the hyphen and only return the primary-language code
# NOTE: This is typically a two character code. The standard allows
# 1, 2, 3 and 4 character codes. In the future we can consider
# mapping from the 3 to 2 character codes, for example. But for
# now we can just be careful in use.
if not lang:
return get_default_lang()
elif not isinstance(lang, str):
raise(TypeError("lingua_franca.get_primary_lang_code() expects"
" an (optional)argument of type 'str', but got " +
type(lang)))
else:
lang_code = lang.lower()
if lang_code not in _SUPPORTED_FULL_LOCALIZATIONS and lang_code not in \
_SUPPORTED_LANGUAGES:
# We don't know this language code. Check if the input is
# formatted like a language code.
if lang == (("-".join([lang[:2], lang[3:]]) or None)):
warn("Unrecognized language code: '" + lang + "', but it appears "
"to be a valid language code. Returning the first two chars.")
return lang_code.split("-")[0]
else:
raise(ValueError("Invalid input: " + lang))
return lang_code.split("-")[0]
# TODO remove this when invalid lang codes are removed (currently deprecated)
def get_full_lang_code(lang=''):
if not lang:
if lang is None:
warn(NoneLangWarning)
lang = get_default_loc()
if not is_supported_full_lang(lang):
try:
lang = __get_full_lang_code_deprecation_warning(lang)
except UnsupportedLanguageError:
warn(InvalidLangWarning)
lang = get_default_loc()
return lang
def __get_full_lang_code_deprecation_warning(lang=''):
""" Get the full language code
Args:
lang(str, optional): A BCP-47 language code
(if omitted, equivalent to
`lingua_franca.get_default_loc()`)
Returns:
str: A full language code, such as "en-us" or "de-de"
"""
if lang is None:
return __active_lang_code.lower()
elif not isinstance(lang, str):
raise TypeError("get_full_lang_code expects str, "
"got {}".format(type(lang)))
if lang.lower() in _SUPPORTED_FULL_LOCALIZATIONS:
return lang
elif lang in _DEFAULT_FULL_LANG_CODES:
return _DEFAULT_FULL_LANG_CODES[lang]
else:
raise UnsupportedLanguageError(lang)
def localized_function(run_own_code_on=[type(None)]):
"""
Decorator which finds localized functions, and calls them, from signatures
defined in the top-level modules. See lingua_franca.format or .parse for
examples of the decorator in action.
Note that, by default, wrapped functions will never actually be executed.
Rather, when they're called, their arguments will be passed directly to
their localized equivalent, specified by the 'lang' parameter.
The wrapper can be instructed to execute the wrapped function itself when
a specified error is raised (see the argument 'run_own_code_on')
For instance, this decorator wraps parse.extract_number(), which has no
logic of its own. A call to
extract_number('uno', lang='es')
will locate and call
lingua_franca.lang.parse_es.extract_number_es('uno')
By contrast, here's the decorator above format.nice_number, with the param:
@localized_function(run_own_code_on=[UnsupportedLanguageError])
def nice_number(number, lang='', speech=True, denominators=None):
Here, nice_number() itself will be executed in the event that the localizer
raises an UnsupportedLanguageError.
Arguments:
run_own_code_on(list(type), optional)
A list of Error types (ValueError, NotImplementedError, etc)
which, if they are raised, will trigger the wrapped function's
own code.
If this argument is omitted, the function itself will never
be run. Calls to the wrapped function will be passed to the
appropriate, localized function.
"""
# Make sure everything in run_own_code_on is an Error or None
BadTypeError = \
ValueError("@localized_function(run_own_code_on=<>) expected an "
"Error type, or a list of Error types. Instead, it "
"received this value:\n" + str(run_own_code_on))
# TODO deprecate these kwarg values 6-12 months after v0.3.0 releases
if run_own_code_on != [None]:
def is_error_type(_type):
if not callable(_type):
return False
_instance = _type()
rval = isinstance(_instance, BaseException) if _instance else True
del _instance
return rval
if not isinstance(run_own_code_on, list):
try:
run_own_code_on = list(run_own_code_on)
except TypeError:
raise BadTypeError
if not all((is_error_type(e) for e in run_own_code_on)):
raise BadTypeError
# Begin wrapper
def localized_function_decorator(func):
# Wrapper's logic
def _call_localized_function(func, *args, **kwargs):
lang_code = None
load_langs_on_demand = config.load_langs_on_demand
unload_language_afterward = False
func_signature = signature(func)
func_params = list(func_signature.parameters)
lang_param_index = func_params.index('lang')
full_lang_code = None
# Check if we need to add timezone awareness to any datetime object
if config.inject_timezones:
for key, value in kwargs.items():
if isinstance(value, datetime) and value.tzinfo is None:
kwargs[key] = to_local(value)
for idx, value in enumerate(args):
if isinstance(value, datetime) and value.tzinfo is None:
args = (*args[:idx], to_local(value), *args[idx + 1:])
# Check if we're passing a lang as a kwarg
if 'lang' in kwargs.keys():
lang_param = kwargs['lang']
if lang_param is None:
warn(NoneLangWarning)
lang_code = get_default_lang()
else:
lang_code = lang_param
# Check if we're passing a lang as a positional arg
elif lang_param_index < len(args):
lang_param = args[lang_param_index]
if lang_param is None:
warn(NoneLangWarning)
lang_code = get_default_lang()
elif lang_param in _SUPPORTED_LANGUAGES or \
lang_param in _SUPPORTED_FULL_LOCALIZATIONS:
lang_code = args[lang_param_index]
args = args[:lang_param_index] + args[lang_param_index+1:]
# Turns out, we aren't passing a lang code at all
lang_code = lang_code or get_default_lang()
if not lang_code:
if load_langs_on_demand:
raise ModuleNotFoundError("No language module loaded "
"and none specified.")
else:
raise ModuleNotFoundError("No language module loaded.")
if lang_code not in _SUPPORTED_LANGUAGES:
try:
tmp = lang_code
__use_tmp = True
lang_code = get_primary_lang_code(lang_code)
except ValueError:
__error = \
UnsupportedLanguageError("\nLanguage '{language}' is not yet "
"supported by Lingua Franca. "
"Supported language codes "
"include the following:\n{supported}"
.format(
language=lang_code,
supported=_SUPPORTED_FULL_LOCALIZATIONS))
if UnsupportedLanguageError in run_own_code_on:
raise __error
else:
warn(DeprecationWarning("The following warning will "
"become an exception in a future "
"version of Lingua Franca." +
str(__error)))
lang_code = get_default_lang()
full_lang_code = get_full_lang_code()
__use_tmp = False
if lang_code not in _SUPPORTED_LANGUAGES:
_raise_unsupported_language(lang_code)
if __use_tmp:
full_lang_code = tmp
else:
full_lang_code = get_full_lang_code(lang_code)
# Here comes the ugly business.
_module_name = func.__module__.split('.')[-1]
_module = import_module(".lang." + _module_name +
"_" + lang_code, "lingua_franca")
# The nonsense above gets you from lingua_franca.parse
# to lingua_franca.lang.parse_xx
if _module_name not in _localized_functions.keys():
raise ModuleNotFoundError("Module lingua_franca." +
_module_name + " not recognized")
if lang_code not in _localized_functions[_module_name].keys():
if load_langs_on_demand:
load_language(lang_code)
unload_language_afterward = True
else:
raise ModuleNotFoundError(_module_name +
" module of language '" +
lang_code +
"' is not currently loaded.")
func_name = func.__name__.split('.')[-1]
# At some point in the past, both the module and the language
# were imported/loaded, respectively.
# When that happened, we cached the *signature* of each
# localized function.
#
# This is the crucial element that allows us to import funcs
# on the fly.
#
# If we didn't find a localized function to correspond with
# the wrapped function, we cached NotImplementedError in its
# place.
loc_signature = _localized_functions[_module_name][lang_code][func_name]
if isinstance(loc_signature, type(NotImplementedError())):
raise loc_signature
# Now we have the appropriate localized module. Let's get
# the localized function.
try:
localized_func = getattr(
_module, func_name + "_" + lang_code)
except AttributeError:
raise FunctionNotLocalizedError(func_name, lang_code)
# We now have a localized function, such as
# lingua_franca.parse.extract_datetime_en
# Get 'lang' out of its parameters.
if 'lang' in kwargs:
del kwargs['lang']
args = tuple(arg for arg in list(args) if
arg not in (lang_code, full_lang_code))
# Now we call the function, ignoring any kwargs from the
# wrapped function that aren't in the localized function.
r_val = localized_func(*args,
**{arg: val for arg, val
in kwargs.items()
if arg in loc_signature.parameters})
# Unload all the stuff we just assembled and imported
del localized_func
del _module
if unload_language_afterward:
unload_language(lang_code)
return r_val
# Actual wrapper
@wraps(func)
def call_localized_function(*args, **kwargs):
if run_own_code_on != [type(None)]:
try:
return _call_localized_function(func, *args, **kwargs)
except Exception as e: # Intercept, check for run_own_code_on
if any((isinstance(e, error) for error in run_own_code_on)):
return func(*args, **kwargs)
else:
raise e
else: # don't intercept any exceptions
return _call_localized_function(func, *args, **kwargs)
return call_localized_function
try:
return localized_function_decorator
except NotImplementedError as e:
warn(str(e))
return
def populate_localized_function_dict(lf_module, langs=get_active_langs()):
"""Returns a dictionary of dictionaries, containing localized functions.
Used by the top-level modules to locate, cache, and call localized funcs.
Arguments:
lf_module(str) - - the name of the top-level module
Returns:
Dict - - {language_code: {function_name(str): function}}
Note:
The dictionary returned can be used directly,
but it's normally discarded. Rather, this function will create
the dictionary as a member of
`lingua_franca.internal._localized_functions`,
and its members are invoked via the `@localized_function` decorator.
Example:
populate_localized_function_dict("format")["en"]["pronounce_number"](1)
"one"
"""
bad_lang_code = "Language code '{}' is registered with" \
" Lingua Franca, but its " + lf_module + " module" \
" could not be found."
return_dict = {}
for lang_code in langs:
primary_lang_code = get_primary_lang_code(lang_code)
return_dict[primary_lang_code] = {}
_FUNCTION_NOT_FOUND = ""
try:
lang_common_data = import_module(".lang.common_data_" + primary_lang_code,
"lingua_franca")
_FUNCTION_NOT_FOUND = getattr(lang_common_data,
"_FUNCTION_NOT_IMPLEMENTED_WARNING")
del lang_common_data
except Exception:
_FUNCTION_NOT_FOUND = "This function has not been implemented" \
" in the specified language."
_FUNCTION_NOT_FOUND = FunctionNotLocalizedError(_FUNCTION_NOT_FOUND)
try:
mod = import_module(".lang." + lf_module + "_" + primary_lang_code,
"lingua_franca")
except ModuleNotFoundError:
warn(Warning(bad_lang_code.format(primary_lang_code)))
continue
function_names = getattr(import_module("." + lf_module, "lingua_franca"),
"_REGISTERED_FUNCTIONS")
for function_name in function_names:
try:
function = getattr(mod, function_name
+ "_" + primary_lang_code)
function_signature = signature(function)
del function
except AttributeError:
function_signature = _FUNCTION_NOT_FOUND
# TODO log these occurrences: "function 'function_name' not
# implemented in language 'primary_lang_code'"
#
# Perhaps provide this info to autodocs, to help volunteers
# identify the functions in need of localization
return_dict[primary_lang_code][function_name] = function_signature
del mod
_localized_functions[lf_module] = return_dict
return _localized_functions[lf_module]
def resolve_resource_file(res_name, data_dir=None):
"""Convert a resource into an absolute filename.
Resource names are in the form: 'filename.ext'
or 'path/filename.ext'
The system wil look for ~/.mycroft/res_name first, and
if not found will look at / opt/mycroft/res_name,
then finally it will look for res_name in the 'mycroft/res'
folder of the source code package.
Example:
With mycroft running as the user 'bob', if you called
resolve_resource_file('snd/beep.wav')
it would return either '/home/bob/.mycroft/snd/beep.wav' or
'/opt/mycroft/snd/beep.wav' or '.../mycroft/res/snd/beep.wav',
where the '...' is replaced by the path where the package has
been installed.
Args:
res_name(str): a resource path/name
Returns:
str: path to resource or None if no resource found
"""
# First look for fully qualified file (e.g. a user setting)
if os.path.isfile(res_name):
return res_name
# Now look for ~/.mycroft/res_name (in user folder)
filename = os.path.expanduser("~/.mycroft/" + res_name)
if os.path.isfile(filename):
return filename
# Next look for /opt/mycroft/res/res_name
data_dir = data_dir or os.path.expanduser("/opt/mycroft/res/")
filename = os.path.expanduser(os.path.join(data_dir, res_name))
if os.path.isfile(filename):
return filename
# Finally look for it in the source package
filename = os.path.join(os.path.dirname(__file__), 'res', res_name)
filename = os.path.abspath(os.path.normpath(filename))
if os.path.isfile(filename):
return filename
return None # Resource cannot be resolved
def lookup_variant(mappings, key="variant"):
"""function decorator
maps strings to Enums expected by language specific functions
mappings can be used to translate values read from configuration files
Example usage:
@lookup_variant({
"default": TimeVariant.DEFAULT,
"traditional": TimeVariant.TRADITIONAL
})
def nice_time_XX(dt, speech=True, use_24hour=False, use_ampm=False,
variant=None):
variant = variant or TimeVariant.DEFAULT
(...)
"""
if not isinstance(mappings, dict):
raise ValueError
# Begin wrapper
def lang_variant_function_decorator(func):
@wraps(func)
def call_function(*args, **kwargs):
if key in kwargs and isinstance(kwargs[key], str):
if kwargs[key] in mappings:
kwargs[key] = mappings[kwargs[key]]
else:
raise ValueError("Unknown variant, mapping does not "
"exist for {v}".format(v=key))
return func(*args, **kwargs)
return call_function
try:
return lang_variant_function_decorator
except NotImplementedError as e:
warn(str(e))
return