Skip to content

Commit

Permalink
Add proof of concept for TUI
Browse files Browse the repository at this point in the history
  • Loading branch information
jbaublitz committed Sep 3, 2024
1 parent 069e789 commit f015cdf
Show file tree
Hide file tree
Showing 16 changed files with 950 additions and 462 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y libcairo-dev libgtk-3-dev cmake gobject-introspection libgirepository1.0-dev
python -m pip install --upgrade pip
pip install .
pip install pylint
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
*egg-info
build
dist
*.db
35 changes: 23 additions & 12 deletions language-practice
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ Inflection charts are pulled from wiktionary.
"""

import argparse
import asyncio
import sys

from language_practice.terminal import Application
from language_practice.gui import GuiApplication
from language_practice.sqlite import SqliteHandle
from language_practice.terminal import TerminalApplication


class Once(argparse.Action):
Expand All @@ -37,27 +38,37 @@ def main():
prog="language-practice", description="Flashcard app"
)
parse.add_argument("-t", "--traceback", action="store_true")
parse.add_argument("-r", "--reset", action="store_true")
parse.add_argument("-f", "--file", action=Once, required=True)
parse.add_argument("-d", "--dir", action="store_true")
parse.add_argument("-g", "--gui", action="store_true")
parse.add_argument("-d", "--db", action="store", required=True)
args = parse.parse_args()

try:
app = Application(
args.file,
args.dir,
args.reset,
)
asyncio.run(app.startup())
app.run()
handle = SqliteHandle(args.db)
except Exception as err: # pylint: disable=broad-exception-caught
if args.traceback:
raise err
print(f"{err}")
sys.exit(1)

try:
all_sets = handle.get_all_sets()
if args.gui:
GuiApplication(handle, all_sets)
else:
tui = TerminalApplication(handle, all_sets)
tui.run()
except Exception as err: # pylint: disable=broad-exception-caught
if args.traceback:
raise err

print(f"{err}")
handle.close()
sys.exit(1)
except KeyboardInterrupt:
print("Exiting...")
handle.close()
finally:
handle.close()


if __name__ == "__main__":
Expand Down
38 changes: 0 additions & 38 deletions language_practice/cache.py

This file was deleted.

183 changes: 183 additions & 0 deletions language_practice/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
"""
Handles TOML parsing from the configuration file.
"""

from datetime import date
from tomllib import load

from language_practice.repetition import WordRepetition


# pylint: disable=too-many-instance-attributes
class Entry:
"""
A single entry in the TOML file.
"""

# pylint: disable=too-many-arguments
def __init__(
self,
word,
definition,
gender,
aspect,
usage,
part_of_speech,
charts,
repetition,
):
self.word = word
self.definition = definition
self.gender = gender
self.aspect = aspect
self.usage = usage
self.part_of_speech = part_of_speech
self.charts = charts
self.repetition = repetition

def get_word(self):
"""
Get word.
"""
return self.word

def get_definition(self):
"""
Get definition.
"""
return self.definition

def get_gender(self):
"""
Get gender.
"""
return self.gender

def get_aspect(self):
"""
Get aspect.
"""
return self.aspect

def get_usage(self):
"""
Get usage.
"""
return self.usage

def get_part_of_speech(self):
"""
Get part of speech.
"""
return self.part_of_speech

def get_charts(self):
"""
Get charts.
"""
return self.charts

def get_repetition(self):
"""
Get repetition data structure.
"""
return self.repetition


class Config:
"""
Generic config data structure.
"""

def __init__(self, lang, entries):
self.lang = lang
self.words = entries

def __iter__(self):
return iter(self.words)

def __len__(self):
return len(self.words)

def get_lang(self):
"""
Get the language associated with this word file, if any.
"""
return self.lang

def get_words(self):
"""
Get a list of all words in the TOML file.
"""
return self.words

def extend(self, config):
"""
Extend a TOML config with another TOML config.
"""
if self.lang != config.lang:
raise RuntimeError(
f"Attempted to join a TOML config with lang {self.lang} with \
one with lang {config.lang}"
)

self.words += config.words
return self


class GraphicalConfig(Config):
"""
All entries in the graphical config.
"""

def __init__(self, lang, dcts):
try:
words = [
Entry(
dct["word"],
dct["definition"],
dct.get("gender", None),
dct.get("aspect", None),
dct.get("usage", None),
dct.get("part_of_speech", None),
dct.get("charts", None),
WordRepetition(2.5, 0, 0, date.today(), False),
)
for dct in dcts
]
super().__init__(lang, words)
except KeyError as err:
raise RuntimeError(f"Key {err} not found") from err


class TomlConfig(Config):
"""
All entries in the TOML file.
"""

def __init__(self, file_path):
try:
with open(file_path, "rb") as file_handle:
toml = load(file_handle)
lang = toml.get("lang", None)
if lang is not None and lang not in ["fr", "uk", "ru"]:
raise RuntimeError(
f"Language {lang} is not supported; if you would like it to "
"be, please open a feature request!"
)
words = [
Entry(
dct["word"],
dct["definition"],
dct.get("gender", None),
dct.get("aspect", None),
dct.get("usage", None),
dct.get("part_of_speech", None),
dct.get("charts", None),
WordRepetition(2.5, 0, 0, date.today(), False),
)
for dct in toml["words"]
]
super().__init__(lang, words)
except KeyError as err:
raise RuntimeError(f"Key {err} not found") from err
75 changes: 75 additions & 0 deletions language_practice/flashcard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""
Flashcard handling code.
"""

from collections import deque
from datetime import date
from random import shuffle


class Flashcard:
"""
Handler for studying flashcards.
"""

def __init__(self, handle, words):
self.handle = handle

scheduled = []
review = []
for entry in words:
repetition = entry.get_repetition()
if repetition.get_review():
review.append(entry)
if repetition.get_date_of_next() <= date.today():
scheduled.append(entry)

shuffle(scheduled)
self.scheduled = deque(scheduled)
shuffle(review)
self.review = deque(review)
self.complete = []

def current(self):
"""
Get current flashcard.
"""
if len(self.scheduled) > 0:
current_entry = self.scheduled[0]
is_review = False
elif len(self.review) > 0:
current_entry = self.review[0]
is_review = True
else:
current_entry = None
is_review = None

return (current_entry, is_review)

def post_grade(self):
"""
Handle changing to a new flashcard after grading has been completed.
"""
if len(self.scheduled) > 0:
next_entry = self.scheduled.popleft()
else:
next_entry = self.review.popleft()

if next_entry.get_repetition().get_review():
self.review.append(next_entry)
else:
self.complete.append(next_entry)

def get_all_entries(self):
"""
Get all flashcard entries.
"""
return list(self.review) + list(self.scheduled) + self.complete

def save(self):
"""
Save updates to the flashcards.
"""
all_entries = self.get_all_entries()
for entry in all_entries:
self.handle.update_config(entry.get_word(), entry.get_repetition())
23 changes: 23 additions & 0 deletions language_practice/gui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""
Graphical user interface.
"""

# pylint: disable=wrong-import-position
# pylint: disable=too-few-public-methods

import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk


class GuiApplication:
"""
Graphical application.
"""

def __init__(self, _, __):
win = Gtk.Window()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
Loading

0 comments on commit f015cdf

Please sign in to comment.