Coverage for afasi/tabel.py: 98.18%

58 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-21 13:35:37 +00:00

1# -*- coding: utf-8 -*- 

2# pylint: disable=expression-not-assigned,line-too-long 

3"""Tabel (Danish for Table). Class API.""" 

4import json 

5import pathlib 

6import typing 

7 

8import yaml 

9 

10ENCODING = 'utf-8' 

11 

12 

13class Table: 

14 """Translation table.""" 

15 

16 __slots__ = ['description', 'contra', 'count', 'flip_is_stop', 'flip_flop', 'pro', 'ff_state', 'translations'] 

17 

18 @typing.no_type_check 

19 def __init__(self, **kwargs): 

20 """Instance created from dictionary usually stored in a JSON file.""" 

21 self.ff_state = True 

22 for key, value in kwargs.items(): 

23 if key == 'table': 

24 for me, ta in value.items(): 

25 setattr(self, me, ta) 

26 elif key == 'translations': 

27 setattr(self, key, []) 

28 for entry in value: 

29 self.translations.append(Translation(**entry)) 

30 else: 

31 print(f'table ignored ({key=} -> {value=})') 

32 

33 @typing.no_type_check 

34 def translate(self, text: str) -> str: 

35 """Sequenced replacer (WIP).""" 

36 if self.flip_flop: 

37 for pos, token in enumerate(self.flip_flop, start=1): 

38 if token not in text: 

39 continue 

40 else: 

41 if self.flip_is_stop: 

42 self.ff_state = False if pos % 2 else True 

43 else: 

44 self.ff_state = True if pos % 2 else False 

45 

46 if self.ff_state: 

47 if self.contra and any(stop in text for stop in self.contra): 

48 return text 

49 

50 if not self.pro or any(start in text for start in self.pro): 

51 for rule in self.translations: 

52 text = rule.apply(text) 

53 

54 return text 

55 

56 @typing.no_type_check 

57 def __str__(self): 

58 """Human readable rendition esp. for debugging.""" 

59 ff = "'" + "'\n '".join(switch for switch in self.flip_flop) + "'" 

60 return ( 

61 f'table:\n {self.description=}\n' 

62 f' {self.contra=}\n' 

63 f' {self.count=}\n' 

64 f' {self.flip_is_stop=}\n' 

65 f' flip_flop:\n {ff}\n' 

66 f' {self.count=}\n' 

67 f' {self.pro=}\n' 

68 f' translations:\n {" ".join(str(translation) for translation in self.translations)}\n' 

69 ) 

70 

71 

72class Translation: 

73 """Translation task.""" 

74 

75 __slots__ = ['repl', 'ace'] 

76 

77 @typing.no_type_check 

78 def __init__(self, **kwargs): 

79 """Instance created from dictionary.""" 

80 for key, value in kwargs.items(): 

81 setattr(self, key, value) 

82 

83 @typing.no_type_check 

84 def apply(self, text: str) -> str: 

85 """Elementary replacer (WIP).""" 

86 return text.replace(self.repl, self.ace) 

87 

88 @typing.no_type_check 

89 def __str__(self): 

90 """Human readable rendition esp. for debugging.""" 

91 return f'{self.repl=} -> {self.ace=}\n' 

92 

93 

94def load_table(path: pathlib.Path) -> typing.Any: 

95 """Generate Table instance from YAML or JSON file.""" 

96 suffix = path.suffix.lower() 

97 if suffix == '.json': 

98 with open(path, 'rt', encoding=ENCODING) as dump: 

99 return Table(**json.load(dump)) 

100 with open(path, 'rt', encoding=ENCODING) as dump: 100 ↛ 101line 100 didn't jump to line 101 because

101 return Table(**yaml.safe_load(dump))