Completed
Push — master ( f4c0a2...4a0808 )
by Oleksandr
03:35
created

regiments.core.Regiment.__repr__()   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 2
rs 10
c 0
b 0
f 0
cc 1
nop 1
1
from pathlib import Path
2
3
from typing import Any
4
from typing import Callable
5
from typing import Dict
6
from typing import List
7
from typing import Optional
8
from typing import Text
9
10
from verboselib import get_language
11
12
from il2fb.commons import SupportedLanguages
13
from il2fb.commons.organization import AirForce
14
from il2fb.commons.organization import AirForces
15
16
17
__here__ = Path(__file__).absolute().parent
18
__all__  = []
19
20
21
DEFAULT_LANGUAGE_NAME = SupportedLanguages.get_default().name
22
23
DEFAULT_DATA_DIR_PATH = __here__ / "data"
24
DEFAULT_DATA_MISSING_VALUE = None
25
DEFAULT_DATA_FILE_ENCODING = "cp1251"
26
DEFAULT_VALUE_ENCODING = "unicode-escape"
27
28
DEFAULT_CATALOG_FILE_NAME = "regiments.ini"
29
30
DEFAULT_NAMES_FILE_NAME_FORMAT        = "regShort_{language}.properties"
31
DEFAULT_DESCRIPTIONS_FILE_NAME_FORMAT = "regInfo_{language}.properties"
32
33
FLIGHT_PREFIXES = set(AirForces.get_flight_prefixes())
34
35
36
def export(target: Any) -> Any:
37
  __all__.append(target.__name__)
38
  return target
39
40
41
@export
42
class RegimentInfoLoader:
43
44
  def __init__(
45
    self,
46
    data_dir_path: Path=DEFAULT_DATA_DIR_PATH,
47
    data_file_encoding: Text=DEFAULT_DATA_FILE_ENCODING,
48
    data_value_encoding: Text=DEFAULT_VALUE_ENCODING,
49
    data_missing_value: Text=DEFAULT_DATA_MISSING_VALUE,
50
    names_file_name_format: Text=DEFAULT_NAMES_FILE_NAME_FORMAT,
51
    descriptions_file_name_format: Text=DEFAULT_DESCRIPTIONS_FILE_NAME_FORMAT,
52
  ):
53
    self._data_dir_path = data_dir_path
54
    self._data_file_encoding = data_file_encoding
55
    self._data_value_encoding = data_value_encoding
56
    self._data_missing_value = data_missing_value
57
    self._names_file_name_format = names_file_name_format
58
    self._descriptions_file_name_format = descriptions_file_name_format
59
60
  def get_name(self, code_name: Text, language: Any) -> Text:
61
    file_name = self._names_file_name_format.format(language=language)
62
    return self._get_value(code_name, file_name)
63
64
  def get_description(self, code_name: Text, language: Any) -> Text:
65
    file_name = self._descriptions_file_name_format.format(language=language)
66
    return self._get_value(code_name, file_name)
67
68
  def _get_value(self, code_name: Text, file_name: Text) -> Text:
69
    file_path = self._data_dir_path / file_name
70
    try:
71
      return self._load_value_or_raise(code_name, file_path)
72
    except ValueError:
73
      return self._data_missing_value
74
75
  def _load_value_or_raise(self, code_name: Text, file_path: Path) -> Text:
76
    if not file_path.exists():
77
      raise ValueError
78
79
    with file_path.open(mode="rb") as f:
80
      code_name = code_name.encode(self._data_file_encoding)
81
      for line in f:
82
        if line.startswith(code_name):
83
          key, value = line.split(maxsplit=1)
84
          return value.decode(self._data_value_encoding).strip()
85
      else:
86
        raise ValueError
87
88
89
@export
90
class Regiment:
91
92
  def __init__(
93
    self,
94
    air_force:   AirForce,
95
    code_name:   Text,
96
    info_loader: Optional[RegimentInfoLoader]=None,
97
  ):
98
    self.air_force = air_force
99
    self.code_name = code_name
100
101
    self._info_loader = info_loader or RegimentInfoLoader()
102
    self._text_attribute_loaders = {
103
      'verbose_name': self._info_loader.get_name,
104
      'help_text':    self._info_loader.get_description,
105
    }
106
107
  def __getattr__(self, name: Text) -> Text:
108
    loader = self._text_attribute_loaders.get(name)
109
    if not loader:
110
      raise AttributeError(
111
        f"'{self.__class__}' object has no attribute '{name}'"
112
      )
113
114
    language  = get_language()
115
    if language not in SupportedLanguages:
116
      language = DEFAULT_LANGUAGE_NAME
117
118
    full_name = f"{name}_{language}"
119
120
    value = getattr(self, full_name, None)
121
122
    if not value:
123
      value = self._load_value(loader, language)
124
      setattr(self, full_name, value)
125
126
    return value
127
128
  def _load_value(
129
    self,
130
    loader:   Callable[[Text, Any], Text],
131
    language: Any,
132
  ) -> Text:
133
134
    value = loader(code_name=self.code_name, language=language)
135
136
    if not value and language != DEFAULT_LANGUAGE_NAME:
137
      value = loader(code_name=self.code_name, language=DEFAULT_LANGUAGE_NAME)
138
139
    return value
140
141
  def to_primitive(self, context: Any=None) -> Dict[Text, Any]:
142
    return {
143
      'air_force':    self.air_force.to_primitive(context),
144
      'code_name':    self.code_name,
145
      'verbose_name': self.verbose_name,
146
      'help_text':    self.help_text,
147
    }
148
149
  def __repr__(self) -> Text:
150
    return f"<{self.__class__.__name__} '{self.code_name}'>"
151
152
153
@export
154
class Regiments:
155
156
  def __init__(
157
    self,
158
    data_dir_path:      Path=DEFAULT_DATA_DIR_PATH,
159
    data_file_name:     Text=DEFAULT_CATALOG_FILE_NAME,
160
    data_file_encoding: Text=DEFAULT_DATA_FILE_ENCODING,
161
    info_loader:        RegimentInfoLoader=None,
162
  ):
163
    self._data_file_path = data_dir_path / data_file_name
164
    if not self._data_file_path.exists():
165
      raise ValueError(
166
        f"Data file '{str(self._data_file_path)}' does not exist"
167
      )
168
169
    self._data_file_encoding = data_file_encoding
170
171
    self._info_loader = info_loader or RegimentInfoLoader(
172
      data_dir_path=data_dir_path,
173
      data_file_encoding=data_file_encoding,
174
    )
175
176
    self._cache = dict()
177
178
  def get_by_code_name(self, code_name: Text) -> Regiment:
179
    regiment = self._cache.get(code_name)
180
181
    if not regiment:
182
      regiment = self._load_by_code_name_or_raise(code_name)
183
      self._cache[code_name] = regiment
184
185
    return regiment
186
187
  def _load_by_code_name_or_raise(self, code_name: Text) -> Regiment:
188
    flight_prefix = self._get_flight_prefix_for_existing_regiment(code_name)
189
    if not flight_prefix:
190
      raise ValueError(f"Regiment with code name '{code_name}' not found")
191
192
    air_force = AirForces.get_by_flight_prefix(flight_prefix)
193
194
    return Regiment(
195
      air_force=air_force,
196
      code_name=code_name,
197
      info_loader=self._info_loader,
198
    )
199
200
  def _get_flight_prefix_for_existing_regiment(self, code_name: Text) -> Optional[Text]:
201
    with self._data_file_path.open(
202
      mode="rt",
203
      encoding=self._data_file_encoding,
204
      buffering=1,
205
    ) as f:
206
207
      flight_prefix = None
208
209
      for line in f:
210
        line = line.strip()
211
        if not line:
212
          continue
213
214
        if line in FLIGHT_PREFIXES:
215
          flight_prefix = line
216
217
        elif line == code_name:
218
          return flight_prefix
219
220
  def filter_by_air_force(self, air_force: AirForce) -> List[Regiment]:
221
    result = []
222
223
    with self._data_file_path.open(
224
      mode="rt",
225
      encoding=self._data_file_encoding,
226
      buffering=1,
227
    ) as f:
228
229
      default_flight_prefix = air_force.default_flight_prefix
230
      air_force_is_found = False
231
232
      for line in f:
233
        line = line.strip()
234
235
        if not line:
236
          continue
237
238
        if line == default_flight_prefix:
239
          air_force_is_found = True
240
241
        elif air_force_is_found:
242
          if (
243
             line in FLIGHT_PREFIXES or
244
            (line.startswith('[') and line.endswith(']'))
245
          ):
246
            # Next section was found. Fullstop.
247
            break
248
249
          regiment = self._cache.get(line)
250
          if not regiment:
251
            regiment = Regiment(
252
              air_force=air_force,
253
              code_name=line,
254
              info_loader=self._info_loader,
255
            )
256
            self._cache[line] = regiment
257
258
          result.append(regiment)
259
260
    return result
261