Init
This commit is contained in:
commit
f05ee8709f
7 changed files with 234 additions and 0 deletions
5
debian/changelog
vendored
Normal file
5
debian/changelog
vendored
Normal file
|
|
@ -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
|
||||
17
debian/control
vendored
Normal file
17
debian/control
vendored
Normal file
|
|
@ -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
|
||||
6
debian/copyright
vendored
Normal file
6
debian/copyright
vendored
Normal file
|
|
@ -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+
|
||||
2
debian/klette.install
vendored
Executable file
2
debian/klette.install
vendored
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/usr/bin/dh-exec
|
||||
main.py => /usr/bin/klette
|
||||
4
debian/rules
vendored
Executable file
4
debian/rules
vendored
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/make -f
|
||||
|
||||
%:
|
||||
dh $@ --with python3
|
||||
1
debian/source/format
vendored
Normal file
1
debian/source/format
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
3.0 (native)
|
||||
199
main.py
Normal file
199
main.py
Normal file
|
|
@ -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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue