diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95b423f --- /dev/null +++ b/.gitignore @@ -0,0 +1,77 @@ +# Local secrets: tokens are for personal use and are never part of the project +.env +.env.* + +# Learner's own progress (created by the program on the first "known" answer) +data/words_to_learn.csv + +# Virtual environments +.venv/ +venv/ +env/ + +# Generated documentation (doxygen Doxyfile) +docs/doxygen/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ + +# Type checkers and linters +.mypy_cache/ +.dmypy.json +dmypy.json +.pyre/ +.pytype/ +.ruff_cache/ +.import_linter_cache/ + +# Jupyter, IDEs and operating systems +.ipynb_checkpoints +.idea/ +.vscode/ +*.swp +.DS_Store +Thumbs.db diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..ac48f2d --- /dev/null +++ b/Doxyfile @@ -0,0 +1,36 @@ +# Doxygen configuration for the flash card program. +# Build: doxygen Doxyfile (output: docs/doxygen/html/index.html) +# Only the settings that differ from the Doxygen defaults are listed. + +PROJECT_NAME = "Flash Card" +PROJECT_BRIEF = "Malay to Danish flash cards (Udemy 100 Days of Code, day 31)" +PROJECT_NUMBER = 0.1.0 +OUTPUT_DIRECTORY = docs/doxygen +CREATE_SUBDIRS = NO + +INPUT = src +FILE_PATTERNS = *.py +RECURSIVE = YES +EXCLUDE_PATTERNS = */__pycache__/* */.venv/* + +# Python +OPTIMIZE_OUTPUT_JAVA = YES +PYTHON_DOCSTRING = NO +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = YES +JAVADOC_AUTOBRIEF = YES +AUTOLINK_SUPPORT = YES +MARKDOWN_SUPPORT = YES + +# Diagrams need Graphviz; leave them off so the build works everywhere. +HAVE_DOT = NO + +# Output +GENERATE_HTML = YES +GENERATE_LATEX = NO +SOURCE_BROWSER = YES +QUIET = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = YES diff --git a/README.md b/README.md index 55440f4..449c5a2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,132 @@ -# 031-flash-card +# 🚀 Flash Card +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*, written for course participants who want to compare solutions and for visitors who browse the repository for ideas. + +## 📚 Table of Contents + +- [Overview](#-overview) +- [Requirements](#-requirements) +- [Setup](#-setup) +- [Run](#-run) +- [Tests](#-tests) +- [License](#-license) +- [Links](#-links) +- [Appendix: Project documents](#-appendix-project-documents) + +## 🧭 Overview + +The program shows a Malay word on a card. After three seconds the card turns over and shows the Danish translation. You press the green tick when you know the word and the red cross when you do not. A known word is removed from the words to learn, and the words still to learn are saved, so the next start continues where you stopped. + +| Part | What it is for | +| --- | --- | +| `src/flash_card/` | The program, in four layers: `domain`, `application`, `infrastructure`, `presentation` | +| `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` | +| `docs/` | Project documents (business case, plan, design, review records) | +| `Doxyfile` | Doxygen configuration for the source code documentation | + +The core program uses only the Python standard library. Spoken Malay words are an optional extra. + +## 📋 Requirements + +| Need | Why | +| --- | --- | +| Python 3.13 or later, with Tkinter | The program and its window (Tkinter is part of the standard Python installers for Windows and macOS; on Debian or Ubuntu install `python3-tk`) | +| `git` | To clone the repository | +| Doxygen (optional) | To build the source code documentation | +| `gTTS` and `playsound==1.2.2` (optional, extra `speech`) | To hear the Malay word; needs a network connection | + +## 🛠️ Setup + +Create a virtual environment in `.venv`, activate it, upgrade pip and install the project. Run the commands in the repository folder. + +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]" +``` + +- Use `python -m pip install -e .` if you only want to run the program; the `dev` extra adds `pytest`, `ruff`, `mypy` and `import-linter`. +- Add the spoken word with `python -m pip install -e ".[speech]"`. +- If PowerShell refuses to run `Activate.ps1`, run `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` once, or use `.\.venv\Scripts\activate.bat` in `cmd`. +- Leave the environment with `deactivate`. Delete the `.venv` folder to start over. + +## ▶️ Run + +```bash +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. + +## 🧪 Tests + +```bash +python -m pytest +``` + +Quality checks used by the project: + +```bash +python -m ruff check src tests +python -m mypy +lint-imports +``` + +The window test in `tests/e2e/` is skipped when no display is available. + +## 📄 License + +GNU Affero General Public License v3.0 or later. See [LICENSE][license]. + +## 🔗 Links + +- [Repository][repository] +- [Documentation][documentation] +- [Issue tracker][issues] + +## 📎 Appendix: Project documents + +The documents a newcomer needs, in reading order. + +| Document | What it tells you | +| --- | --- | +| [AGENTS.md][agents] | The rules AI agents follow in this repository | +| [Business Case][BC-001] | Why the project exists, its scope and success criteria | +| [Stakeholder Analysis][SA-001] | Who is involved; the stakeholder IDs used for owners and reviewers | +| [Project Plan][PP-001] | The gateways and their schedule | +| [Milestones][milestones] | One document per gateway: Go/No-Go criteria and tasks | +| [Artifact registry][registry] | Where each artifact type lives, the next version, the PO language and domain | +| [Domain dictionary][DICT-001] | The PO terms and the IT terms | +| [Traceability matrix][TM-001] | How the artifacts link and when each was last reviewed | +| [Framework README][framework-readme] | The framework this project uses | +| [Framework changelog][framework-changelog] | What changed in each framework release | + +--- + +[license]: ./LICENSE +[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 +[issues]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/issues +[agents]: ./AGENTS.md +[BC-001]: ./docs/business-case.md +[SA-001]: ./docs/stakeholder-analysis.md +[PP-001]: ./docs/project-plan.md +[milestones]: ./docs/milestones/ +[registry]: ./docs/artifact-registry.md +[DICT-001]: ./docs/dictionary.md +[TM-001]: ./docs/sqa/traceability-matrix.md +[framework-readme]: ./framework/README.md +[framework-changelog]: ./framework/CHANGELOG.md diff --git a/data/README.md b/data/README.md new file mode 100644 index 0000000..a06debc --- /dev/null +++ b/data/README.md @@ -0,0 +1,21 @@ +# Data + +| File | Content | In git | +| --- | --- | --- | +| `ms-da.csv` | The word list: 1000 of the most frequent Malay words with a Danish translation | yes | +| `words_to_learn.csv` | The words you have not marked as known; created by the program | no (`.gitignore`) | + +## Format + +- UTF-8 text, one card per line, cells separated by a semicolon (`;`). +- The first line is a header with the two language codes: `ms;da` (Malay, Danish). +- Blank lines are ignored. +- `words_to_learn.csv` has the same format, so it can be used as a word list. + +## Origin + +The Malay words are a frequency list built from film and series subtitles (the *FrequencyWords* lists by the GitHub user `hermitdave`). The Danish column was filled with machine translation in a spreadsheet, as the course suggests. A word can therefore have a translation that does not fit its use; edit the file if you find one. + +## Start again + +Delete `words_to_learn.csv`. The next start uses `ms-da.csv`. diff --git a/data/ms-da.csv b/data/ms-da.csv new file mode 100644 index 0000000..fabde6e --- /dev/null +++ b/data/ms-da.csv @@ -0,0 +1,1001 @@ +ms;da +aku;mig +yang;hvilke +tidak;ingen +kau;du +ini;denne +itu;at +dan;og +di;i +anda;du +akan;vilje +apa;hvad +dia;ham/hende +saya;mig +kita;os +untuk;for +mereka;dem +ada;der er +tahu;vide +dengan;med +bisa;kan +dari;fra +tak;ingen +kamu;du +kami;os +adalah;er +ke;til +ya;ja +orang;person +tapi;men +harus;skal +pergi;gå +baik;god +dalam;dyb +sini;her +seperti;ligesom +hanya;kun +ingin;mangel +sekarang;nu +semua;alle +saja;kun +sudah;allerede +jika;hvis +oh;åh +apakah;hvad +jadi;så +satu;en +jangan;ikke +pada;på +punya;have +lebih;mere +benar;ægte +bukan;ikke +bagaimana;hvordan +lagi;igen +telah;har +lakukan;gør +mungkin;måske +hal;ting +sangat;meget +bahwa;at +pernah;nogensinde +melihat;se +datang;komme +hari;dag +kembali;tilbagevenden +lihat;se +ayo;kom nu +karena;fordi +siapa;WHO +atau;eller +sesuatu;noget +banyak;mange +tentang;om +dapat;kan +kasih;kærlighed +terima;acceptere +yeah;ja +menjadi;at være +anak;barn +seorang;en +ia;det +kalian;jer +juga;også +mau;mangel +oke;okay +keluar;ud +begitu;så +mana;hvor +bagus;god +hei;hej +melakukan;gør +sana;der +terjadi;skete +yg;hvilke +sebuah;-en +lain;andre +malam;nat +waktu;tid +membuat;lave +tahun;år +beberapa;nogle +saat;øjeblik +mengapa;hvorfor +tempat;placere +memiliki;egen +besar;stor +mati;død +maaf;undskyld +baru;ny +masih;stadig +katakan;sige +ketika;når +sendiri;mig selv +kenapa;hvorfor +sama;samme +pikir;tænke +sampai;indtil +bilang;sige +hidup;levende +mengatakan;sige +baiklah;okay +sedang;medium +tunggu;vente +kalau;hvis +dua;to +sekali;engang +tuhan;Gud +lalu;så +salah;forkert +perlu;nødvendig +mendapatkan;få +rumah;hus +ayah;far +cepat;hurtig +masuk;indtast +selamat;sikker +mari;komme +dengar;lytte +suka;ligesom +sedikit;lidt +semuanya;alt +jalan;vej +tentu;selvfølgelig +disini;her +padaku;til mig +atas;top +tinggal;ophold +masalah;problem +boleh;kan +cukup;nok +setiap;hver +selalu;altid +berada;være +kecil;lille +sebagai;som +makan;spise +berpikir;tænke +oleh;ved +mengambil;tage +yah;godt +belum;ikke endnu +kepada;til +percaya;tro +the;de +seseorang;nogen +teman;ven +bahkan;endog +sayang;kære +dimana;hvor +ok;okay +pertama;først +menemukan;finde +bicara;tale +melakukannya;gør det +lama;gammel +mengerti;forstå +bekerja;arbejde +dunia;verden +hey;hej +diri;selv +terlalu;også +kali;gange +kemudian;så +berhenti;stop +luar;uden for +pasti;definitivt +tolong;behage +cara;vej +kan;højre +mulai;starte +setelah;efter +wanita;kvinde +ku;mine +ayolah;kom nu +it;det +bertemu;møde +senang;lykkelig +tetap;fast +selama;under +pak;hr +tetapi;men +ibu;mor +gadis;pige +yakin;overbevist +sebelum;før +i;jeg +merasa;føle +membawa;bringe +tuan;hr +bersama;sammen +pria;mand +berbicara;tale +jam;ur +pun;også +uang;penge +ingat;huske +membunuh;dræbe +biarkan;lade +siap;parat +a;-en +mencoba;prøve +tidur;sove +buruk;dårlig +padamu;til dig +berapa;hvor meget +nya;hans/hendes +mencari;leder efter +tanpa;uden +tiga;tre +sial;lort +nama;navn +bawah;bund +sialan;forbandet +terus;fortsætte +apapun;hvad som helst +hati;hjerte +maksudku;Jeg mener +halo;hej +gila;skør +sebenarnya;faktisk +cinta;kærlighed +mobil;bil +rasa;smag +bagi;for +takut;bange +depan;front +sakit;syg +mendengar;lytte +pulang;gå hjem +membantu;hjælp +terakhir;sidst +suatu;-en +manusia;human +air;vand +tangan;hånd +ambil;tage +kota;by +you;du +polisi;politi +butuh;behov +segera;straks +berikan;give +nak;mangel +mr;Hr. +paling;mest +menunggu;venter +bagian;del +pagi;morgen +para;de +hebat;stor +tua;gammel +jauh;langt væk +bawa;bringe +tepat;nøjagtig +bodoh;dum +sungguh;virkelig +biasa;normal +bergerak;flytte +itulah;det er det +dapatkan;få +kurasa;Jeg tror +l;l +seharusnya;burde være +masa;tid +meninggalkan;forlade +no;ingen. +hai;hej +dulu;først +duduk;sidde +sebentar;et øjeblik +memberikan;give +berkata;sagde +kata;ord +minta;spørge +seluruh;overalt +kehilangan;tab +laki;mand +awak;du +buat;lave +denganmu;med dig +bulan;måne +padanya;på ham +menit;minut +pekerjaan;job +sekitar;omkring +kepala;hoved +berjalan;gåture +cuma;lige +pintu;dør +memang;virkelig +tenang;berolige +disana;der +nanti;senere +bermain;spil +jatuh;falde +terbaik;bedst +terlihat;synlig +sekolah;skole +aneh;mærkelig +sir;hr +penting;vigtig +maafkan;tilgive +keluarga;familie +mata;øjne +berhasil;vellykket +besok;i morgen +hampir;næsten +takkan;vil ikke +coba;prøve +berarti;betyder +diam;vær stille +minum;drikke +turun;ned +melihatnya;se det +dirimu;dig selv +bisakah;kan du +inginkan;mangel +selesai;færdig +daripada;fra +peduli;omsorg +indah;smuk +dekat;tæt +memberi;give +buku;bog +hentikan;stop +ikut;følge +senjata;våben +man;mand +kaki;fod +menarik;interessant +mu;dine +minggu;uge +secara;på en måde +nah;godt +berdua;begge +cantik;smuk +anjing;hund +sepertinya;det ser ud til +ah;åh +kehidupan;liv +satunya;den eneste +mudah;let +amerika;Amerika +khawatir;bekymret +mendapat;fik +silakan;behage +memberitahu;fortælle +jelas;klar +akhirnya;endelig +tadi;tidligere +menggunakan;ved hjælp af +well;godt +belakang;tilbage +okay;okay +bangun;vågn op +lima;fem +melalui;ved +perjalanan;rejse +cari;søge +makanan;mad +sebelumnya;tidligere +sejak;siden +keras;hård +tiba;ankomme +kesempatan;lejlighed +untukmu;til dig +berakhir;ender +tinggi;høj +bumi;jord +dilakukan;færdig +aman;fredelig +naik;op +john;Johannes +perang;krig +sulit;fortrolig +kosong;tom +beri;give +berharap;håb +kapan;når +kawan;ven +com;com +api;brand +menyenangkan;behagelig +kedua;anden +menunjukkan;vise +suara;stemme +maka;så +maksudmu;du mener +ruang;plads +cerita;historie +kemari;her +kamar;værelse +kapal;skib +mempunyai;have +kuat;stærk +akhir;ende +tanah;jord +serius;alvorlig +beritahu;fortælle +bertanya;spørge +si;de +raja;konge +meminta;spørge +berubah;forandring +lari;løbe +bunuh;dræbe +membiarkan;lade +belajar;studere +panas;varm +eh;øh +muda;ung +tn;Hr. +bayi;baby +ayahku;min far +berbeda;anderledes +ni;denne +kapten;kaptajn +pesawat;fly +negara;land +sepanjang;hele vejen igennem +dokter;læge +hilang;tabt +hi;hej +tampak;synlig +berdiri;stå +ide;ide +antara;mellem +tim;hold +pertanyaan;spørgsmål +lupa;glemme +penuh;fuld +kerja;arbejde +lainnya;andre +wow;wow +dr;læge +lucu;sjov +alasan;årsag +telepon;telefon +dengarkan;lytte +bukankah;er det ikke +empat;fire +dengannya;med det +kabar;nyheder +menikah;gift +perempuan;kvinde +bantuan;hjælp +bajingan;idiot +terbang;flyve +meninggal;døde +buka;åben +ayahmu;din far +dah;allerede +membutuhkan;behov +darah;blod +kecuali;undtagen +bila;når +biar;lade +uh;åh +of;af +to;til +pikirkan;tænke +film;film +nomor;antal +menang;vinde +bintang;stjerne +kira;tænke +laut;hav +omong;tale +mesin;maskine +kekuatan;styrke +kematian;død +silahkan;behage +sehingga;indtil +kemana;hvor +benarkah;er det sandt +menembak;skud +dingin;kold +sementara;midlertidig +menyesal;beklagelse +mendengarkan;lytte +utk;for +jack;donkraft +me;mig +marah;vred +tu;at +tinggalkan;forlade +pergilah;gå væk +terlambat;sent +kereta;bil +nyata;ægte +bahagia;lykkelig +saudara;bror +permisi;undskyld mig +menyelamatkan;spare +sempurna;perfektionere +lepaskan;frigøre +rencana;plan +and;og +membayar;betale +harap;håb +demi;af hensyn til +mendapatkannya;få det +segalanya;alt +bicarakan;tale om +berikutnya;næste +barang;gods +membaca;læsning +brengsek;idiot +up;op +berusaha;prøve +dirinya;sig selv +semacam;slags +matahari;sol +kanan;højre +permainan;spil +setengah;halv +go;gå +bahasa;sprog +melawan;kæmpe +denganku;med mig +langsung;direkte +jenis;type +menuju;mod +rasanya;det føles som +diriku;mig selv +musim;sæson +pesta;parti +tutup;tæt +menulis;skrive +menerima;acceptere +terhadap;mod +sendirian;alene +pilihan;valg +agak;helt +tahan;holde +tersebut;de +astaga;åh gud +dapatkah;kan du +on;på +tanda;tegn +new;ny +bung;bror +membeli;købe +jumpa;vi ses +agar;så at +pikiran;sind +la;Det er det. +janji;løfte +waktunya;tiden +muncul;fremstå +tampaknya;det ser ud til +membuatku;få mig +guru;lærer +surat;bogstav +enam;seks +pasukan;hold +lewat;sent +mencintaimu;elsker dig +segala;alt +rahasia;hemmelighed +kupikir;Jeg tror +menurutmu;ifølge dig +namun;imidlertid +namanya;hans navn +mundur;tilbagetog +menjaga;tage sig af +membuatnya;gør det +semakin;i stigende grad +mimpi;drøm +bola;bold +udara;luft +gambar;billede +awal;tidlig +kelas;klasse +anakku;mit barn +memilih;vælge +fucking;kneppe +kumohon;behage +ikan;fisk +menelepon;opkald +bercanda;spøg +kesalahan;fejl +perusahaan;selskab +pesan;besked +ibumu;din mor +bisnis;forretning +ltu;At +whoa;hold da op +kenal;vide +bantu;hjælp +fuck;kneppe +lihatlah;se +planet;planet +artinya;mening +temukan;finde +bertahan;overleve +sam;Sam +mengetahui;at vide +my;mine +dasar;politik +huh;hva' +batu;sten +merah;rød +benda;ting +benci;had +memakai;slid +beruntung;heldig +manis;sød +mengenai;om +sisi;side +tembak;skud +burung;fugl +nona;dame +tengah;midten +juta;million +kulakukan;Jeg gjorde det. +panjang;lang +ulang;gentage +untukku;for mig +kunci;nøgle +jahat;ond +perintah;bestille +alam;natur +paman;onkel +cahaya;lys +maksud;formål +ibuku;min mor +tertawa;grine +pindah;flytte +menangis;skrig +mulia;ædel +kiri;venstre +hidupku;mit liv +penjara;fængsel +harry;Harry +lt;lt +menangkap;fangst +menghancurkan;ødelægge +putih;hvid +tanya;spørge +setuju;enig +in;denne +siang;middag +mengerikan;frygtelig +lagu;sang +arah;retning +macam;slags +inggris;Engelsk +hukum;lov +es;is +istri;kone +kantor;kontor +berita;nyheder +dolar;dollar +kadang;undertiden +berat;vægt +presiden;formand +frank;åben +wajah;ansigt +musik;musik +betul;højre +menghabiskan;bruge +kurang;mindre +tau;du ved +kasus;sag +hmm;hmm +jawab;svar +betapa;hvor meget +makhluk;væsen +um;øh +memutuskan;beslutte +hadiah;gave +pembunuh;morder +mencuri;stjæle +terbuka;åben +neraka;helvede +sebaiknya;helst +mencintai;kærlighed +bebas;gratis +lantai;etage +panggil;opkald +detik;sekunder +hitam;sort +mengubah;forandring +selamanya;for altid +tubuh;legeme +mengikuti;følge +semoga;forhåbentlig +berani;modig +binatang;dyr +hak;højre +mandi;bad +menemukannya;fandt det +panggilan;opkald +membuka;åben +sistem;system +tugas;opgave +bu;mor +memikirkan;tænke over +that;at +sering;ofte +setidaknya;i det mindste +hubungan;forhold +adakah;er det +bergabung;deltage +pakaian;tøj +iya;ja +is;is +disebut;kaldt +kuda;hest +putri;prinsesse +membunuhnya;dræb ham +york;York +membawanya;bring det +berbahaya;farlig +main;spil +menonton;ur +melewati;forbi +off;slukket +inilah;det er det +lupakan;glemme +meskipun;skønt +keren;afkøle +pohon;træ +digunakan;brugt +terluka;skade +memanggil;opkald +bunga;blomst +membuatmu;få dig +melihatmu;vi ses +lelaki;mand +kukatakan;Jeg siger +musuh;fjende +perhatian;opmærksomhed +bukti;bevis +pakai;slid +mencapai;opnå +serangan;angreb +hello;hej +s;s +prajurit;soldat +bye;farvel +lni;Dette er +utama;hoved +by;ved +merasakan;føle +berjanji;løfte +sebelah;ved siden af +tentara;hæren +berbohong;ligge +gunakan;bruge +merupakan;er +agen;agent +toko;butik +soal;spørgsmål +mengirim;sende +dimulai;startede +ha;ja +bau;lugt +michael;Michael +memukul;hit +nyonya;elskerinde +bank;bank +kelihatan;synlig +allah;Gud +sebabnya;årsagen +obat;medicin +utara;nord +siapapun;nogen +menurut;ifølge +khusus;særlig +out;ud +daging;kød +perasaan;følelse +pembunuhan;mord +melindungi;beskytte +selatan;syd +kartu;kort +keberatan;indvending +menempatkan;placere +asing;udenlandsk +menyerah;give op +yesus;Jesus +sejarah;historie +pribadi;personlig +sibuk;optaget +langit;himmel +temanku;min ven +mengalami;erfaring +bos;chef +menghentikan;stop +kopi;kaffe +kesana;der +harusnya;burde være +tumbuh;dyrke +mama;mor +jaga;pas på dig selv +pistol;pistol +rambut;hår +menyukai;ligesom +mahu;mangel +pintar;smart +hutan;skov +tujuh;syv +balik;tilbage +kesini;her +titik;punkt +sebagian;delvist +gagal;mislykkedes +menyerang;angreb +mil;mil +meja;tabel +pusat;centrum +ahli;ekspert +selain;udover +mampu;i stand til +kebenaran;sandhed +maju;fremrykning +bersalah;skyldig +memulai;starte +lapar;sulten +foto;foto +langkah;trin +pernikahan;bryllup +dewa;Gud +istirahat;pause +mulut;mund +kulit;hud +informasi;information +sang;de +sampah;affald +kemarin;i går +sepuluh;ti +kotak;boks +keadaan;situation +tdk;ikke +miss;gå glip af +pukul;hit +menutup;tæt +namaku;mit navn +posisi;position +mengganggu;genere +menjual;sælge +nggak;ingen +email;e-mail +milik;tilhører +lubang;hul +kamera;kamera +tunjukkan;vise +lahir;født +akal;årsag +hah;hva' +menyukainya;kan lide det +kotoran;smuds +sih;hej +angin;vind +enak;lækker +sungai;flod +pacar;kæreste +dariku;fra mig +nenek;bedstemor +blogspot;blogspot +george;Georg +melarikan;løbe væk +jujur;ærlig +pahlawan;helt +ruangan;værelse +hotel;hotel +hingga;indtil +tarik;træk +m;m +miliki;have +peter;Peter +meletakkan;sætte +tertarik;interesseret +ikuti;følge +gelap;mørk +biasanya;som regel +dibuat;lavet +jerman;Tyskland +cocok;egnet +ditemukan;fundet +tv;television +tembakan;skud +guys;fyre +terbesar;største +menyelesaikan;slutte +tujuan;formål +sub;underdel +menyadari;realisere +daerah;distrikt +begini;sådan her +d;d +masalahnya;problemet +we;vi +love;kærlighed +acara;tilfælde +memeriksa;check +diluar;uden for +terimakasih;tak +saling;hinanden +kubilang;Jeg sagde +kecelakaan;ulykke +ceritakan;fortæl mig +garis;linje +berteriak;råbe +periksa;check +catatan;note +pantai;strand +keamanan;fred +kerajaan;regering +bahan;materiale +david;David +seks;køn +sepatu;sko +puluh;ti +kalah;tabe +max;maks +otak;hjerne +jenderal;generel +memotong;skære +gigi;tænder +emas;guld +mabuk;beruset +perubahan;forandring +bermaksud;betyder +lampu;lampe +e;e +memahami;forstå +saatnya;det er tid +tidakkah;er det ikke +hujan;regn +natal;jul +master;mestre +sejauh;så vidt som +misi;mission +bukanlah;er ikke +seberapa;hvor meget +don;don +menghilang;forsvinde +ayam;kylling +hewan;dyr +bom;bombe +kakak;søster +ben;Ben +menjelaskan;forklare +bentuk;form +kena;hit +membunuhmu;dræbe dig +kucing;kat +membangun;bygge +harapan;håb +bangga;stolt +bersumpah;sværge +kukira;Jeg tror +suami;mand +kemungkinan;mulighed +kejahatan;forbrydelse +berasal;kommer fra +dewasa;voksen +butuhkan;behov +what;hvad +laporan;rapport +pelacur;prostitueret +pemerintah;regering +tanganmu;din hånd +sersan;sergent +bernama;navngivet +didalam;indenfor +situ;der +penjaga;vagt +bahawa;at +kaya;rig +alat;værktøj +memerlukan;behov +bahaya;fare +bersih;ren +mengalahkan;nederlag +keparat;idiot +pertemuan;møde diff --git a/docs/artifact-registry.md b/docs/artifact-registry.md index 8587475..1b72989 100644 --- a/docs/artifact-registry.md +++ b/docs/artifact-registry.md @@ -25,7 +25,7 @@ document of a type. `Primary File` may contain a glob (e.g. | DCD | Design Class Diagram | docs/dcd.md | 002 | | DICT | Domain Dictionary | docs/dictionary.md | 002 | | ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0004 | -| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 023 | +| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 024 | | TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 | ## Languages diff --git a/docs/sqa/reviews/rc-023-py-mil-001.md b/docs/sqa/reviews/rc-023-py-mil-001.md new file mode 100644 index 0000000..0d4d22a --- /dev/null +++ b/docs/sqa/reviews/rc-023-py-mil-001.md @@ -0,0 +1,64 @@ +# RC-023: Review of PY-MIL-001 + +## Metadata +| Key | Value | +| --- | --- | +| ID | RC-023 | +| CrossReference | [PY-MIL-001], [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 | [cc24b7d] | + +--- + +## Artifact Under Review + +- Instance reviewed: [PY-MIL-001] +- 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 | Modules, functions and constants use snake_case and UPPER_SNAKE; 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 (`FLIP_DELAY_MS`, `DEFAULT_DATA_DIR`); no abbreviations. | +| 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 with no suppressions. | +| 4 | Every function and method signature is type-annotated, including `-> None` | Pass | Every function has annotated parameters and return type; `mypy --strict` passes on 9 files. | +| 5 | No bare `except:`, no swallowed exceptions; specific exceptions are raised and the cause is kept (`raise ... from`) | Pass | No exception handling exists yet; no bare except. | +| 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 | N-A | No file or connection is opened yet. | +| 8 | Public modules, classes and functions have docstrings that say what, not how | Pass | Every module and function has a Doxygen docstring with `@file`/`@brief`/`@param`/`@return`; `doxygen Doxyfile` ends with no warning. | +| 9 | Logging uses `logging`, not `print`; no secrets or personal data in log output | Pass | No `print`; no logging needed yet. | +| 10 | Classes and operations trace to the Design Class Diagram they implement; deviations are recorded | Pass | `app.main` and `app.run` are the `app.main` of DCD-001; constants have no design class. | +| 11 | Tests exist for new behaviour, are named for the behaviour, and do not depend on order or the network | Pass | `tests/unit/test_constants.py` and `test_app.py` (7 tests) name the behaviour and use no network. | +| 12 | Type checker runs in strict mode without errors; `Any` is justified in a comment | Pass | `mypy --strict` reports no issue; no `Any` is used. | +| 13 | Dependencies are declared and pinned in the project's dependency file, none unused | Pass | `dependencies` is empty; extras are declared in `pyproject.toml` with version ranges, and `playsound` is pinned. | +| 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 | Every constant is defined once in `constants.py`. | +| 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 the contract `Layers point inward` kept. | +| 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 | Modules are small and single-purpose; nothing to extend yet. | +| 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 | `domain`, `application`, `infrastructure`, `presentation` packages exist under `src/flash_card/`, and `tests/unit`, `integration`, `e2e` exist. | +| 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 are English lower snake_case with no space. | + +## Overall Verdict + +Go — every Mandatory criterion passes. Reviewed by AI as stand-in for S01; not independent of the author. + +Milestone MIL-001 Go criteria: 1 `pip install -e ".[dev]"` succeeded in a fresh `.venv`; 2 `git check-ignore` reports `.venv/`, `.env`, `data/words_to_learn.csv`, `docs/doxygen/`; 3 packages and test folders exist; 4 `doxygen Doxyfile` exit 0 with no warning; 5 `check-readme.sh` exit 0; 6 `pytest` 7 passed. All six Go. + +## Action Items + +| Action | Owner | Due | +| --- | --- | --- | +| none | - | - | + +--- + +[PY-MIL-001]: ../../../src/flash_card +[QC-PY-001]: ../../../framework/qc/code/qc-programming-python.md +[cc24b7d]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/commit/cc24b7d890dbef41c303ad14aea4119cd505c51d diff --git a/docs/sqa/traceability-matrix.md b/docs/sqa/traceability-matrix.md index 385bcd3..a71678e 100644 --- a/docs/sqa/traceability-matrix.md +++ b/docs/sqa/traceability-matrix.md @@ -45,6 +45,7 @@ updated whenever an artifact instance is created or reviewed. | MIL-005 | Milestone / Gateway | en | it | BC-001, PP-001 | tasks of MIL-005 | [RC-020] | | MIL-006 | Milestone / Gateway | en | it | BC-001, PP-001 | tasks of MIL-006 | [RC-021] | | MIL-007 | Milestone / Gateway | en | it | BC-001, PP-001 | tasks of MIL-007 | [RC-022] | +| PY-MIL-001 | Python Source Code | - | - | DCD-001, ADR-0002 | - | [RC-023] | ## Coverage Notes @@ -76,6 +77,7 @@ updated whenever an artifact instance is created or reviewed. [MIL-005]: ../milestones/mil-005-step4-save-progress.md [MIL-006]: ../milestones/mil-006-malay-speech.md [MIL-007]: ../milestones/mil-007-documentation-and-publication.md +[PY-MIL-001]: ../../src/flash_card [RC-001]: ./reviews/rc-001-business-case.md [RC-002]: ./reviews/rc-002-stakeholder-analysis.md [RC-003]: ./reviews/rc-003-project-plan.md @@ -98,4 +100,5 @@ updated whenever an artifact instance is created or reviewed. [RC-020]: ./reviews/rc-020-mil-005.md [RC-021]: ./reviews/rc-021-mil-006.md [RC-022]: ./reviews/rc-022-mil-007.md +[RC-023]: ./reviews/rc-023-py-mil-001.md [c3fbcbc]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/031-flash-card/commit/c3fbcbcfaf799e0ae34aed7e14d3bc8a62f03db3 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..711827d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,75 @@ +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[project] +name = "flash-card" +version = "0.1.0" +description = "Flash card program for learning Malay words with Danish translations (Udemy 100 Days of Code, day 31)" +readme = "README.md" +requires-python = ">=3.13" +license = "AGPL-3.0-or-later" +license-files = ["LICENSE"] +authors = [{ name = "Jens Tirsvad Nielsen" }] +keywords = ["flashcards", "tkinter", "language-learning", "malay", "danish", "udemy"] +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.13", + "Environment :: X11 Applications", + "Topic :: Education", +] +# The core program uses the Python standard library only (ADR-0001). +dependencies = [] + +[project.optional-dependencies] +# Spoken Malay word (text to speech). Optional; the program works without it. +speech = ["gTTS>=2.5", "playsound==1.2.2"] +dev = ["pytest>=8", "ruff>=0.6", "mypy>=1.11", "import-linter>=2.0"] + +[project.scripts] +flash-card = "flash_card.presentation.app:run" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-data] +flash_card = ["presentation/assets/*.png"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +pythonpath = ["src"] +addopts = "-ra" + +[tool.ruff] +line-length = 88 +target-version = "py313" +src = ["src", "tests"] + +[tool.ruff.lint] +select = ["E", "F", "I", "UP", "B", "SIM", "N", "D"] +ignore = ["D203", "D213"] + +[tool.ruff.lint.per-file-ignores] +"tests/**" = ["D"] + +[tool.ruff.lint.pydocstyle] +convention = "pep257" + +[tool.mypy] +python_version = "3.13" +strict = true +mypy_path = ["src"] +packages = ["flash_card"] + +[tool.importlinter] +root_package = "flash_card" + +[[tool.importlinter.contracts]] +name = "Layers point inward" +type = "layers" +layers = [ + "flash_card.presentation", + "flash_card.infrastructure", + "flash_card.application", + "flash_card.domain", +] diff --git a/src/flash_card/__init__.py b/src/flash_card/__init__.py new file mode 100644 index 0000000..810fc07 --- /dev/null +++ b/src/flash_card/__init__.py @@ -0,0 +1,7 @@ +"""Flash card program that teaches Malay words with Danish translations. + +@file __init__.py +@brief Package root of the flash card program. +""" + +__version__ = "0.1.0" diff --git a/src/flash_card/__main__.py b/src/flash_card/__main__.py new file mode 100644 index 0000000..a9db895 --- /dev/null +++ b/src/flash_card/__main__.py @@ -0,0 +1,10 @@ +"""Run the flash card program with ``python -m flash_card``. + +@file __main__.py +@brief Module entry point that calls the presentation layer. +""" + +from flash_card.presentation.app import run + +if __name__ == "__main__": + run() diff --git a/src/flash_card/application/__init__.py b/src/flash_card/application/__init__.py new file mode 100644 index 0000000..d26caea --- /dev/null +++ b/src/flash_card/application/__init__.py @@ -0,0 +1,5 @@ +"""Application layer: use cases and ports. + +@file __init__.py +@brief Holds use cases and the ports they need; may import only the domain layer. +""" diff --git a/src/flash_card/constants.py b/src/flash_card/constants.py new file mode 100644 index 0000000..9ec03b2 --- /dev/null +++ b/src/flash_card/constants.py @@ -0,0 +1,74 @@ +"""All constants of the flash card program, defined once. + +@file constants.py +@brief Window, card, font, language, file and asset constants. +Imports only the standard library. +""" + +from collections.abc import Mapping +from pathlib import Path +from typing import Final + +## @brief Window background colour. +BACKGROUND_COLOR: Final[str] = "#B1DDC6" +## @brief Window title. +WINDOW_TITLE: Final[str] = "Flashy" +## @brief Padding around the card in pixels. +WINDOW_PADDING: Final[int] = 50 + +## @brief Card width in pixels. +CARD_WIDTH: Final[int] = 800 +## @brief Card height in pixels. +CARD_HEIGHT: Final[int] = 526 +## @brief Horizontal centre of the card in pixels. +CARD_CENTER_X: Final[int] = 400 +## @brief Vertical position of the language text in pixels. +LANGUAGE_TEXT_Y: Final[int] = 150 +## @brief Vertical position of the word text in pixels. +WORD_TEXT_Y: Final[int] = 263 +## @brief Delay before the card flips, in milliseconds. +FLIP_DELAY_MS: Final[int] = 3000 + +## @brief Font of the language text: family, size, style. +LANGUAGE_FONT: Final[tuple[str, int, str]] = ("Arial", 40, "italic") +## @brief Font of the word text: family, size, style. +WORD_FONT: Final[tuple[str, int, str]] = ("Arial", 60, "bold") +## @brief Text colour on the card front. +FRONT_TEXT_COLOR: Final[str] = "black" +## @brief Text colour on the card back. +BACK_TEXT_COLOR: Final[str] = "white" + +## @brief Display names by language code; the caller shows unknown codes in upper case. +LANGUAGE_NAMES: Final[Mapping[str, str]] = {"ms": "Malay", "da": "Danish"} +## @brief Message shown when no word is left to learn. +FINISHED_MESSAGE: Final[str] = "Nothing left to learn" +## @brief Language name shown before the first card. +PLACEHOLDER_LANGUAGE_NAME: Final[str] = "Malay" +## @brief Word shown before the first card. +PLACEHOLDER_WORD: Final[str] = "kata" + +## @brief Delimiter of the CSV files. +CSV_DELIMITER: Final[str] = ";" +## @brief Encoding of the CSV files. +CSV_ENCODING: Final[str] = "utf-8" +## @brief File name of the vocabulary. +VOCABULARY_FILE_NAME: Final[str] = "ms-da.csv" +## @brief File name of the words still to learn. +PROGRESS_FILE_NAME: Final[str] = "words_to_learn.csv" +## @brief Language code used for speech. +SPEECH_LANGUAGE_CODE: Final[str] = "ms" + +## @brief Root folder of the project. +PROJECT_ROOT: Final[Path] = Path(__file__).resolve().parents[2] +## @brief Default folder of the data files. +DEFAULT_DATA_DIR: Final[Path] = PROJECT_ROOT / "data" +## @brief Folder of the image assets. +ASSETS_DIR: Final[Path] = Path(__file__).resolve().parent / "presentation" / "assets" +## @brief File name of the card front image. +CARD_FRONT_IMAGE_NAME: Final[str] = "card_front.png" +## @brief File name of the card back image. +CARD_BACK_IMAGE_NAME: Final[str] = "card_back.png" +## @brief File name of the right button image. +RIGHT_IMAGE_NAME: Final[str] = "right.png" +## @brief File name of the wrong button image. +WRONG_IMAGE_NAME: Final[str] = "wrong.png" diff --git a/src/flash_card/domain/__init__.py b/src/flash_card/domain/__init__.py new file mode 100644 index 0000000..51919c5 --- /dev/null +++ b/src/flash_card/domain/__init__.py @@ -0,0 +1,6 @@ +"""Domain layer: entities and rules of the flash card program. + +@file __init__.py +@brief Holds entities, value objects and rules. +May import nothing outside the standard library. +""" diff --git a/src/flash_card/infrastructure/__init__.py b/src/flash_card/infrastructure/__init__.py new file mode 100644 index 0000000..95088f0 --- /dev/null +++ b/src/flash_card/infrastructure/__init__.py @@ -0,0 +1,5 @@ +"""Infrastructure layer: adapters for files, speech and other outside systems. + +@file __init__.py +@brief Holds implementations of ports; may import the application and domain layers. +""" diff --git a/src/flash_card/presentation/__init__.py b/src/flash_card/presentation/__init__.py new file mode 100644 index 0000000..2300f54 --- /dev/null +++ b/src/flash_card/presentation/__init__.py @@ -0,0 +1,5 @@ +"""Presentation layer: views, view models and the entry point. + +@file __init__.py +@brief Holds the user interface and the composition root; may import every inner layer. +""" diff --git a/src/flash_card/presentation/app.py b/src/flash_card/presentation/app.py new file mode 100644 index 0000000..6087310 --- /dev/null +++ b/src/flash_card/presentation/app.py @@ -0,0 +1,28 @@ +"""Entry point of the flash card program. + +@file app.py +@brief Starts the program; the window arrives in MIL-002. +""" + +from collections.abc import Sequence +from typing import NoReturn + + +def main(argv: Sequence[str] | None = None) -> int: + """Run the program and return its exit code. + + The window arrives in MIL-002; for now nothing happens. + + @param argv Command line arguments, or None for none. + @return The exit code, 0 on success. + """ + return 0 + + +def run() -> NoReturn: + """Run the program and exit the interpreter with its exit code. + + @return Never returns; the function always raises SystemExit. + @throws SystemExit Always, with the exit code of main. + """ + raise SystemExit(main()) diff --git a/src/flash_card/presentation/assets/.gitkeep b/src/flash_card/presentation/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/e2e/.gitkeep b/tests/e2e/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/integration/.gitkeep b/tests/integration/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/test_app.py b/tests/unit/test_app.py new file mode 100644 index 0000000..12c5fd1 --- /dev/null +++ b/tests/unit/test_app.py @@ -0,0 +1,11 @@ +"""Tests of the entry point. + +@file test_app.py +@brief Checks main of flash_card.presentation.app. +""" + +from flash_card.presentation.app import main + + +def test_main_returns_zero_without_arguments() -> None: + assert main([]) == 0 diff --git a/tests/unit/test_constants.py b/tests/unit/test_constants.py new file mode 100644 index 0000000..89fba6c --- /dev/null +++ b/tests/unit/test_constants.py @@ -0,0 +1,33 @@ +"""Tests of the constants module. + +@file test_constants.py +@brief Checks the key values of flash_card.constants. +""" + +from flash_card import constants + + +def test_flip_delay_ms_is_three_seconds() -> None: + assert constants.FLIP_DELAY_MS == 3000 + + +def test_background_color_is_green() -> None: + assert constants.BACKGROUND_COLOR == "#B1DDC6" + + +def test_default_data_dir_ends_with_data() -> None: + assert constants.DEFAULT_DATA_DIR.name == "data" + + +def test_file_names_are_csv() -> None: + assert constants.VOCABULARY_FILE_NAME.endswith(".csv") + assert constants.PROGRESS_FILE_NAME.endswith(".csv") + + +def test_language_names_cover_ms_and_da() -> None: + assert "ms" in constants.LANGUAGE_NAMES + assert "da" in constants.LANGUAGE_NAMES + + +def test_project_root_contains_pyproject() -> None: + assert (constants.PROJECT_ROOT / "pyproject.toml").is_file()