commit
						f05ee8709f
					
				 7 changed files with 234 additions and 0 deletions
			
			
		- 
					5debian/changelog
 - 
					17debian/control
 - 
					6debian/copyright
 - 
					2debian/klette.install
 - 
					4debian/rules
 - 
					1debian/source/format
 - 
					199main.py
 
@ -0,0 +1,5 @@ | 
				
			|||
klette (0.1) UNRELEASED; urgency=low | 
				
			|||
 | 
				
			|||
  * Initial release. | 
				
			|||
 | 
				
			|||
 -- Adrian Heine <debian@adrianheine.de>  Mon, 08 Apr 2024 11:29:30 +0200 | 
				
			|||
@ -0,0 +1,17 @@ | 
				
			|||
Source: klette | 
				
			|||
Section: education | 
				
			|||
Priority: optional | 
				
			|||
Maintainer: Adrian Heine <debian@adrianheine.de> | 
				
			|||
Build-Depends: debhelper-compat (= 13), dh-python, python3-all, dh-exec | 
				
			|||
Standards-Version: 4.6.2 | 
				
			|||
Homepage: https://git.adrianheine.de/adrian/klette | 
				
			|||
Rules-Requires-Root: no | 
				
			|||
Vcs-Git: https://git.adrianheine.de/adrian/klette.git | 
				
			|||
Vcs-Browser: https://git.adrianheine.de/adrian/klette | 
				
			|||
X-Python3-Version: >= 3.10 | 
				
			|||
 | 
				
			|||
Package: klette | 
				
			|||
Architecture: all | 
				
			|||
Multi-Arch: foreign | 
				
			|||
Depends: ${misc:Depends}, ${python3:Depends}, python3-pyamf | 
				
			|||
Description: Replacement for klett's flash-based vocabulary trainer | 
				
			|||
@ -0,0 +1,6 @@ | 
				
			|||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ | 
				
			|||
Upstream-Name: klette | 
				
			|||
 | 
				
			|||
Files:     * | 
				
			|||
Copyright: 2024 Adrian Heine | 
				
			|||
License:   AGPL-3.0+ | 
				
			|||
@ -0,0 +1,2 @@ | 
				
			|||
#!/usr/bin/dh-exec | 
				
			|||
main.py => /usr/bin/klette | 
				
			|||
@ -0,0 +1,4 @@ | 
				
			|||
#!/usr/bin/make -f | 
				
			|||
 | 
				
			|||
%: | 
				
			|||
	dh $@ --with python3 | 
				
			|||
@ -0,0 +1 @@ | 
				
			|||
3.0 (native) | 
				
			|||
@ -0,0 +1,199 @@ | 
				
			|||
#!/usr/bin/python3 | 
				
			|||
 | 
				
			|||
import pyamf | 
				
			|||
import shutil | 
				
			|||
import json | 
				
			|||
import os | 
				
			|||
import argparse | 
				
			|||
import unicodedata | 
				
			|||
import random | 
				
			|||
import re | 
				
			|||
import sys | 
				
			|||
import readline | 
				
			|||
 | 
				
			|||
DATA_DIR = os.environ['XDG_DATA_HOME'] + '/klette' | 
				
			|||
VOKABELN_FILE = DATA_DIR + '/vokabeln.json' | 
				
			|||
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 | 
				
			|||
                    }) | 
				
			|||
                    shutil.copy2(audio_base + audio + '.bin', | 
				
			|||
                                 AUDIO_BASE + audio + '.aac') | 
				
			|||
    with open(VOKABELN_FILE, '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 = unicodedata.normalize('NFD', respuesta) | 
				
			|||
    expecta = unicodedata.normalize('NFD', expecta) | 
				
			|||
    if respuesta == expecta: return 0 | 
				
			|||
    respuesta = respuesta.encode('ASCII', 'ignore').decode('ASCII') | 
				
			|||
    expecta = expecta.encode('ASCII', 'ignore').decode('ASCII') | 
				
			|||
    if respuesta == expecta: return 1 | 
				
			|||
    respuesta = respuesta.casefold() | 
				
			|||
    expecta = expecta.casefold() | 
				
			|||
    if respuesta == expecta: return 2 | 
				
			|||
    respuesta = re.sub('\W', '', respuesta) | 
				
			|||
    expecta = re.sub('\W', '', expecta) | 
				
			|||
    if respuesta == expecta: return 3 | 
				
			|||
    return 4 | 
				
			|||
 | 
				
			|||
assert vergleiche("e", "e") == 0 | 
				
			|||
assert vergleiche("é", "e") == 1 | 
				
			|||
assert vergleiche("Bien", "bien") == 2 | 
				
			|||
assert vergleiche("quétal", "Qué tal") == 3 | 
				
			|||
assert vergleiche("Buenas Noches", "¡Buenas noches!") == 3 | 
				
			|||
 | 
				
			|||
class Resultado: | 
				
			|||
    BIEN = 1 | 
				
			|||
    MAL = 2 | 
				
			|||
    ADIOS = 3 | 
				
			|||
 | 
				
			|||
def einzelne_abfrage(palabra, status): | 
				
			|||
    status_text = f"({status})" if status > 0 else f"{bcolors.OKGREEN}neu{bcolors.ENDC}" | 
				
			|||
    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']) | 
				
			|||
    match status: | 
				
			|||
        case 0: bien = resultado < 4 | 
				
			|||
        case 1: bien = resultado < 3 | 
				
			|||
        case 2: bien = resultado < 3 | 
				
			|||
        case 3: bien = resultado < 2 | 
				
			|||
        case 4: bien = resultado < 1 | 
				
			|||
    print(f" {bcolors.LINK_START}file://{AUDIO_BASE}{palabra['audio']}.aac{bcolors.LINK_MIDDLE}",end="") | 
				
			|||
    if bien and resultado == 0: | 
				
			|||
        print(f"{bcolors.OKGREEN}✓{bcolors.ENDC}",end="") | 
				
			|||
    elif bien: | 
				
			|||
        print(f"{bcolors.WARNING}{palabra['es']}{bcolors.ENDC}",end="") | 
				
			|||
    else: | 
				
			|||
        print(f"{bcolors.FAIL}{palabra['es']}{bcolors.ENDC}",end="") | 
				
			|||
    print(bcolors.LINK_END,end="") | 
				
			|||
    try: | 
				
			|||
        input(" ") | 
				
			|||
    except EOFError: | 
				
			|||
        print() | 
				
			|||
        return Resultado.ADIOS | 
				
			|||
    return Resultado.BIEN if bien else Resultado.MAL | 
				
			|||
 | 
				
			|||
def abfrage(parser): | 
				
			|||
    random.seed() | 
				
			|||
    try: | 
				
			|||
        with open(VOKABELN_FILE, '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 | 
				
			|||
 | 
				
			|||
    unidad = False | 
				
			|||
    paso = False | 
				
			|||
    cur_palabras = False | 
				
			|||
    bien = 0 | 
				
			|||
    mal = 0 | 
				
			|||
    for palabra in palabras: | 
				
			|||
        if paso != palabra['paso']: | 
				
			|||
            if cur_palabras != False: | 
				
			|||
                c = [len(cur_palabras[x]) for x in cur_palabras] | 
				
			|||
                print(f"{bcolors.BOLD}{unidad}{bcolors.ENDC}: {paso} (",end="") | 
				
			|||
                print(*c, sep="/", end=")") | 
				
			|||
                print() | 
				
			|||
                for n in range(5): # 1..4, no 5 | 
				
			|||
                    random.shuffle(cur_palabras[n]) | 
				
			|||
                    for palabra in cur_palabras[n]: | 
				
			|||
                        s = status.get(palabra['id'], 0) | 
				
			|||
                        match einzelne_abfrage(palabra, s): | 
				
			|||
                            case Resultado.BIEN: | 
				
			|||
                                status[palabra['id']] = s + 1 | 
				
			|||
                                bien+=1 | 
				
			|||
                            case Resultado.MAL: | 
				
			|||
                                if s > 0: | 
				
			|||
                                    status[palabra['id']] = s - 1 | 
				
			|||
                                    mal+=1 | 
				
			|||
                                    continue | 
				
			|||
                            case Resultado.ADIOS: | 
				
			|||
                                print(f'{bcolors.OKGREEN}+{bien}{bcolors.ENDC} / {bcolors.FAIL}-{mal}{bcolors.ENDC}') | 
				
			|||
                                return | 
				
			|||
                        with open(STATUS_FILE + '.new', 'w') as f: | 
				
			|||
                            json.dump(status, f) | 
				
			|||
                            f.flush() | 
				
			|||
                            os.fsync(f.fileno()) | 
				
			|||
                        with open(STATUS_FILE + '.new', 'r') as f: | 
				
			|||
                            status = json.load(f) | 
				
			|||
                        os.replace(STATUS_FILE + '.new', STATUS_FILE) | 
				
			|||
 | 
				
			|||
            cur_palabras = {0: [], 1: [], 2: [], 3: [], 4: [], 5: []} | 
				
			|||
            unidad = palabra['unidad'] | 
				
			|||
            paso = palabra['paso'] | 
				
			|||
 | 
				
			|||
        cur_palabras[status.get(palabra['id'], 0)].append(palabra) | 
				
			|||
 | 
				
			|||
parser = argparse.ArgumentParser() | 
				
			|||
default_data_file = os.environ['PWD'] + '/assets/amf/vokabelTrainer147.amf' | 
				
			|||
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) | 
				
			|||
args = parser.parse_args() | 
				
			|||
 | 
				
			|||
if args.import_data: | 
				
			|||
    import_vokabeln(args.import_data + '/amf/vokabelTrainer147.amf', | 
				
			|||
                    args.import_data + '/amf/medium/') | 
				
			|||
elif args.import_status: | 
				
			|||
    import_status(args.import_status) | 
				
			|||
else: | 
				
			|||
    abfrage(parser) | 
				
			|||
						Write
						Preview
					
					
					Loading…
					
					Cancel
						Save
					
		Reference in new issue