You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

248 lines
9.4 KiB

8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
  1. #!/usr/bin/python3
  2. import pyamf
  3. import shutil
  4. import json
  5. import os
  6. import argparse
  7. import unicodedata
  8. import random
  9. import re
  10. import sys
  11. import readline
  12. DATA_DIR = os.environ.get('XDG_DATA_HOME', os.environ['HOME'] + '/.local/share') + '/klette'
  13. VOKABELN_FILE = DATA_DIR + '/vokabeln.json'
  14. STATUS_FILE = DATA_DIR + '/status.json'
  15. AUDIO_BASE = DATA_DIR + '/audio/'
  16. class bcolors:
  17. HEADER = '\033[95m'
  18. OKBLUE = '\033[94m'
  19. OKCYAN = '\033[96m'
  20. OKGREEN = '\033[92m'
  21. WARNING = '\033[93m'
  22. FAIL = '\033[91m'
  23. ENDC = '\033[0m'
  24. BOLD = '\033[1m'
  25. UNDERLINE = '\033[4m'
  26. LINK_START = '\033]8;;'
  27. LINK_MIDDLE = '\033\\'
  28. LINK_END = '\033]8;;\033\\'
  29. def import_vokabeln(file_name, audio_base):
  30. os.makedirs(AUDIO_BASE, exist_ok=True)
  31. palabras = []
  32. with open(file_name, 'rb') as reader:
  33. obj = pyamf.decode(reader.read())
  34. for unidad_data in obj.readElement()['lektionList']:
  35. unidad = unidad_data['name']
  36. for paso_data in unidad_data['lektionsTeilList']:
  37. paso = paso_data['name']
  38. for palabra in paso_data['entryList']:
  39. audio = palabra['vorderSeite']['swTonAbgleichwort']
  40. palabras.append({
  41. 'id': palabra['matId'],
  42. 'de': palabra['rueckSeite']['text'],
  43. 'es': palabra['vorderSeite']['text'],
  44. 'audio': audio,
  45. 'unidad': unidad,
  46. 'paso': paso
  47. })
  48. shutil.copy2(audio_base + audio + '.bin',
  49. AUDIO_BASE + audio + '.aac')
  50. with open(VOKABELN_FILE, 'x') as writer:
  51. writer.write(json.dumps(palabras))
  52. def import_status(file_name):
  53. os.makedirs(DATA_DIR, exist_ok=True)
  54. statuses = {}
  55. with open(file_name, 'rb') as reader:
  56. obj = pyamf.decode(reader.read()).readElement()[0]
  57. for idx in obj:
  58. item = obj[idx]
  59. statuses[item['matId']] = item['statusWortschatz']
  60. with open(STATUS_FILE, 'x') as writer:
  61. writer.write(json.dumps(statuses))
  62. def vergleiche(respuesta, expecta):
  63. if respuesta == "": return 4
  64. if respuesta[0] == '!' and expecta[0] == '¡':
  65. respuesta = '¡' + respuesta[1:]
  66. if respuesta[0] == '?' and expecta[0] == '¿':
  67. respuesta = '¿' + respuesta[1:]
  68. respuesta = re.sub('(~n|n~)', 'ñ', respuesta)
  69. expecta = re.sub(' \((irr.|pl.)\)', '', expecta)
  70. respuesta = unicodedata.normalize('NFD', respuesta)
  71. expecta = unicodedata.normalize('NFD', expecta)
  72. respuesta = re.sub(' ?([!?¿¡….]) ?', r'\1', respuesta)
  73. expecta = re.sub(' ?([!?¿¡….]) ?', r'\1', expecta)
  74. respuesta = re.sub(r'\.\.\.', '', respuesta)
  75. expecta = re.sub(r'\.\.\.', '', expecta)
  76. if respuesta == expecta: return 0
  77. respuesta = re.sub('[!?¿¡…. ]', '', respuesta)
  78. expecta = re.sub('[!?¿¡…. ]', '', expecta)
  79. if respuesta == expecta: return 1
  80. respuesta = respuesta.encode('ASCII', 'ignore').decode('ASCII')
  81. expecta = expecta.encode('ASCII', 'ignore').decode('ASCII')
  82. if respuesta == expecta: return 2
  83. respuesta = respuesta.casefold()
  84. expecta = expecta.casefold()
  85. if respuesta == expecta: return 3
  86. return 4
  87. assert vergleiche("e", "e") == 0
  88. assert vergleiche("é", "e") == 2
  89. assert vergleiche("Bien", "bien") == 3
  90. assert vergleiche("quétal", "Qué tal") == 3
  91. assert vergleiche("Buenas noches", "¡Buenas noches!") == 1
  92. assert vergleiche("ser", "ser (irr.)") == 0
  93. assert vergleiche("hacer algo", "hacer (irr.) algo") == 0
  94. assert vergleiche("padres", "padres (pl.)") == 0
  95. assert vergleiche("¿De dónde …?", "¿De dónde… ?") == 0
  96. assert vergleiche("el nombre", "el apellido") == 4
  97. assert vergleiche("Tengo … a~nos.", "Tengo... años.") == 0
  98. assert vergleiche("¿Cuántos a~nos tienes?", "¿Cuántos años tienes?") == 0
  99. class Resultado:
  100. CORRECTO = 0
  101. BIEN = 1
  102. NO_BIEN = 2
  103. MAL = 3
  104. ADIOS = 4
  105. def einzelne_abfrage(palabra, status):
  106. if status == None:
  107. status_text = f"{bcolors.OKCYAN}neu{bcolors.ENDC}"
  108. status = 0
  109. else:
  110. status_text = f"({status})"
  111. try:
  112. respuesta = input(f"{status_text} {bcolors.BOLD}{palabra['de']}{bcolors.ENDC} {bcolors.OKCYAN}")
  113. except EOFError:
  114. print()
  115. return Resultado.ADIOS
  116. finally:
  117. print(bcolors.ENDC, end="")
  118. print('\033[{}C\033[1A'.format(4 + len(palabra['de']) + 1 + len(respuesta)), end="")
  119. resultado = vergleiche(respuesta, palabra['es'])
  120. if resultado == 0:
  121. bien = Resultado.CORRECTO
  122. else:
  123. match status:
  124. case 0: bien = Resultado.BIEN if resultado < 4 else Resultado.NO_BIEN
  125. case 1: bien = Resultado.BIEN if resultado < 3 else (Resultado.NO_BIEN if resultado < 4 else Resultado.MAL)
  126. case 2: bien = Resultado.BIEN if resultado < 2 else (Resultado.NO_BIEN if resultado < 3 else Resultado.MAL)
  127. case 3: bien = Resultado.BIEN if resultado < 2 else Resultado.MAL
  128. case 4: bien = Resultado.BIEN if resultado < 1 else Resultado.MAL
  129. if 'audio' in palabra:
  130. print(f" {bcolors.LINK_START}file://{AUDIO_BASE}{palabra['audio']}.aac{bcolors.LINK_MIDDLE}",end="")
  131. else:
  132. print(" ",end="")
  133. if bien == Resultado.CORRECTO:
  134. print(f"{bcolors.OKGREEN}✓{bcolors.ENDC}",end="")
  135. elif bien == Resultado.BIEN:
  136. print(f"{bcolors.WARNING}{palabra['es']}{bcolors.ENDC}",end="")
  137. else:
  138. print(f"{bcolors.FAIL}{palabra['es']}{bcolors.ENDC}",end="")
  139. if 'audio' in palabra:
  140. print(bcolors.LINK_END,end="")
  141. try:
  142. if input(" ") == "+":
  143. bien = Resultado.CORRECTO
  144. except EOFError:
  145. print()
  146. return Resultado.ADIOS
  147. return bien
  148. class Sesion:
  149. def __init__(self, palabras, status):
  150. self.palabras = palabras
  151. self.status = status
  152. self.bien = 0
  153. self.mal = 0
  154. def empezar(self, quiero_unidad):
  155. unidad = False
  156. unidad_no = -1
  157. paso = False
  158. cur_palabras = False
  159. for palabra in self.palabras:
  160. if paso != palabra['paso']:
  161. if cur_palabras != False and (quiero_unidad == None or quiero_unidad == unidad_no):
  162. c = [len(cur_palabras[x]) for x in cur_palabras]
  163. print(f"{bcolors.BOLD}{unidad}{bcolors.ENDC}: {paso} ({c[0] + c[6]}/{c[1]}/{c[2]}/{c[3]}/{c[4]}/{bcolors.OKGREEN}{c[5]}{bcolors.ENDC})")
  164. for n in range(5): # 1..4, no 5
  165. if self.hace_palabras(cur_palabras[n], n) == Resultado.ADIOS:
  166. return
  167. n = None
  168. if self.hace_palabras(cur_palabras[n], n) == Resultado.ADIOS:
  169. return
  170. cur_palabras = {0: [], 1: [], 2: [], 3: [], 4: [], 5: [], None: []}
  171. if unidad != palabra['unidad']:
  172. unidad_no += 1
  173. unidad = palabra['unidad']
  174. paso = palabra['paso']
  175. cur_palabras[self.status.get(palabra['id'])].append(palabra)
  176. def hace_palabras(self, palabras, status):
  177. random.shuffle(palabras)
  178. for palabra in palabras:
  179. match einzelne_abfrage(palabra, status):
  180. case Resultado.CORRECTO:
  181. self.status[palabra['id']] = (status or 0) + (2 if (status or 0) < 2 else 1)
  182. self.bien += 1
  183. case Resultado.BIEN:
  184. self.status[palabra['id']] = (status or 0) + 1
  185. self.bien += 1
  186. case Resultado.NO_BIEN:
  187. continue
  188. case Resultado.MAL:
  189. self.status[palabra['id']] = status - 1
  190. self.mal += 1
  191. case Resultado.ADIOS:
  192. return Resultado.ADIOS
  193. with open(STATUS_FILE + '.new', 'w') as f:
  194. json.dump(self.status, f)
  195. f.flush()
  196. os.fsync(f.fileno())
  197. with open(STATUS_FILE + '.new', 'r') as f:
  198. pass
  199. os.replace(STATUS_FILE + '.new', STATUS_FILE)
  200. def abfrage(parser, quiero_unidad):
  201. random.seed()
  202. try:
  203. with open(VOKABELN_FILE, 'r') as f:
  204. palabras = json.load(f)
  205. with open(STATUS_FILE, 'r') as f:
  206. status = json.load(f)
  207. except FileNotFoundError:
  208. print(f"{bcolors.FAIL}Daten können nicht geladen werden, hast du sie schon importiert?{bcolors.ENDC}")
  209. print()
  210. parser.print_help()
  211. return
  212. sesion = Sesion(palabras, status)
  213. sesion.empezar(quiero_unidad)
  214. print(f'{bcolors.OKGREEN}+{sesion.bien}{bcolors.ENDC} / {bcolors.FAIL}-{sesion.mal}{bcolors.ENDC}')
  215. parser = argparse.ArgumentParser()
  216. default_data_file = os.environ['PWD'] + '/assets/amf/vokabelTrainer147.amf'
  217. parser.add_argument('--import-data', type=str, help="Path to assets", metavar="DIR")
  218. default_status_file = os.environ['HOME'] + '/klett/1266/vokabeltrainerData147'
  219. parser.add_argument('--import-status', type=str, help="Path to AMF File, defaults to " + default_status_file, metavar="FILE", nargs='?', const=default_status_file)
  220. parser.add_argument('--unidad', type=int)
  221. args = parser.parse_args()
  222. if args.import_data:
  223. import_vokabeln(args.import_data + '/amf/vokabelTrainer147.amf',
  224. args.import_data + '/amf/medium/')
  225. elif args.import_status:
  226. import_status(args.import_status)
  227. else:
  228. abfrage(parser, args.unidad)