-
Notifications
You must be signed in to change notification settings - Fork 289
/
hedy_error.py
127 lines (102 loc) · 4.82 KB
/
hedy_error.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
import hedy
import hedy_translation
import re
from website.flask_helpers import gettext_with_fallback as gettext
# TODO: we should not maintain a list like this. Translation of exception arguments should happen when the exception
# is created, because then we know what language was used in every part of the code. For example, if a user creates
# the program `draai 50 forward 10` and mixes the languages of the keywords, an error regarding the turn command
# should use `draai` and an error about the forward command - `forward`.
arguments_that_require_translation = [
'allowed_types',
'invalid_type',
'invalid_type_2',
'offending_keyword',
'character_found',
'concept',
'tip',
'else',
'command',
'incomplete_command',
'missing_command',
'print',
'ask',
'echo',
'is',
'if',
'repeat',
'suggestion_color',
'suggestion_note',
'suggestion_number',
'suggestion_numbers_or_strings'
]
def get_error_text(ex, lang):
""" Return a translated and highlighted text of the provided HedyException """
# TODO: TB -> We have to find a more delicate way to fix this: returns some gettext() errors
error_template = gettext('' + str(ex.error_code))
arguments = ex.arguments
# Some of the arguments like 'allowed_types' or 'characters' need to be translated in the error message
for k, v in arguments.items():
if k in arguments_that_require_translation:
arguments[k] = _translate_error_arg(v, lang)
# Some of the already translated arguments like 'tip' and 'allowed_types' are translated to text, which
# in turn needs to be translated/highlighted. Atm we explicitly allow for 1 level of nested translation.
for k, v in arguments.items():
if k in arguments_that_require_translation:
nested_arguments = _extract_nested_arguments(v, lang)
if nested_arguments:
arguments[k] = v.format(**nested_arguments)
# Errors might contain hardcoded references to commands which are not supplied in the arguments, e.g. {print}, {ask}
arguments.update(_get_missing_arguments(error_template, arguments, lang))
# Do not use a safe_format here. Every exception is tested against every language in tests_translation_error
result = error_template.format(**arguments)
return _highlight(result)
def _highlight(input_):
""" Add highlight styling to the parts in the input surrounded by backticks, for example:
'`print` is incorrect' becomes '<span class="command-highlighted">print</span> is incorrect'
This is a simple implementation that does not allow escaping backticks. Extend it in the future if needed. """
quote_positions = [i for i, x in enumerate(input_) if x == "`"]
quotes_even = len(quote_positions) % 2 == 0
if not quote_positions or not quotes_even:
return input_
quote_positions.insert(0, 0)
result = []
for i, position in enumerate(quote_positions):
start_ = position if i == 0 else position + 1
end_ = quote_positions[i+1] if i + 1 < len(quote_positions) else len(input_)
if start_ < end_:
part = input_[start_:end_]
is_highlighted = i % 2 == 1
result.append(hedy.style_command(part) if is_highlighted else part)
return ''.join(result)
def _get_keys(input_):
pattern = r'\{([^}]*)\}'
return re.findall(pattern, input_)
def _get_missing_arguments(input_, args, lang):
""" Discover and translate keywords used in the template which are missing from the arguments, e.g. {print} """
matches = _get_keys(input_)
existing_keywords = hedy_translation.keywords_to_dict_single_choice(lang)
used_keys = [k for k in matches if k not in args and k in existing_keywords]
translated_keywords = {k: _translate_error_arg(k, lang) for k in used_keys}
return translated_keywords
def _extract_nested_arguments(template, lang):
used_keys = _get_keys(template)
return {k: _translate_error_arg(k, lang) for k in used_keys}
def _translate_error_arg(arg, lang):
if isinstance(arg, list):
return _translate_list(arg, lang)
return _translate(arg, lang)
def _translate_list(args, lang):
translated_args = [_translate(a, lang) for a in args]
# deduplication is needed because diff values could be translated to the same value, e.g. int and float => a number
translated_args = list(dict.fromkeys(translated_args))
if len(translated_args) > 1:
return f"{', '.join(translated_args[0:-1])}" \
f" {gettext('or')} " \
f"{translated_args[-1]}"
return ''.join(translated_args)
def _translate(v, language):
translation = gettext('' + str(v))
# if there is no translation available, probably this is a keyword
if v == translation:
translation = hedy_translation.translate_keyword_from_en(v, language)
return translation