MIL-007: Documentation and publication #44

Merged
Tirsvad merged 2 commits from mil-007-documentation-and-publication into main 2026-10-14 16:42:44 +02:00
24 changed files with 914 additions and 111 deletions
+40 -2
View File
@@ -22,11 +22,34 @@ The program shows a Malay word on a card. After three seconds the card turns ove
| `src/flash_card/` | The program, in four layers: `domain`, `application`, `infrastructure`, `presentation` | | `src/flash_card/` | The program, in four layers: `domain`, `application`, `infrastructure`, `presentation` |
| `tests/` | `pytest` tests: `unit`, `integration`, `e2e` | | `tests/` | `pytest` tests: `unit`, `integration`, `e2e` |
| `data/` | The word list `ms-da.csv` and, after the first known word, your own `words_to_learn.csv` | | `data/` | The word list `ms-da.csv` and, after the first known word, your own `words_to_learn.csv` |
| `docs/` | Project documents (business case, plan, design, review records) | | `docs/` | Project documents (business case, plan, design, review records), `docs/wiki/` and `docs/images/` |
| `tools/` | Helper scripts: `generate_images.py` and `capture_screenshots.py` |
| `Doxyfile` | Doxygen configuration for the source code documentation | | `Doxyfile` | Doxygen configuration for the source code documentation |
The core program uses only the Python standard library. Spoken Malay words are an optional extra. The core program uses only the Python standard library. Spoken Malay words are an optional extra.
### Screenshots
| Front: the Malay word | Back: the Danish translation after 3 seconds |
| --- | --- |
| ![The front of a card][screenshot-front] | ![The back of a card][screenshot-back] |
### Where is each assignment step?
| Assignment step | Code |
| --- | --- |
| 1. User interface | `FlashCardView.__init__` in `src/flash_card/presentation/features/flash_card/flash_card_view.py`; images made by `tools/generate_images.py` |
| 2. New flash cards | `CsvVocabularyRepository` (the standard library `csv` instead of `pandas`), `Deck.draw`, `StudySession.next_card`, `FlashCardView.next_card` |
| 3. Flip the cards | `FlashCardView.flip_card` and `FlashCardView.flip_timer` (`after`, `after_cancel`), `FlashCardViewModel.flip_card` |
| 4. Save your progress | `FlashCardView.is_known`, `StudySession.mark_known`, `CsvProgressRepository` |
| Extra: spoken word | `GttsSpeech`, `NullSpeech`, `build_speech` in `presentation/app.py` |
The wiki ([English][wiki-en], [Dansk][wiki-da], [Bahasa Malaysia][wiki-ms]) explains the same in three languages.
### Data
`data/ms-da.csv` holds 1000 frequent Malay words with a Danish machine translation; see [data/README.md][data-readme] for the format and origin.
## 📋 Requirements ## 📋 Requirements
| Need | Why | | Need | Why |
@@ -69,7 +92,14 @@ python -m pip install -e ".[dev]"
python -m flash_card python -m flash_card
``` ```
The program is run from the activated `.venv`. The window opens with the first card; the program is built step by step and the pull requests and milestones show each step. The program is run from the activated `.venv`. The window opens with a card. Press the green tick for a word you know and the red cross for a word you do not. Options:
| Option | Meaning |
| --- | --- |
| `--data-dir <folder>` | Folder with `ms-da.csv` and `words_to_learn.csv` |
| `--no-speech` | Do not speak the Malay word |
To start again, delete `data/words_to_learn.csv`.
## 🧪 Tests ## 🧪 Tests
@@ -87,6 +117,8 @@ lint-imports
The window test in `tests/e2e/` is skipped when no display is available. The window test in `tests/e2e/` is skipped when no display is available.
Build the source code documentation (Doxygen comments) with `doxygen Doxyfile`; the HTML is written to `docs/doxygen/html/`. The screenshots are made with `python tools/capture_screenshots.py` after `python -m pip install -e ".[tools]"` (Pillow, tool only).
## 📄 License ## 📄 License
GNU Affero General Public License v3.0 or later. See [LICENSE][license]. GNU Affero General Public License v3.0 or later. See [LICENSE][license].
@@ -117,6 +149,12 @@ The documents a newcomer needs, in reading order.
--- ---
[license]: ./LICENSE [license]: ./LICENSE
[screenshot-front]: ./docs/images/screenshot-front.png
[screenshot-back]: ./docs/images/screenshot-back.png
[wiki-en]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/wiki/Home
[wiki-da]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/wiki/Home-da
[wiki-ms]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/wiki/Home-ms
[data-readme]: ./data/README.md
[repository]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card [repository]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card
[documentation]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/wiki [documentation]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/wiki
[issues]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/issues [issues]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/issues
+1 -1
View File
@@ -25,7 +25,7 @@ document of a type. `Primary File` may contain a glob (e.g.
| DCD | Design Class Diagram | docs/dcd.md | 002 | | DCD | Design Class Diagram | docs/dcd.md | 002 |
| DICT | Domain Dictionary | docs/dictionary.md | 002 | | DICT | Domain Dictionary | docs/dictionary.md | 002 |
| ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0004 | | ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0004 |
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 029 | | RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 030 |
| TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 | | TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 |
## Languages ## Languages
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+14 -7
View File
@@ -31,13 +31,13 @@ This plan schedules the seven phases (gateways) that deliver the flash card prog
| Gateway | Document | Window | Decision date | Owner | Stories | Main deliverable | Milestone | | Gateway | Document | Window | Decision date | Owner | Stories | Main deliverable | Milestone |
| --- | --- | --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- | --- |
| Project foundation | [MIL-001] | 2026-10-14 | 2026-10-14 | S01 | none | pyproject, gitignore, skeleton, Doxyfile, first README, word list | | | Project foundation | [MIL-001] | 2026-10-14 | 2026-10-14 | S01 | none | pyproject, gitignore, skeleton, Doxyfile, first README, word list | [Milestone MIL-001][MS-001] |
| Step 1 - User interface | [MIL-002] | 2026-10-15 | 2026-10-15 | S01 | US-001.01 | The window with card and buttons | | | Step 1 - User interface | [MIL-002] | 2026-10-15 | 2026-10-15 | S01 | US-001.01 | The window with card and buttons | [Milestone MIL-002][MS-002] |
| Step 2 - New flash cards | [MIL-003] | 2026-10-16 | 2026-10-16 | S01 | US-001.02 | Random cards from the word list | | | Step 2 - New flash cards | [MIL-003] | 2026-10-16 | 2026-10-16 | S01 | US-001.02 | Random cards from the word list | [Milestone MIL-003][MS-003] |
| Step 3 - Flip the cards | [MIL-004] | 2026-10-17 | 2026-10-17 | S01 | US-001.03 | Flip after 3000 ms | | | Step 3 - Flip the cards | [MIL-004] | 2026-10-17 | 2026-10-17 | S01 | US-001.03 | Flip after 3000 ms | [Milestone MIL-004][MS-004] |
| Step 4 - Save progress | [MIL-005] | 2026-10-18 | 2026-10-18 | S01 | US-001.04 | words_to_learn.csv | | | Step 4 - Save progress | [MIL-005] | 2026-10-18 | 2026-10-18 | S01 | US-001.04 | words_to_learn.csv | [Milestone MIL-005][MS-005] |
| Spoken Malay word | [MIL-006] | 2026-10-19 | 2026-10-19 | S01 | US-001.05 | Text to speech as optional extra | | | Spoken Malay word | [MIL-006] | 2026-10-19 | 2026-10-19 | S01 | US-001.05 | Text to speech as optional extra | [Milestone MIL-006][MS-006] |
| Documentation and publication | [MIL-007] | 2026-10-19 to 2026-10-21 | 2026-10-21 | S01 | none | README, screenshots, wiki in 3 languages, description and topics | | | Documentation and publication | [MIL-007] | 2026-10-19 to 2026-10-21 | 2026-10-21 | S01 | none | README, screenshots, wiki in 3 languages, description and topics | [Milestone MIL-007][MS-007] |
```plantuml ```plantuml
@startgantt @startgantt
@@ -106,3 +106,10 @@ A No-Go on a phase moves every later date by the days the rework takes; the buff
[MIL-006]: ./milestones/mil-006-malay-speech.md [MIL-006]: ./milestones/mil-006-malay-speech.md
[MIL-007]: ./milestones/mil-007-documentation-and-publication.md [MIL-007]: ./milestones/mil-007-documentation-and-publication.md
[c3fbcbc]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/commit/c3fbcbcfaf799e0ae34aed7e14d3bc8a62f03db3 [c3fbcbc]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/commit/c3fbcbcfaf799e0ae34aed7e14d3bc8a62f03db3
[MS-001]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/milestone/165
[MS-002]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/milestone/166
[MS-003]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/milestone/167
[MS-004]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/milestone/168
[MS-005]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/milestone/169
[MS-006]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/milestone/170
[MS-007]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/milestone/171
+64
View File
@@ -0,0 +1,64 @@
# RC-029: Review of PY-MIL-007
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-029 |
| CrossReference | [PY-MIL-007], [QC-PY-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | [9289426] |
---
## Artifact Under Review
- Instance reviewed: [PY-MIL-007]
- Checklist used: [QC-PY-001]
- Scope: full review
- Language and domain: n/a (technical type)
- Language reviewer: none
- Mode: autonomous, not independent (reviewer is `AI as stand-in for S01`)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | Packages, modules, functions, variables, classes and constants follow PEP 8 casing (`snake_case`, `PascalCase`, `UPPER_SNAKE`) | Pass | PEP 8 casing holds in `tools/capture_screenshots.py` and the changed view; ruff rule set N passes. |
| 2 | Names state purpose in the domain's language; no unexplained abbreviations, no single-letter names outside tiny scopes | Pass | Names state purpose (`capture`, `_make_dpi_aware`, `SCREENSHOT_SEED`). |
| 3 | Code is produced by the project's formatter and passes its linter with no unexplained suppressions | Pass | `ruff check` and `ruff format --check` pass on 56 files; the one `type: ignore` is for a Windows-only attribute and says which error it silences. |
| 4 | Every function and method signature is type-annotated, including `-> None` | Pass | Every signature is annotated; `mypy --strict` passes on `src` (38 files) and on both tool scripts. |
| 5 | No bare `except:`, no swallowed exceptions; specific exceptions are raised and the cause is kept (`raise ... from`) | Pass | No exception handling was added. |
| 6 | No mutable default arguments and no shadowed builtins | Pass | No mutable defaults, no builtin shadowed. |
| 7 | Files, locks and connections are managed with context managers | Pass | The data copy uses `tempfile.TemporaryDirectory` as a context manager; the Tk root is destroyed. |
| 8 | Public modules, classes and functions have docstrings that say what, not how | Pass | Module, functions and constants have Doxygen comments; `doxygen Doxyfile` ends with 0 warnings. |
| 9 | Logging uses `logging`, not `print`; no secrets or personal data in log output | Pass | The tool writes the two output paths with `sys.stdout.write`; no `print`, no secret. |
| 10 | Classes and operations trace to the Design Class Diagram they implement; deviations are recorded | Pass | The only source change is the button background in `FlashCardView`, which follows `wireframe.md`; the tool is outside the design classes and is not part of the program. |
| 11 | Tests exist for new behaviour, are named for the behaviour, and do not depend on order or the network | Pass | 125 tests, 2 consecutive full runs green; one e2e assertion was added for the button background; the tool is checked by running it and reading both images. |
| 12 | Type checker runs in strict mode without errors; `Any` is justified in a comment | Pass | `mypy --strict` reports no issue on the touched files; no `Any`. |
| 13 | Dependencies are declared and pinned in the project's dependency file, none unused | Pass | `dependencies` stays empty; Pillow is the optional extra `tools` and is imported only by the tool. |
| 14 | Each piece of knowledge (a business rule, constant, format, validation or query) is defined in one place; code is merged only where it expresses the same knowledge, not where it merely looks alike | Pass | The seed, output folder and delay come from one definition each; the delay is `FLIP_DELAY_MS`. |
| 15 | Imports point inward: business-rule modules import no framework, database, UI or delivery-mechanism code, and there are no import cycles | Pass | `lint-imports` reports `Layers point inward` kept; the tool imports the composition root only. |
| 16 | SOLID holds: a class or module has one reason to change; new behaviour is added by extension, not by editing a type test in several places; subclasses honour the contract of their base; protocols are narrow; high-level code depends on protocols or abstract types and receives its implementations | Pass | The tool has one purpose (capture); the view change adds no rule. |
| 17 | Source is laid out in the layers and folders of the clean architecture folder structure (`domain`, `application`, `infrastructure`, `presentation` packages under `src/`, and `unit`, `integration` and `e2e` under `tests/`), in `snake_case`, and imports between the layers point inward | Pass | `tools/` holds helper scripts outside `src/`; the layers are unchanged. |
| 18 | File and folder names are English, contain no space and no character outside letters, digits, `-`, `_` and `.`, and follow the casing of the language for files | Pass | File and folder names follow the rule (`docs/images`, `docs/wiki` pages are exceptions for wiki names). |
## Overall Verdict
Go — every Mandatory criterion passes. Reviewed by AI as stand-in for S01; not independent of the author.
Milestone MIL-007 Go criteria: 1 `check-readme.sh` exit 0 and both screenshots linked from the README; 2 `docs/wiki/` holds Home, Install, Usage and Architecture as `<Page>.md`, `<Page>-da.md`, `<Page>-ms.md` (12 pages) and `_Sidebar.md` listing the three languages; 3 the screenshots are captured from the running program by `tools/capture_screenshots.py` and were inspected; 4 the README Setup and Run need 5 commands (create `.venv`, activate, upgrade pip, install, run); 5 the repository has a description and 10 topics (read back from the API); 6 `doxygen Doxyfile` 0 warnings; 7 `docs/sqa/traceability-matrix.md` has a row for every created artifact with its latest `RC-*`, and the plan links each milestone; 8 `check-languages.sh` exit 0 (`check-plan.sh` is a commit-message hook and was not run). The wiki is pushed to the host's wiki after this merge. The Danish and Malay wiki pages were written by the AI and have not been read by a native speaker; this is stated in the end-of-run summary. All Go.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| none | - | - |
---
[PY-MIL-007]: ../../../tools
[QC-PY-001]: ../../../framework/qc/code/qc-programming-python.md
[9289426]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/commit/92894268c51ec77d56cff48d968566e6265795a2
+3
View File
@@ -51,6 +51,7 @@ updated whenever an artifact instance is created or reviewed.
| PY-MIL-004 | Python Source Code | - | - | DCD-001, ADR-0002 | - | [RC-026] | | PY-MIL-004 | Python Source Code | - | - | DCD-001, ADR-0002 | - | [RC-026] |
| PY-MIL-005 | Python Source Code | - | - | DCD-001, ADR-0002 | - | [RC-027] | | PY-MIL-005 | Python Source Code | - | - | DCD-001, ADR-0002 | - | [RC-027] |
| PY-MIL-006 | Python Source Code | - | - | DCD-001, ADR-0002 | - | [RC-028] | | PY-MIL-006 | Python Source Code | - | - | DCD-001, ADR-0002 | - | [RC-028] |
| PY-MIL-007 | Python Source Code | - | - | DCD-001, ADR-0002 | - | [RC-029] |
## Coverage Notes ## Coverage Notes
@@ -117,3 +118,5 @@ updated whenever an artifact instance is created or reviewed.
[PY-MIL-005]: ../../src/flash_card [PY-MIL-005]: ../../src/flash_card
[RC-028]: ./reviews/rc-028-py-mil-006.md [RC-028]: ./reviews/rc-028-py-mil-006.md
[PY-MIL-006]: ../../src/flash_card [PY-MIL-006]: ../../src/flash_card
[RC-029]: ./reviews/rc-029-py-mil-007.md
[PY-MIL-007]: ../../src/flash_card
+40
View File
@@ -0,0 +1,40 @@
# Arkitektur
🌐 [English](Architecture) · **Dansk** · [Bahasa Malaysia](Architecture-ms)
Programmet er bygget i fire lag under `src/flash_card/`. Importer peger indad; `lint-imports` kontrollerer det.
| Lag | Indeholder | Må importere |
| --- | --- | --- |
| `domain` | `Card`, `LanguagePair`, `Vocabulary`, `Deck`, fejlene og repository-grænsefladerne | ingenting |
| `application` | `StudySession`, `open_study_session`, `CardDto` og `SpeechPort` | `domain` |
| `infrastructure` | CSV-repositories og tale-adaptere | `application`, `domain` |
| `presentation` | `FlashCardView`, `FlashCardViewModel` (MVVM) og kompositionsroden `app.py` | alle lag |
Alle konstanter står i `src/flash_card/constants.py`.
## Hvor er hvert opgavetrin?
| Opgave | Kode |
| --- | --- |
| Trin 1: vindue, lærred, billede, tekster, knapper | `FlashCardView.__init__` i `presentation/features/flash_card/flash_card_view.py`; billederne laves af `tools/generate_images.py` |
| Trin 2: læs CSV, tilfældigt ord ved hvert tryk | `CsvVocabularyRepository` og `read_word_list` (erstatter `pandas`), `Deck.draw`, `StudySession.next_card`, `FlashCardView.next_card` |
| Trin 3: vend efter 3 sekunder | `FlashCardView.flip_card`, `FlashCardView.flip_timer` (`after`, `after_cancel`), `FlashCardViewModel.flip_card` |
| Trin 4: kendte ord, `words_to_learn.csv` | `FlashCardView.is_known`, `StudySession.mark_known`, `CsvProgressRepository`, `open_study_session` |
| Ekstra: tale | `SpeechPort`, `GttsSpeech`, `NullSpeech`, `build_speech` i `presentation/app.py` |
## Hvorfor en lagdelt løsning til en lille opgave?
Reglerne (hvilket kort der kommer næste gang, hvad et kendt ord gør) testes uden vindue og uden filsystem. Vinduet, CSV-filerne og taletjenesten kan udskiftes uden at røre reglerne. Se ADR-0001 og ADR-0002 i `docs/adr/`.
## Test
| Mappe | Hvad den tester |
| --- | --- |
| `tests/unit/` | Hvert lag for sig, med falske objekter til portene |
| `tests/integration/` | Den rigtige ordliste og fremgangsfilen i en midlertidig mappe |
| `tests/e2e/` | Det rigtige Tkinter-vindue (springes over uden skærm) |
## Projektdokumenter
Business case, plan, design og gennemgangsrapporter ligger i `docs/`. Arbejdet blev udført i syv milepæle, én pull request hver: se `docs/milestones/`.
+40
View File
@@ -0,0 +1,40 @@
# Seni bina
🌐 [English](Architecture) · [Dansk](Architecture-da) · **Bahasa Malaysia**
Program dibina dalam empat lapisan di bawah `src/flash_card/`. Import menuju ke dalam; `lint-imports` memeriksanya.
| Lapisan | Mengandungi | Boleh mengimport |
| --- | --- | --- |
| `domain` | `Card`, `LanguagePair`, `Vocabulary`, `Deck`, ralat dan antara muka repositori | tiada |
| `application` | `StudySession`, `open_study_session`, `CardDto` dan `SpeechPort` | `domain` |
| `infrastructure` | Repositori CSV dan penyesuai suara | `application`, `domain` |
| `presentation` | `FlashCardView`, `FlashCardViewModel` (MVVM) dan akar komposisi `app.py` | semua lapisan |
Semua pemalar terdapat dalam `src/flash_card/constants.py`.
## Di manakah setiap langkah tugasan?
| Tugasan | Kod |
| --- | --- |
| Langkah 1: tetingkap, kanvas, imej, teks, butang | `FlashCardView.__init__` dalam `presentation/features/flash_card/flash_card_view.py`; imej dibuat oleh `tools/generate_images.py` |
| Langkah 2: baca CSV, perkataan rawak setiap tekanan | `CsvVocabularyRepository` dan `read_word_list` (menggantikan `pandas`), `Deck.draw`, `StudySession.next_card`, `FlashCardView.next_card` |
| Langkah 3: terbalik selepas 3 saat | `FlashCardView.flip_card`, `FlashCardView.flip_timer` (`after`, `after_cancel`), `FlashCardViewModel.flip_card` |
| Langkah 4: perkataan diketahui, `words_to_learn.csv` | `FlashCardView.is_known`, `StudySession.mark_known`, `CsvProgressRepository`, `open_study_session` |
| Tambahan: suara | `SpeechPort`, `GttsSpeech`, `NullSpeech`, `build_speech` dalam `presentation/app.py` |
## Mengapa program berlapis untuk tugasan kecil?
Peraturan (kad mana yang seterusnya, apa yang berlaku apabila perkataan diketahui) diuji tanpa tetingkap atau sistem fail. Tetingkap, fail CSV dan perkhidmatan suara boleh diganti tanpa menyentuh peraturan. Lihat ADR-0001 dan ADR-0002 dalam `docs/adr/`.
## Ujian
| Folder | Apa yang diuji |
| --- | --- |
| `tests/unit/` | Setiap lapisan sendiri, dengan objek palsu untuk port |
| `tests/integration/` | Senarai perkataan sebenar dan fail kemajuan dalam folder sementara |
| `tests/e2e/` | Tetingkap Tkinter sebenar (dilangkau tanpa paparan) |
## Dokumen projek
Kes perniagaan, pelan, reka bentuk dan rekod semakan terdapat dalam `docs/`. Kerja dilakukan dalam tujuh pencapaian, satu pull request setiap satu: lihat `docs/milestones/`.
+40
View File
@@ -0,0 +1,40 @@
# Architecture
🌐 **English** · [Dansk](Architecture-da) · [Bahasa Malaysia](Architecture-ms)
The program is built in four layers under `src/flash_card/`. Imports point inward; `lint-imports` checks this.
| Layer | Holds | May import |
| --- | --- | --- |
| `domain` | `Card`, `LanguagePair`, `Vocabulary`, `Deck`, the errors and the repository interfaces | nothing |
| `application` | `StudySession`, `open_study_session`, `CardDto` and `SpeechPort` | `domain` |
| `infrastructure` | The CSV repositories and the speech adapters | `application`, `domain` |
| `presentation` | `FlashCardView`, `FlashCardViewModel` (MVVM) and the composition root `app.py` | all layers |
All constants are in `src/flash_card/constants.py`.
## Where is each assignment step?
| Assignment | Code |
| --- | --- |
| Step 1: window, canvas, image, texts, buttons | `FlashCardView.__init__` in `presentation/features/flash_card/flash_card_view.py`; images made by `tools/generate_images.py` |
| Step 2: read the CSV, random word on each press | `CsvVocabularyRepository` and `read_word_list` (replace `pandas`), `Deck.draw`, `StudySession.next_card`, `FlashCardView.next_card` |
| Step 3: flip after 3 seconds | `FlashCardView.flip_card`, `FlashCardView.flip_timer` (`after`, `after_cancel`), `FlashCardViewModel.flip_card` |
| Step 4: known words, `words_to_learn.csv` | `FlashCardView.is_known`, `StudySession.mark_known`, `CsvProgressRepository`, `open_study_session` |
| Extra: speech | `SpeechPort`, `GttsSpeech`, `NullSpeech`, `build_speech` in `presentation/app.py` |
## Why a layered program for a small assignment?
The rules (which card comes next, what a known word does) are tested without a window or a file system. The window, the CSV files and the speech service can be replaced without touching the rules. See ADR-0001 and ADR-0002 in `docs/adr/`.
## Tests
| Folder | What it tests |
| --- | --- |
| `tests/unit/` | Each layer alone, with fakes for the ports |
| `tests/integration/` | The real word list and the progress file in a temporary folder |
| `tests/e2e/` | The real Tkinter window (skipped when no display exists) |
## Project documents
The business case, plan, design and review records are in `docs/`. The work was done in seven milestones, one pull request each: see `docs/milestones/`.
+30
View File
@@ -0,0 +1,30 @@
# Flash Card
🌐 [English](Home) · **Dansk** · [Bahasa Malaysia](Home-ms)
Et flashkortprogram til at lære de mest almindelige malajiske ord med danske oversættelser. Det er løsningen på dag 31 i Udemys kursus *100 Days of Code: The Complete Python Pro Bootcamp*.
![Forsiden af et kort: det malajiske ord](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/raw/branch/main/docs/images/screenshot-front.png)
![Bagsiden af kortet efter tre sekunder: den danske oversættelse](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/raw/branch/main/docs/images/screenshot-back.png)
| Side | Læs den, når du vil |
| --- | --- |
| [Installation](Install-da) | Oprette `.venv`, installere programmet og køre testene |
| [Brug](Usage-da) | Øve kortene, forstå fremgangsfilen og bruge indstillingerne |
| [Arkitektur](Architecture-da) | Se lagene og finde koden til hvert opgavetrin |
## De fire opgavetrin
| Trin | Hvad programmet gør | Milepæl |
| --- | --- | --- |
| 1. Brugerflade | Et vindue med et kort og to knapper | MIL-002 |
| 2. Nye flashkort | Et tilfældigt ord fra ordlisten ved hvert tryk på en knap | MIL-003 |
| 3. Vend kortene | Efter 3 sekunder viser kortet den danske oversættelse | MIL-004 |
| 4. Gem din fremgang | Kendte ord fjernes, og resten gemmes i `words_to_learn.csv` | MIL-005 |
| Ekstra | Det malajiske ord bliver udtalt (valgfrit) | MIL-006 |
## Til kursister
Opgavens funktionsnavne er bevaret: `next_card`, `flip_card`, `is_known` og attributterne `flip_timer` og `current_card`. Data læses med standardbibliotekets `csv`-modul i stedet for `pandas`, så kerneprogrammet ikke har nogen afhængigheder ved kørsel.
Repository: <https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card>
+30
View File
@@ -0,0 +1,30 @@
# Flash Card
🌐 [English](Home) · [Dansk](Home-da) · **Bahasa Malaysia**
Program kad imbas untuk mempelajari perkataan Melayu yang paling kerap digunakan beserta terjemahan Denmark. Ia ialah penyelesaian hari ke-31 kursus Udemy *100 Days of Code: The Complete Python Pro Bootcamp*.
![Bahagian depan kad: perkataan Melayu](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/raw/branch/main/docs/images/screenshot-front.png)
![Bahagian belakang kad selepas tiga saat: terjemahan Denmark](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/raw/branch/main/docs/images/screenshot-back.png)
| Halaman | Baca apabila anda mahu |
| --- | --- |
| [Pemasangan](Install-ms) | Mencipta `.venv`, memasang program dan menjalankan ujian |
| [Penggunaan](Usage-ms) | Berlatih dengan kad, memahami fail kemajuan dan menggunakan pilihan |
| [Seni bina](Architecture-ms) | Melihat lapisan dan mencari kod bagi setiap langkah tugasan |
## Empat langkah tugasan
| Langkah | Apa yang program lakukan | Pencapaian |
| --- | --- | --- |
| 1. Antara muka pengguna | Tetingkap dengan satu kad dan dua butang | MIL-002 |
| 2. Kad imbas baharu | Satu perkataan rawak daripada senarai perkataan setiap kali butang ditekan | MIL-003 |
| 3. Terbalikkan kad | Selepas 3 saat kad menunjukkan terjemahan Denmark | MIL-004 |
| 4. Simpan kemajuan anda | Perkataan yang diketahui dibuang dan selebihnya disimpan dalam `words_to_learn.csv` | MIL-005 |
| Tambahan | Perkataan Melayu disebut (pilihan) | MIL-006 |
## Untuk peserta kursus
Nama fungsi tugasan dikekalkan: `next_card`, `flip_card`, `is_known`, serta atribut `flip_timer` dan `current_card`. Data dibaca dengan modul `csv` pustaka standard dan bukan `pandas`, jadi program teras tidak mempunyai kebergantungan semasa larian.
Repositori: <https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card>
+30
View File
@@ -0,0 +1,30 @@
# Flash Card
🌐 **English** · [Dansk](Home-da) · [Bahasa Malaysia](Home-ms)
A flash card program for learning the most frequent Malay words with their Danish translations. It is the solution of day 31 of Udemy's *100 Days of Code: The Complete Python Pro Bootcamp*.
![The front of a card: the Malay word](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/raw/branch/main/docs/images/screenshot-front.png)
![The back of the card after three seconds: the Danish translation](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/raw/branch/main/docs/images/screenshot-back.png)
| Page | Read it when you want to |
| --- | --- |
| [Install](Install) | Create the `.venv`, install the program and run the tests |
| [Usage](Usage) | Study the cards, understand the progress file, use the options |
| [Architecture](Architecture) | See the layers and find the code of each assignment step |
## The four assignment steps
| Step | What the program does | Milestone |
| --- | --- | --- |
| 1. User interface | A window with a card and two buttons | MIL-002 |
| 2. New flash cards | A random word from the word list on every button press | MIL-003 |
| 3. Flip the cards | After 3 seconds the card shows the Danish translation | MIL-004 |
| 4. Save your progress | Known words are removed and the rest is saved in `words_to_learn.csv` | MIL-005 |
| Extra | The Malay word is spoken (optional) | MIL-006 |
## For course participants
The function names of the assignment are kept: `next_card`, `flip_card`, `is_known`, and the attributes `flip_timer` and `current_card`. The data is read with the standard library `csv` module instead of `pandas`, so the core program has no runtime dependency.
Repository: <https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card>
+65
View File
@@ -0,0 +1,65 @@
# Installation
🌐 [English](Install) · **Dansk** · [Bahasa Malaysia](Install-ms)
## Krav
- Python 3.13 eller nyere med Tkinter (på Debian eller Ubuntu: `sudo apt install python3-tk`).
- `git`.
- Valgfrit: en internetforbindelse og tilvalget `speech`, hvis du vil høre det malajiske ord.
## Hent koden
```bash
git clone https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card.git
cd 031-flash-card
```
## Opret og brug et lokalt virtuelt miljø
Windows (PowerShell):
```powershell
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
Linux og macOS:
```bash
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
- `.[dev]` tilføjer `pytest`, `ruff`, `mypy` og `import-linter`. Brug `.`, hvis du kun vil køre programmet.
- Hvis PowerShell nægter at køre `Activate.ps1`, så kør `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` én gang.
- Forlad miljøet med `deactivate`; slet mappen `.venv` for at starte forfra.
## Udtalt malajisk ord (valgfrit)
```bash
python -m pip install -e ".[speech]"
```
Det installerer `gTTS` og `playsound==1.2.2`. Uden tilvalget virker programmet uden lyd.
## Kør og test
```bash
python -m flash_card
python -m pytest
```
Vinduestesten i `tests/e2e/` springes over, når der ikke er en skærm.
## Dokumentation af kildekoden
```bash
doxygen Doxyfile
```
HTML-filerne skrives til `docs/doxygen/html/index.html`.
+65
View File
@@ -0,0 +1,65 @@
# Pemasangan
🌐 [English](Install) · [Dansk](Install-da) · **Bahasa Malaysia**
## Keperluan
- Python 3.13 atau lebih baharu dengan Tkinter (pada Debian atau Ubuntu: `sudo apt install python3-tk`).
- `git`.
- Pilihan: sambungan internet dan tambahan `speech` jika anda mahu mendengar perkataan Melayu.
## Dapatkan kod
```bash
git clone https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card.git
cd 031-flash-card
```
## Cipta dan gunakan persekitaran maya setempat
Windows (PowerShell):
```powershell
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
Linux dan macOS:
```bash
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
- `.[dev]` menambah `pytest`, `ruff`, `mypy` dan `import-linter`. Gunakan `.` jika anda hanya mahu menjalankan program.
- Jika PowerShell enggan menjalankan `Activate.ps1`, jalankan `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` sekali.
- Keluar daripada persekitaran dengan `deactivate`; padam folder `.venv` untuk bermula semula.
## Perkataan Melayu bersuara (pilihan)
```bash
python -m pip install -e ".[speech]"
```
Ini memasang `gTTS` dan `playsound==1.2.2`. Tanpa tambahan ini program berfungsi tanpa bunyi.
## Jalankan dan uji
```bash
python -m flash_card
python -m pytest
```
Ujian tetingkap dalam `tests/e2e/` dilangkau apabila tiada paparan.
## Dokumentasi kod sumber
```bash
doxygen Doxyfile
```
HTML ditulis ke `docs/doxygen/html/index.html`.
+65
View File
@@ -0,0 +1,65 @@
# Install
🌐 **English** · [Dansk](Install-da) · [Bahasa Malaysia](Install-ms)
## Requirements
- Python 3.13 or later with Tkinter (on Debian or Ubuntu: `sudo apt install python3-tk`).
- `git`.
- Optional: an internet connection and the `speech` extra to hear the Malay word.
## Get the code
```bash
git clone https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card.git
cd 031-flash-card
```
## Create and use a local virtual environment
Windows (PowerShell):
```powershell
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
Linux and macOS:
```bash
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
- `.[dev]` adds `pytest`, `ruff`, `mypy` and `import-linter`. Use `.` if you only want to run the program.
- If PowerShell refuses to run `Activate.ps1`, run `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` once.
- Leave the environment with `deactivate`; delete the `.venv` folder to start over.
## Spoken Malay word (optional)
```bash
python -m pip install -e ".[speech]"
```
This installs `gTTS` and `playsound==1.2.2`. Without it the program works silently.
## Run and test
```bash
python -m flash_card
python -m pytest
```
The window test in `tests/e2e/` is skipped when no display is available.
## Documentation of the source code
```bash
doxygen Doxyfile
```
The HTML is written to `docs/doxygen/html/index.html`.
+49
View File
@@ -0,0 +1,49 @@
# Brug
🌐 [English](Usage) · **Dansk** · [Bahasa Malaysia](Usage-ms)
## Øv kortene
1. Start programmet med `python -m flash_card`.
2. Et kort viser et malajisk ord. Prøv at huske den danske oversættelse.
3. Efter tre sekunder vender kortet og viser oversættelsen.
4. Tryk på det grønne flueben, hvis du kendte ordet, eller på det røde kryds, hvis du ikke gjorde.
5. Et nyt tilfældigt ord vises. Gentag.
Et ord, du har markeret som kendt, fjernes og kommer ikke igen. Når der ikke er flere ord, står der "Nothing left to learn" på kortet.
## Din fremgang
- Første gang du trykker på det grønne flueben, skriver programmet `data/words_to_learn.csv` med de ord, du endnu ikke kan.
- Næste start bruger den fil. Findes den ikke, bruger programmet hele ordlisten `data/ms-da.csv`.
- For at starte forfra skal du slette `data/words_to_learn.csv`.
- Det røde kryds ændrer aldrig filen.
## Filformat
Begge filer er UTF-8-tekst med ét kort pr. linje og cellerne adskilt af semikolon. Første linje har de to sprogkoder.
```text
ms;da
aku;mig
yang;hvilke
```
## Indstillinger
| Indstilling | Betydning |
| --- | --- |
| `--data-dir <mappe>` | Mappen med `ms-da.csv` og `words_to_learn.csv` (standard: `data/` i repositoryet) |
| `--no-speech` | Udtal ikke det malajiske ord, selv om tilvalget til tale er installeret |
## Brug et andet sprogpar
Læg en fil med samme format i datamappen og kald den `ms-da.csv`, eller ret `VOCABULARY_FILE_NAME` og `LANGUAGE_NAMES` i `src/flash_card/constants.py`. Kodene i overskriften vises som sprognavne fra `LANGUAGE_NAMES`; ukendte koder vises med store bogstaver.
## Fejlfinding
| Problem | Hvad du skal gøre |
| --- | --- |
| `No module named tkinter` | Installer Tkinter (`python3-tk` på Debian og Ubuntu) |
| Ingen lyd | Installer tilvalget `speech` og tjek netværket; programmet logger en advarsel, når udtalen mislykkes |
| Programmet stopper med en meddelelse om filen | CSV-filen er forkert opbygget; meddelelsen nævner filen og linjen. Filen bliver ikke overskrevet |
+49
View File
@@ -0,0 +1,49 @@
# Penggunaan
🌐 [English](Usage) · [Dansk](Usage-da) · **Bahasa Malaysia**
## Berlatih dengan kad
1. Mulakan program dengan `python -m flash_card`.
2. Kad menunjukkan satu perkataan Melayu. Cuba ingat terjemahan Denmarknya.
3. Selepas tiga saat kad terbalik dan menunjukkan terjemahan.
4. Tekan tanda semak hijau jika anda tahu perkataan itu, atau pangkah merah jika tidak.
5. Satu perkataan rawak baharu muncul. Ulang.
Perkataan yang anda tandakan sebagai diketahui dibuang dan tidak muncul lagi. Apabila tiada perkataan lagi, kad menunjukkan "Nothing left to learn".
## Kemajuan anda
- Kali pertama anda menekan tanda semak hijau, program menulis `data/words_to_learn.csv` dengan perkataan yang anda belum tahu.
- Permulaan seterusnya menggunakan fail itu. Jika ia tiada, program menggunakan senarai penuh `data/ms-da.csv`.
- Untuk bermula semula, padam `data/words_to_learn.csv`.
- Pangkah merah tidak pernah mengubah fail.
## Format fail
Kedua-dua fail ialah teks UTF-8 dengan satu kad setiap baris dan sel dipisahkan oleh koma bertitik. Baris pertama mengandungi dua kod bahasa.
```text
ms;da
aku;mig
yang;hvilke
```
## Pilihan
| Pilihan | Maksud |
| --- | --- |
| `--data-dir <folder>` | Folder yang mengandungi `ms-da.csv` dan `words_to_learn.csv` (lalai: `data/` dalam repositori) |
| `--no-speech` | Jangan sebut perkataan Melayu, walaupun tambahan suara dipasang |
## Gunakan pasangan bahasa lain
Letakkan fail dengan format yang sama dalam folder data dan namakannya `ms-da.csv`, atau ubah `VOCABULARY_FILE_NAME` dan `LANGUAGE_NAMES` dalam `src/flash_card/constants.py`. Kod pada baris pertama dipaparkan sebagai nama bahasa daripada `LANGUAGE_NAMES`; kod yang tidak dikenali dipaparkan dengan huruf besar.
## Penyelesaian masalah
| Masalah | Apa yang perlu dilakukan |
| --- | --- |
| `No module named tkinter` | Pasang Tkinter (`python3-tk` pada Debian dan Ubuntu) |
| Tiada bunyi | Pasang tambahan `speech` dan periksa rangkaian; program mencatat amaran apabila penyebutan gagal |
| Program berhenti dengan mesej tentang fail | Fail CSV tidak betul; mesej menyebut fail dan barisnya. Fail tidak ditulis ganti |
+49
View File
@@ -0,0 +1,49 @@
# Usage
🌐 **English** · [Dansk](Usage-da) · [Bahasa Malaysia](Usage-ms)
## Study the cards
1. Start the program with `python -m flash_card`.
2. A card shows a Malay word. Try to remember its Danish translation.
3. After three seconds the card turns over and shows the translation.
4. Press the green tick when you knew the word, or the red cross when you did not.
5. A new random word appears. Repeat.
A word you marked as known is removed and does not come up again. When no word is left, the card says "Nothing left to learn".
## Your progress
- The first time you press the green tick, the program writes `data/words_to_learn.csv` with the words you do not know yet.
- The next start uses that file. If it does not exist, the program uses the full word list `data/ms-da.csv`.
- To start again, delete `data/words_to_learn.csv`.
- The red cross never changes the file.
## File format
Both files are UTF-8 text with one card per line and the cells separated by a semicolon. The first line holds the two language codes.
```text
ms;da
aku;mig
yang;hvilke
```
## Options
| Option | Meaning |
| --- | --- |
| `--data-dir <folder>` | Folder that holds `ms-da.csv` and `words_to_learn.csv` (default: `data/` of the repository) |
| `--no-speech` | Do not speak the Malay word, even when the speech extra is installed |
## Use another language pair
Put a file with the same format in the data folder and name it `ms-da.csv`, or change `VOCABULARY_FILE_NAME` and `LANGUAGE_NAMES` in `src/flash_card/constants.py`. The header codes are shown as language names from `LANGUAGE_NAMES`; unknown codes are shown in upper case.
## Troubleshooting
| Problem | What to do |
| --- | --- |
| `No module named tkinter` | Install Tkinter (`python3-tk` on Debian and Ubuntu) |
| No sound | Install the `speech` extra and check the network; the program logs a warning when speaking fails |
| The program stops with a message about the file | The CSV file is malformed; the message names the file and the line. The file is not overwritten |
+12
View File
@@ -0,0 +1,12 @@
**Flash Card**
- [Home](Home)
- [Install](Install)
- [Usage](Usage)
- [Architecture](Architecture)
**Languages**
- English: [Home](Home) · [Install](Install) · [Usage](Usage) · [Architecture](Architecture)
- Dansk: [Hjem](Home-da) · [Installation](Install-da) · [Brug](Usage-da) · [Arkitektur](Architecture-da)
- Bahasa Malaysia: [Utama](Home-ms) · [Pemasangan](Install-ms) · [Penggunaan](Usage-ms) · [Seni bina](Architecture-ms)
+2
View File
@@ -24,6 +24,8 @@ dependencies = []
[project.optional-dependencies] [project.optional-dependencies]
# Spoken Malay word (text to speech). Optional; the program works without it. # Spoken Malay word (text to speech). Optional; the program works without it.
speech = ["gTTS>=2.5", "playsound==1.2.2"] speech = ["gTTS>=2.5", "playsound==1.2.2"]
# Only for tools/capture_screenshots.py; not needed to run the program.
tools = ["pillow>=10"]
dev = ["pytest>=8", "ruff>=0.6", "mypy>=1.11", "import-linter>=2.0"] dev = ["pytest>=8", "ruff>=0.6", "mypy>=1.11", "import-linter>=2.0"]
[project.scripts] [project.scripts]
@@ -107,6 +107,9 @@ class FlashCardView:
self.wrong_button = tk.Button( self.wrong_button = tk.Button(
root, root,
image=self._wrong_image, image=self._wrong_image,
bg=BACKGROUND_COLOR,
activebackground=BACKGROUND_COLOR,
relief="flat",
highlightthickness=0, highlightthickness=0,
bd=0, bd=0,
command=self.next_card, command=self.next_card,
@@ -116,6 +119,9 @@ class FlashCardView:
self.right_button = tk.Button( self.right_button = tk.Button(
root, root,
image=self._right_image, image=self._right_image,
bg=BACKGROUND_COLOR,
activebackground=BACKGROUND_COLOR,
relief="flat",
highlightthickness=0, highlightthickness=0,
bd=0, bd=0,
command=self.is_known, command=self.is_known,
+8
View File
@@ -163,6 +163,14 @@ def test_buttons_sit_in_row_one_with_their_images(
assert str(view.right_button.cget("command")) assert str(view.right_button.cget("command"))
def test_buttons_sit_on_the_window_background(
window: tuple[tk.Tk, FlashCardView],
) -> None:
_root, view = window
assert view.wrong_button.cget("bg") == BACKGROUND_COLOR
assert view.right_button.cget("bg") == BACKGROUND_COLOR
def test_wrong_button_press_shows_next_card( def test_wrong_button_press_shows_next_card(
window: tuple[tk.Tk, FlashCardView], window: tuple[tk.Tk, FlashCardView],
) -> None: ) -> None:
+111
View File
@@ -0,0 +1,111 @@
"""Capture screenshots of the running flash card window.
@file capture_screenshots.py
@brief Starts the program, grabs the front of a card and, three seconds later,
the back, and saves both as PNG files.
The tool needs Pillow (`python -m pip install -e ".[tools]"`), which is a tool
dependency only and not a dependency of the program. It grabs the screen area
of the window, so it needs a visible desktop.
"""
from __future__ import annotations
import argparse
import ctypes
import random
import shutil
import sys
import tempfile
import time
from collections.abc import Sequence
from pathlib import Path
from PIL import ImageGrab
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from flash_card.constants import ( # noqa: E402
DEFAULT_DATA_DIR,
FLIP_DELAY_MS,
PROJECT_ROOT,
VOCABULARY_FILE_NAME,
)
from flash_card.presentation.app import create_application # noqa: E402
## @brief Default folder of the screenshots.
DEFAULT_OUTPUT_DIR = PROJECT_ROOT / "docs" / "images"
## @brief Seed that makes the first card the same on every run.
SCREENSHOT_SEED = 7
## @brief Extra time in seconds to let the window settle before a grab.
SETTLE_SECONDS = 0.5
def _make_dpi_aware() -> None:
"""Make Windows report real pixel coordinates (no effect elsewhere)."""
if sys.platform == "win32":
ctypes.windll.shcore.SetProcessDpiAwareness(2) # type: ignore[attr-defined,unused-ignore]
def _pump(root: object, seconds: float) -> None:
"""Run the Tk event loop for a while.
@param root The Tk root window.
@param seconds How long to run.
"""
end = time.monotonic() + seconds
while time.monotonic() < end:
root.update() # type: ignore[attr-defined]
time.sleep(0.01)
def capture(output_dir: Path) -> list[Path]:
"""Capture the front and the back of one card.
@param output_dir Folder to write `screenshot-front.png` and
`screenshot-back.png` into.
@return The two files written, front first.
"""
_make_dpi_aware()
output_dir.mkdir(parents=True, exist_ok=True)
random.seed(SCREENSHOT_SEED)
with tempfile.TemporaryDirectory() as data_dir:
shutil.copy(DEFAULT_DATA_DIR / VOCABULARY_FILE_NAME, data_dir)
root, view = create_application(Path(data_dir), speech_enabled=False)
root.lift()
root.attributes("-topmost", True)
_pump(root, SETTLE_SECONDS)
box = (
view.canvas.winfo_rootx() - 20,
view.canvas.winfo_rooty() - 20,
view.canvas.winfo_rootx() + view.canvas.winfo_width() + 20,
view.canvas.winfo_rooty()
+ view.canvas.winfo_height()
+ view.right_button.winfo_height()
+ 20,
)
front = output_dir / "screenshot-front.png"
back = output_dir / "screenshot-back.png"
ImageGrab.grab(bbox=box).save(front)
_pump(root, FLIP_DELAY_MS / 1000 + SETTLE_SECONDS)
ImageGrab.grab(bbox=box).save(back)
root.destroy()
return [front, back]
def main(argv: Sequence[str] | None = None) -> int:
"""Run the tool.
@param argv Command line arguments, or None for `sys.argv`.
@return The exit code, 0 on success.
"""
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT_DIR)
args = parser.parse_args(argv)
for path in capture(args.output_dir):
sys.stdout.write(f"{path}\n")
return 0
if __name__ == "__main__":
raise SystemExit(main())