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.

283 lines
11 KiB

#!/usr/bin/python3
import pyamf
import shutil
import json
import os
import glob
import argparse
import unicodedata
import random
import re
import sys
import readline
import datetime
DATA_DIR = os.environ.get('XDG_DATA_HOME', os.environ['HOME'] + '/.local/share') + '/klette'
DEPRECATED_VOKABELN_FILE = DATA_DIR + '/vokabeln.json'
VOKABELN_DIR = DATA_DIR + '/vokabeln/'
STATUS_FILE = DATA_DIR + '/status.json'
AUDIO_BASE = DATA_DIR + '/audio/'
class bcolors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKCYAN = '\033[96m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
LINK_START = '\033]8;;'
LINK_MIDDLE = '\033\\'
LINK_END = '\033]8;;\033\\'
def import_vokabeln(file_name, audio_base):
os.makedirs(AUDIO_BASE, exist_ok=True)
palabras = []
with open(file_name, 'rb') as reader:
obj = pyamf.decode(reader.read())
for unidad_data in obj.readElement()['lektionList']:
unidad = unidad_data['name']
for paso_data in unidad_data['lektionsTeilList']:
paso = paso_data['name']
for palabra in paso_data['entryList']:
audio = palabra['vorderSeite']['swTonAbgleichwort']
palabras.append({
'id': palabra['matId'],
'de': palabra['rueckSeite']['text'],
'es': palabra['vorderSeite']['text'],
'audio': audio,
'unidad': unidad,
'paso': paso
})
if audio:
shutil.copy2(audio_base + audio + '.bin',
AUDIO_BASE + audio + '.aac')
with open(VOKABELN_DIR + '/import-' + datetime.datetime.now().isoformat() + '.json', 'x') as writer:
writer.write(json.dumps(palabras))
def import_status(file_name):
os.makedirs(DATA_DIR, exist_ok=True)
statuses = {}
with open(file_name, 'rb') as reader:
obj = pyamf.decode(reader.read()).readElement()[0]
for idx in obj:
item = obj[idx]
statuses[item['matId']] = item['statusWortschatz']
with open(STATUS_FILE, 'x') as writer:
writer.write(json.dumps(statuses))
def vergleiche(respuesta, expecta):
if respuesta == "": return 4
if respuesta[0] == '!' and expecta[0] == '¡':
respuesta = '¡' + respuesta[1:]
if respuesta[0] == '?' and expecta[0] == '¿':
respuesta = '¿' + respuesta[1:]
respuesta = re.sub('(~n|n~)', 'ñ', respuesta)
expecta = re.sub(' \((irr.|pl.)\)', '', expecta)
respuesta = unicodedata.normalize('NFD', respuesta)
expecta = unicodedata.normalize('NFD', expecta)
respuesta = re.sub(' ?([!?¿¡….]) ?', r'\1', respuesta)
expecta = re.sub(' ?([!?¿¡….]) ?', r'\1', expecta)
respuesta = re.sub(r'\.\.\.', '', respuesta)
expecta = re.sub(r'\.\.\.', '', expecta)
if respuesta == expecta: return 0
respuesta = re.sub('[!?¿¡…. ]', '', respuesta)
expecta = re.sub('[!?¿¡…. ]', '', expecta)
if respuesta == expecta: return 1
respuesta = respuesta.encode('ASCII', 'ignore').decode('ASCII')
expecta = expecta.encode('ASCII', 'ignore').decode('ASCII')
if respuesta == expecta: return 2
respuesta = respuesta.casefold()
expecta = expecta.casefold()
if respuesta == expecta: return 3
return 4
assert vergleiche("e", "e") == 0
assert vergleiche("é", "e") == 2
assert vergleiche("Bien", "bien") == 3
assert vergleiche("quétal", "Qué tal") == 3
assert vergleiche("Buenas noches", "¡Buenas noches!") == 1
assert vergleiche("ser", "ser (irr.)") == 0
assert vergleiche("hacer algo", "hacer (irr.) algo") == 0
assert vergleiche("padres", "padres (pl.)") == 0
assert vergleiche("¿De dónde …?", "¿De dónde… ?") == 0
assert vergleiche("el nombre", "el apellido") == 4
assert vergleiche("Tengo … a~nos.", "Tengo... años.") == 0
assert vergleiche("¿Cuántos a~nos tienes?", "¿Cuántos años tienes?") == 0
class Resultado:
CORRECTO = 0
BIEN = 1
NO_BIEN = 2
MAL = 3
ADIOS = 4
def einzelne_abfrage(palabra, status):
if status == None:
status_text = f"{bcolors.OKCYAN}neu{bcolors.ENDC}"
status = 0
else:
status_text = f"({status})"
try:
respuesta = input(f"{status_text} {bcolors.BOLD}{palabra['de']}{bcolors.ENDC} {bcolors.OKCYAN}")
except EOFError:
print()
return Resultado.ADIOS
finally:
print(bcolors.ENDC, end="")
print('\033[{}C\033[1A'.format(4 + len(palabra['de']) + 1 + len(respuesta)), end="")
resultado = vergleiche(respuesta, palabra['es'])
if resultado == 0:
bien = Resultado.CORRECTO
else:
match status:
case 0: bien = Resultado.BIEN if resultado < 4 else Resultado.NO_BIEN
case 1: bien = Resultado.BIEN if resultado < 3 else (Resultado.NO_BIEN if resultado < 4 else Resultado.MAL)
case 2: bien = Resultado.BIEN if resultado < 2 else (Resultado.NO_BIEN if resultado < 3 else Resultado.MAL)
case 3: bien = Resultado.BIEN if resultado < 2 else Resultado.MAL
case 4: bien = Resultado.BIEN if resultado < 1 else Resultado.MAL
if 'audio' in palabra:
print(f" {bcolors.LINK_START}file://{AUDIO_BASE}{palabra['audio']}.aac{bcolors.LINK_MIDDLE}",end="")
else:
print(" ",end="")
if bien == Resultado.CORRECTO:
print(f"{bcolors.OKGREEN}✓{bcolors.ENDC}",end="")
elif bien == Resultado.BIEN:
print(f"{bcolors.WARNING}{palabra['es']}{bcolors.ENDC}",end="")
else:
print(f"{bcolors.FAIL}{palabra['es']}{bcolors.ENDC}",end="")
if 'audio' in palabra:
print(bcolors.LINK_END,end="")
try:
if input(" ") == "+":
bien = Resultado.CORRECTO
except EOFError:
print()
return Resultado.ADIOS
return bien
class Sesion:
def __init__(self, palabras, status):
self.palabras = palabras
self.status = status
self.bien = 0
self.mal = 0
def otra_vez(self, quiero_unidad):
unidad = False
unidad_no = -1
paso = False
cur_palabras = False
for palabra in self.palabras:
if unidad != palabra['unidad']:
unidad_no += 1
unidad = palabra['unidad']
if quiero_unidad == None or quiero_unidad == unidad_no:
if self.status.get(palabra['id'], None) == 5:
self.status[palabra['id']] = 4
def empezar(self, quiero_unidad):
unidad = False
unidad_no = -1
paso = False
cur_palabras = False
for palabra in self.palabras:
if paso != palabra['paso']:
if cur_palabras != False and (quiero_unidad == None or quiero_unidad == unidad_no):
if self.hace_paso(unidad, paso, cur_palabras) == Resultado.ADIOS:
return
cur_palabras = {0: [], 1: [], 2: [], 3: [], 4: [], 5: [], None: []}
if unidad != palabra['unidad']:
unidad_no += 1
unidad = palabra['unidad']
paso = palabra['paso']
cur_palabras[self.status.get(palabra['id'])].append(palabra)
if cur_palabras != False and (quiero_unidad == None or quiero_unidad == unidad_no):
if self.hace_paso(unidad, paso, cur_palabras) == Resultado.ADIOS:
return
def hace_paso(self, unidad, paso, cur_palabras):
c = [len(cur_palabras[x]) for x in cur_palabras]
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})")
n = None
if self.hace_palabras(cur_palabras[n], n) == Resultado.ADIOS:
return Resultado.ADIOS
for n in range(5): # 1..4, no 5
if self.hace_palabras(cur_palabras[n], n) == Resultado.ADIOS:
return Resultado.ADIOS
def hace_palabras(self, palabras, status):
random.shuffle(palabras)
for palabra in palabras:
match einzelne_abfrage(palabra, status):
case Resultado.CORRECTO:
self.status[palabra['id']] = (status or 0) + (2 if (status or 0) < 2 else 1)
self.bien += 1
case Resultado.BIEN:
self.status[palabra['id']] = (status or 0) + 1
self.bien += 1
case Resultado.NO_BIEN:
continue
case Resultado.MAL:
self.status[palabra['id']] = status - 1
self.mal += 1
case Resultado.ADIOS:
return Resultado.ADIOS
with open(STATUS_FILE + '.new', 'w') as f:
json.dump(self.status, f)
f.flush()
os.fsync(f.fileno())
with open(STATUS_FILE + '.new', 'r') as f:
pass
os.replace(STATUS_FILE + '.new', STATUS_FILE)
def abfrage(parser, quiero_unidad, otra_vez):
random.seed()
try:
palabras = []
os.makedirs(VOKABELN_DIR, exist_ok=True)
try:
os.rename(DEPRECATED_VOKABELN_FILE, os.path.join(VOKABELN_DIR, 'vokabeln.json'))
except FileNotFoundError:
pass
for filename in glob.glob(os.path.join(VOKABELN_DIR, '*.json')):
with open(filename, 'r') as f:
palabras += json.load(f)
with open(STATUS_FILE, 'r') as f:
status = json.load(f)
except FileNotFoundError:
print(f"{bcolors.FAIL}Daten können nicht geladen werden, hast du sie schon importiert?{bcolors.ENDC}")
print()
parser.print_help()
return
sesion = Sesion(palabras, status)
if otra_vez:
sesion.otra_vez(quiero_unidad)
sesion.empezar(quiero_unidad)
print(f'{bcolors.OKGREEN}+{sesion.bien}{bcolors.ENDC} / {bcolors.FAIL}-{sesion.mal}{bcolors.ENDC}')
parser = argparse.ArgumentParser()
parser.add_argument('--import-data', type=str, help="Path to assets", metavar="DIR")
default_status_file = os.environ['HOME'] + '/klett/1266/vokabeltrainerData147'
parser.add_argument('--import-status', type=str, help="Path to AMF File, defaults to " + default_status_file, metavar="FILE", nargs='?', const=default_status_file)
parser.add_argument('--unidad', type=int)
parser.add_argument('--otra-vez', action='store_true')
args = parser.parse_args()
if args.import_data:
import_vokabeln(glob.glob(os.path.join(args.import_data, 'amf/vokabelTrainer*.amf'))[0],
args.import_data + '/amf/medium/')
elif args.import_status:
import_status(args.import_status)
else:
abfrage(parser, args.unidad, args.otra_vez)