Adrian Heine
9 months ago
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