From d932c7fa4fe9087bd4878fc019ac02c1532d519c Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Tue, 13 Oct 2026 16:45:01 +0800 Subject: [PATCH 1/3] Add README, screenshots and wiki in English, Danish and Bahasa Malaysia Task: MIL-003#1 Task: MIL-003#2 Refs #14 Refs #15 Refs #16 Refs #17 Refs #18 --- Doxyfile | 6 +- README.md | 206 +++++++++++++++++++++++++++++++++- docs/images/retry-invalid.png | Bin 0 -> 24391 bytes docs/images/spell-word.png | Bin 0 -> 16937 bytes src/nato_alphabet/__init__.py | 10 +- wiki/Development-da.md | 43 +++++++ wiki/Development-ms.md | 43 +++++++ wiki/Development.md | 43 +++++++ wiki/Home-da.md | 23 ++++ wiki/Home-ms.md | 23 ++++ wiki/Home.md | 23 ++++ wiki/Install-da.md | 57 ++++++++++ wiki/Install-ms.md | 57 ++++++++++ wiki/Install.md | 57 ++++++++++ wiki/Usage-da.md | 31 +++++ wiki/Usage-ms.md | 31 +++++ wiki/Usage.md | 31 +++++ wiki/_Sidebar.md | 24 ++++ 18 files changed, 703 insertions(+), 5 deletions(-) create mode 100644 docs/images/retry-invalid.png create mode 100644 docs/images/spell-word.png create mode 100644 wiki/Development-da.md create mode 100644 wiki/Development-ms.md create mode 100644 wiki/Development.md create mode 100644 wiki/Home-da.md create mode 100644 wiki/Home-ms.md create mode 100644 wiki/Home.md create mode 100644 wiki/Install-da.md create mode 100644 wiki/Install-ms.md create mode 100644 wiki/Install.md create mode 100644 wiki/Usage-da.md create mode 100644 wiki/Usage-ms.md create mode 100644 wiki/Usage.md create mode 100644 wiki/_Sidebar.md diff --git a/Doxyfile b/Doxyfile index 4248036..10fa16b 100644 --- a/Doxyfile +++ b/Doxyfile @@ -130,10 +130,10 @@ WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- -INPUT = src README.md +INPUT = src INPUT_ENCODING = UTF-8 INPUT_FILE_ENCODING = -FILE_PATTERNS = *.py *.md +FILE_PATTERNS = *.py RECURSIVE = YES EXCLUDE = EXCLUDE_SYMLINKS = NO @@ -147,7 +147,7 @@ INPUT_FILTER = FILTER_PATTERNS = FILTER_SOURCE_FILES = NO FILTER_SOURCE_PATTERNS = -USE_MDFILE_AS_MAINPAGE = README.md +USE_MDFILE_AS_MAINPAGE = IMPLICIT_DIR_DOCS = YES FORTRAN_COMMENT_AFTER = 72 #--------------------------------------------------------------------------- diff --git a/README.md b/README.md index 7c00f4e..58254f6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,206 @@ -# 026-nato-alphabet +# 🚀 NATO alphabet +A console tool that spells a word with the NATO phonetic alphabet, the Day 26 assignment of Udemy's "100 Days of Code: The Complete Python Pro Bootcamp". It is written for fellow course participants and GitHub visitors who want a readable, tested, runnable solution that uses list and dictionary comprehensions and has no runtime dependencies. + +![The tool spelling the word Hello][screenshot-spell] + +## 📚 Table of Contents + +- [Overview](#-overview) +- [Requirements](#-requirements) +- [Setup](#-setup) +- [Run](#-run) +- [Tests](#-tests) +- [Continuous integration](#-continuous-integration) +- [Build the source documentation](#-build-the-source-documentation) +- [Project layout](#-project-layout) +- [License](#-license) +- [Links](#-links) +- [Appendix: Project documents](#-appendix-project-documents) + +## 🧭 Overview + +You type a word, the program prints the NATO code word of each letter. A character that is not a letter makes it ask again. + +```text +Enter a word: Hello +Phonetic code words: +Hotel +Echo +Lima +Lima +Oscar +``` + +How it works, in the course's terms: + +- `load_nato_dict` reads the supplied CSV file and builds the letter-to-code-word dictionary with a **dictionary comprehension**. +- `generate_phonetic` turns a word into its code words with a **list comprehension**. +- The standard library `csv` module replaces Pandas, so the project has no runtime dependencies (see the architecture decision record [ADR-0001][ADR-0001]). + +The wiki explains installation, usage and development in English, Danish and Bahasa Malaysia: [wiki][wiki]. + +## 📋 Requirements + +- Python 3.13 or newer, with the `venv` module. +- Git, to clone the repository. +- Optional: [Doxygen](https://www.doxygen.nl/) to build the source documentation. + +The program itself needs no packages. The development tools (pytest, ruff, mypy, import-linter) are installed by the `dev` extra. + +## 🛠️ Setup + +Clone the repository, then create a local virtual environment named `.venv` in its root, upgrade pip and install the project in editable mode with its development tools. + +### Windows PowerShell + +```powershell +git clone https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet.git +cd 026-nato-alphabet +py -3.13 -m venv .venv +.\.venv\Scripts\Activate.ps1 +python -m pip install --upgrade pip +python -m pip install -e ".[dev]" +``` + +If PowerShell refuses to run the activation script, allow it for this window only with `Set-ExecutionPolicy -Scope Process RemoteSigned`. + +### Linux Debian + +```bash +sudo apt install git python3 python3-venv +git clone https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet.git +cd 026-nato-alphabet +python3 -m venv .venv +source .venv/bin/activate +python -m pip install --upgrade pip +python -m pip install -e ".[dev]" +``` + +Debian 13 (trixie) ships Python 3.13. On an older release install Python 3.13 first (for example with `pyenv`). + +### MacOS + +```bash +brew install python@3.13 git +git clone https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet.git +cd 026-nato-alphabet +python3.13 -m venv .venv +source .venv/bin/activate +python -m pip install --upgrade pip +python -m pip install -e ".[dev]" +``` + +## ▶️ Run + +With the virtual environment active: + +```bash +python -m nato_alphabet +``` + +or, as the installed command: + +```bash +nato-alphabet +``` + +An invalid character asks for the word again: + +![A word with a digit is rejected, then a valid word is spelled][screenshot-retry] + +## 🧪 Tests + +Run the tests with the virtual environment active: + +```bash +python -m pytest +``` + +The same checks that CI runs: + +```bash +python -m ruff check . +python -m mypy +lint-imports +``` + +## 🔄 Continuous integration + +The workflow [`.gitea/workflows/ci.yml`](.gitea/workflows/ci.yml) runs on every push to `main` or a `mil-*` branch and on every pull request that touches source, tests or configuration. It sets up Python 3.13, creates a virtual environment, installs the project with the `dev` extra, and runs ruff, mypy and pytest. + +## 📖 Build the source documentation + +The source is documented with Doxygen comments. From the repository root: + +```bash +doxygen Doxyfile +``` + +Open `build/html/index.html` in a browser. The `build/` folder is ignored by git. + +## 🗂️ Project layout + +```text +src/nato_alphabet/ + constants.py every literal of the program + main.py composition root and entry point + __main__.py python -m nato_alphabet + domain/ the spelling rule (no input or output) + application/ the use case and its ports + adapters/ CSV reader and console + data/ nato_phonetic_alphabet.csv +tests/ pytest tests, mirroring src/ +docs/ project documents (see the appendix) +wiki/ wiki pages in en, da and ms +.gitea/workflows/ CI workflow +pyproject.toml project configuration +Doxyfile Doxygen configuration +``` + +## 📄 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 | +| [Architecture decisions][adr] | Why the data is read with the standard library and which analysis documents were skipped | +| [Artifact registry][registry] | Where each artifact type lives, the next version, the PO language and domain | +| [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 | + +--- + +[repository]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet +[documentation]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet/wiki +[wiki]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet/wiki +[issues]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet/issues +[screenshot-spell]: ./docs/images/spell-word.png +[screenshot-retry]: ./docs/images/retry-invalid.png +[agents]: ./AGENTS.md +[BC-001]: ./docs/business-case.md +[SA-001]: ./docs/stakeholder-analysis.md +[PP-001]: ./docs/project-plan.md +[milestones]: ./docs/milestones/ +[adr]: ./docs/adr/ +[ADR-0001]: ./docs/adr/adr-0001-read-nato-data-with-csv-module.md +[registry]: ./docs/artifact-registry.md +[TM-001]: ./docs/sqa/traceability-matrix.md +[framework-readme]: ./framework/README.md +[framework-changelog]: ./framework/CHANGELOG.md diff --git a/docs/images/retry-invalid.png b/docs/images/retry-invalid.png new file mode 100644 index 0000000000000000000000000000000000000000..a0994f02995524ec9a6e530234332bdcd21849d6 GIT binary patch literal 24391 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 YxkSFK-4yzXhbK`wdtUA(;nJP|1JMEwKmY&$ literal 0 HcmV?d00001 diff --git a/src/nato_alphabet/__init__.py b/src/nato_alphabet/__init__.py index d823b9a..ceb2924 100644 --- a/src/nato_alphabet/__init__.py +++ b/src/nato_alphabet/__init__.py @@ -1 +1,9 @@ -"""@brief Console tool that spells a word with the NATO phonetic alphabet.""" +"""@brief Console tool that spells a word with the NATO phonetic alphabet. + +@mainpage NATO alphabet + +The Day 26 assignment of "100 Days of Code: The Complete Python Pro Bootcamp". +`load_nato_dict` builds the letter-to-code-word dictionary with a dictionary +comprehension and `generate_phonetic` spells a word with a list comprehension. +Run it with `python -m nato_alphabet`. +""" diff --git a/wiki/Development-da.md b/wiki/Development-da.md new file mode 100644 index 0000000..75390ac --- /dev/null +++ b/wiki/Development-da.md @@ -0,0 +1,43 @@ +# Udvikling + +🌐 [English](Development) · **Dansk** · [Bahasa Melayu](Development-ms) + +Sæt projektet op først ([Installation](Install-da)). Udvidelsen `dev` installerer pytest, ruff, mypy og import-linter. + +## Kør testene + +```bash +python -m pytest +``` + +## Kør de kontroller, CI kører + +```bash +python -m ruff check . +python -m mypy +lint-imports +``` + +## Byg kildedokumentationen + +```bash +doxygen Doxyfile +``` + +Åbn `build/html/index.html`. Installér Doxygen fra [doxygen.nl](https://www.doxygen.nl/) eller din pakkehåndtering. + +## Kodens opbygning + +| Mappe | Indhold | +| --- | --- | +| `src/nato_alphabet/domain/` | Staveregelen, uden input og output | +| `src/nato_alphabet/application/` | Use casen og dens porte | +| `src/nato_alphabet/adapters/` | CSV-læseren og konsollen | +| `src/nato_alphabet/constants.py` | Alle programmets konstanter | +| `tests/` | pytest-tests, som spejler `src/` | + +Afhængigheder peger indad: adapters kender applikationen, applikationen kender domænet. import-linter håndhæver det. + +## Pull requests + +Én gren og én pull request pr. milepæl; CI skal være grøn. Projektet følger SQA- og QC-frameworket i `framework/`; se `docs/` for planen og review-optegnelserne. diff --git a/wiki/Development-ms.md b/wiki/Development-ms.md new file mode 100644 index 0000000..60e085b --- /dev/null +++ b/wiki/Development-ms.md @@ -0,0 +1,43 @@ +# Pembangunan + +🌐 [English](Development) · [Dansk](Development-da) · **Bahasa Melayu** + +Sediakan projek dahulu ([Pemasangan](Install-ms)). Pilihan tambahan `dev` memasang pytest, ruff, mypy dan import-linter. + +## Jalankan ujian + +```bash +python -m pytest +``` + +## Jalankan semakan yang dijalankan oleh CI + +```bash +python -m ruff check . +python -m mypy +lint-imports +``` + +## Bina dokumentasi sumber + +```bash +doxygen Doxyfile +``` + +Buka `build/html/index.html`. Pasang Doxygen dari [doxygen.nl](https://www.doxygen.nl/) atau pengurus pakej anda. + +## Susun atur kod + +| Folder | Kandungan | +| --- | --- | +| `src/nato_alphabet/domain/` | Peraturan ejaan, tanpa input dan output | +| `src/nato_alphabet/application/` | Kes guna dan portnya | +| `src/nato_alphabet/adapters/` | Pembaca CSV dan konsol | +| `src/nato_alphabet/constants.py` | Semua pemalar program | +| `tests/` | Ujian pytest, mencerminkan `src/` | + +Kebergantungan menghala ke dalam: adapter mengenali aplikasi, aplikasi mengenali domain. import-linter menguatkuasakannya. + +## Pull request + +Satu cabang dan satu pull request bagi setiap milestone; CI mesti hijau. Projek ini mengikut rangka kerja SQA dan QC dalam `framework/`; lihat `docs/` untuk pelan dan rekod semakan. diff --git a/wiki/Development.md b/wiki/Development.md new file mode 100644 index 0000000..3a60c80 --- /dev/null +++ b/wiki/Development.md @@ -0,0 +1,43 @@ +# Development + +🌐 **English** · [Dansk](Development-da) · [Bahasa Melayu](Development-ms) + +Set up the project first ([Install](Install)). The `dev` extra installs pytest, ruff, mypy and import-linter. + +## Run the tests + +```bash +python -m pytest +``` + +## Run the checks that CI runs + +```bash +python -m ruff check . +python -m mypy +lint-imports +``` + +## Build the source documentation + +```bash +doxygen Doxyfile +``` + +Open `build/html/index.html`. Install Doxygen from [doxygen.nl](https://www.doxygen.nl/) or your package manager. + +## Code layout + +| Folder | Content | +| --- | --- | +| `src/nato_alphabet/domain/` | The spelling rule, with no input or output | +| `src/nato_alphabet/application/` | The use case and its ports | +| `src/nato_alphabet/adapters/` | The CSV reader and the console | +| `src/nato_alphabet/constants.py` | Every literal of the program | +| `tests/` | pytest tests, mirroring `src/` | + +Dependencies point inward: adapters know the application, the application knows the domain. import-linter enforces it. + +## Pull requests + +One branch and one pull request per milestone; CI must be green. The project follows the SQA and QC framework in `framework/`; see `docs/` for the plan and the review records. diff --git a/wiki/Home-da.md b/wiki/Home-da.md new file mode 100644 index 0000000..b4e7665 --- /dev/null +++ b/wiki/Home-da.md @@ -0,0 +1,23 @@ +# NATO-alfabet + +🌐 [English](Home) · **Dansk** · [Bahasa Melayu](Home-ms) + +Et konsolprogram, der staver et ord med det fonetiske NATO-alfabet. Det er opgaven fra dag 26 i Udemys "100 Days of Code: The Complete Python Pro Bootcamp" og viser list comprehensions og dictionary comprehensions. Det kræver Python 3.13 eller nyere og har ingen runtime-afhængigheder. + +![Programmet staver ordet Hello](images/spell-word.png) + +| Side | Læs den, når du vil | +| --- | --- | +| [Installation](Install-da) | Oprette et lokalt `.venv` og installere projektet på Windows, Linux eller macOS | +| [Brug](Usage-da) | Køre programmet og læse dets output | +| [Udvikling](Development-da) | Køre testene og kontrollerne og bygge kildedokumentationen | + +## Hvem det er til + +- **Kursister**, der vil have en læsbar løsning med opgavens funktionsnavne (`load_nato_dict`, `generate_phonetic`) at sammenligne med deres egen. +- **GitHub-besøgende**, der vil se et lille, testet Python-projekt uden runtime-afhængigheder. + +## Links + +- [Repository](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet) +- [Fejl og ønsker](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet/issues) diff --git a/wiki/Home-ms.md b/wiki/Home-ms.md new file mode 100644 index 0000000..cc30d9f --- /dev/null +++ b/wiki/Home-ms.md @@ -0,0 +1,23 @@ +# Abjad NATO + +🌐 [English](Home) · [Dansk](Home-da) · **Bahasa Melayu** + +Alat konsol yang mengeja satu perkataan dengan abjad fonetik NATO. Ia ialah tugasan hari ke-26 dalam "100 Days of Code: The Complete Python Pro Bootcamp" di Udemy dan menunjukkan list comprehension serta dictionary comprehension. Ia memerlukan Python 3.13 atau lebih baharu dan tidak mempunyai kebergantungan masa jalan. + +![Alat mengeja perkataan Hello](images/spell-word.png) + +| Halaman | Baca apabila anda mahu | +| --- | --- | +| [Pemasangan](Install-ms) | Mencipta `.venv` setempat dan memasang projek di Windows, Linux atau macOS | +| [Penggunaan](Usage-ms) | Menjalankan program dan membaca outputnya | +| [Pembangunan](Development-ms) | Menjalankan ujian dan semakan serta membina dokumentasi sumber | + +## Untuk siapa + +- **Peserta kursus** yang mahukan penyelesaian yang mudah dibaca dengan nama fungsi tugasan (`load_nato_dict`, `generate_phonetic`) untuk dibandingkan dengan penyelesaian sendiri. +- **Pelawat GitHub** yang mahukan projek Python kecil yang diuji tanpa kebergantungan masa jalan. + +## Pautan + +- [Repositori](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet) +- [Penjejak isu](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet/issues) diff --git a/wiki/Home.md b/wiki/Home.md new file mode 100644 index 0000000..87ef8db --- /dev/null +++ b/wiki/Home.md @@ -0,0 +1,23 @@ +# NATO alphabet + +🌐 **English** · [Dansk](Home-da) · [Bahasa Melayu](Home-ms) + +A console tool that spells a word with the NATO phonetic alphabet. It is the Day 26 assignment of Udemy's "100 Days of Code: The Complete Python Pro Bootcamp" and shows list and dictionary comprehensions. It needs Python 3.13 or newer and has no runtime dependencies. + +![The tool spelling the word Hello](images/spell-word.png) + +| Page | Read it when you want to | +| --- | --- | +| [Install](Install) | Create a local `.venv` and install the project on Windows, Linux or macOS | +| [Usage](Usage) | Run the program and read its output | +| [Development](Development) | Run the tests, the checks and build the source documentation | + +## Who it is for + +- **Course participants** who want a readable solution with the assignment's function names (`load_nato_dict`, `generate_phonetic`) to compare with their own. +- **GitHub viewers** who want a small, tested Python project with no runtime dependencies. + +## Links + +- [Repository](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet) +- [Issue tracker](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet/issues) diff --git a/wiki/Install-da.md b/wiki/Install-da.md new file mode 100644 index 0000000..6364ed7 --- /dev/null +++ b/wiki/Install-da.md @@ -0,0 +1,57 @@ +# Installation + +🌐 [English](Install) · **Dansk** · [Bahasa Melayu](Install-ms) + +## Det skal du bruge + +- Python 3.13 eller nyere med modulet `venv` +- Git + +## Trin + +Alle systemer gør de samme fire ting: klon repository'et, opret et virtuelt miljø kaldet `.venv`, opgradér pip og installér projektet. + +### Windows PowerShell + +```powershell +git clone https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet.git +cd 026-nato-alphabet +py -3.13 -m venv .venv +.\.venv\Scripts\Activate.ps1 +python -m pip install --upgrade pip +python -m pip install -e ".[dev]" +``` + +Hvis PowerShell afviser aktiveringsscriptet, så kør først `Set-ExecutionPolicy -Scope Process RemoteSigned`. Det gælder kun for det vindue. + +### Linux Debian + +```bash +sudo apt install git python3 python3-venv +git clone https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet.git +cd 026-nato-alphabet +python3 -m venv .venv +source .venv/bin/activate +python -m pip install --upgrade pip +python -m pip install -e ".[dev]" +``` + +### macOS + +```bash +brew install python@3.13 git +git clone https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet.git +cd 026-nato-alphabet +python3.13 -m venv .venv +source .venv/bin/activate +python -m pip install --upgrade pip +python -m pip install -e ".[dev]" +``` + +## Tjek at det virker + +```bash +python -m nato_alphabet +``` + +Skriv et ord. Du skal se dets kodeord. Næste side: [Brug](Usage-da). diff --git a/wiki/Install-ms.md b/wiki/Install-ms.md new file mode 100644 index 0000000..f71d540 --- /dev/null +++ b/wiki/Install-ms.md @@ -0,0 +1,57 @@ +# Pemasangan + +🌐 [English](Install) · [Dansk](Install-da) · **Bahasa Melayu** + +## Apa yang diperlukan + +- Python 3.13 atau lebih baharu dengan modul `venv` +- Git + +## Langkah + +Semua sistem melakukan empat perkara yang sama: klon repositori, cipta persekitaran maya bernama `.venv`, naik taraf pip dan pasang projek. + +### Windows PowerShell + +```powershell +git clone https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet.git +cd 026-nato-alphabet +py -3.13 -m venv .venv +.\.venv\Scripts\Activate.ps1 +python -m pip install --upgrade pip +python -m pip install -e ".[dev]" +``` + +Jika PowerShell enggan menjalankan skrip pengaktifan, jalankan `Set-ExecutionPolicy -Scope Process RemoteSigned` dahulu. Ia hanya terpakai pada tetingkap itu. + +### Linux Debian + +```bash +sudo apt install git python3 python3-venv +git clone https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet.git +cd 026-nato-alphabet +python3 -m venv .venv +source .venv/bin/activate +python -m pip install --upgrade pip +python -m pip install -e ".[dev]" +``` + +### macOS + +```bash +brew install python@3.13 git +git clone https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet.git +cd 026-nato-alphabet +python3.13 -m venv .venv +source .venv/bin/activate +python -m pip install --upgrade pip +python -m pip install -e ".[dev]" +``` + +## Semak ia berfungsi + +```bash +python -m nato_alphabet +``` + +Masukkan satu perkataan. Anda akan melihat kata kodnya. Seterusnya: [Penggunaan](Usage-ms). diff --git a/wiki/Install.md b/wiki/Install.md new file mode 100644 index 0000000..0dd6285 --- /dev/null +++ b/wiki/Install.md @@ -0,0 +1,57 @@ +# Install + +🌐 **English** · [Dansk](Install-da) · [Bahasa Melayu](Install-ms) + +## What you need + +- Python 3.13 or newer with the `venv` module +- Git + +## Steps + +Every system does the same four things: clone the repository, create a virtual environment called `.venv`, upgrade pip, install the project. + +### Windows PowerShell + +```powershell +git clone https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet.git +cd 026-nato-alphabet +py -3.13 -m venv .venv +.\.venv\Scripts\Activate.ps1 +python -m pip install --upgrade pip +python -m pip install -e ".[dev]" +``` + +If PowerShell refuses to run the activation script, run `Set-ExecutionPolicy -Scope Process RemoteSigned` first. It only applies to that window. + +### Linux Debian + +```bash +sudo apt install git python3 python3-venv +git clone https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet.git +cd 026-nato-alphabet +python3 -m venv .venv +source .venv/bin/activate +python -m pip install --upgrade pip +python -m pip install -e ".[dev]" +``` + +### macOS + +```bash +brew install python@3.13 git +git clone https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet.git +cd 026-nato-alphabet +python3.13 -m venv .venv +source .venv/bin/activate +python -m pip install --upgrade pip +python -m pip install -e ".[dev]" +``` + +## Check that it works + +```bash +python -m nato_alphabet +``` + +Enter a word. You should see its code words. Next: [Usage](Usage). diff --git a/wiki/Usage-da.md b/wiki/Usage-da.md new file mode 100644 index 0000000..b0bc38b --- /dev/null +++ b/wiki/Usage-da.md @@ -0,0 +1,31 @@ +# Brug + +🌐 [English](Usage) · **Dansk** · [Bahasa Melayu](Usage-ms) + +Aktivér det virtuelle miljø (se [Installation](Install-da)), og start programmet: + +```bash +python -m nato_alphabet +``` + +Den installerede kommando gør det samme: + +```bash +nato-alphabet +``` + +## Stav et ord + +Programmet spørger efter et ord og skriver NATO-kodeordet for hvert bogstav i rækkefølge. Store og små bogstaver giver samme resultat. + +![Programmet staver ordet Hello](images/spell-word.png) + +## Ugyldigt input + +Et ciffer, et mellemrum eller et tegn er ikke et bogstav. Programmet siger det og spørger igen. + +![Et ord med et ciffer afvises, derefter staves et gyldigt ord](images/retry-invalid.png) + +## Tryk Ctrl+C eller Ctrl+D + +Afslutter du inputtet (Ctrl+D på Linux og macOS, Ctrl+Z og Enter på Windows) eller trykker Ctrl+C, lukker programmet stille. diff --git a/wiki/Usage-ms.md b/wiki/Usage-ms.md new file mode 100644 index 0000000..57454df --- /dev/null +++ b/wiki/Usage-ms.md @@ -0,0 +1,31 @@ +# Penggunaan + +🌐 [English](Usage) · [Dansk](Usage-da) · **Bahasa Melayu** + +Aktifkan persekitaran maya (lihat [Pemasangan](Install-ms)), kemudian mulakan program: + +```bash +python -m nato_alphabet +``` + +Arahan yang dipasang melakukan perkara yang sama: + +```bash +nato-alphabet +``` + +## Eja satu perkataan + +Program meminta satu perkataan dan mencetak kata kod NATO bagi setiap huruf mengikut urutan. Huruf besar dan huruf kecil memberi keputusan yang sama. + +![Alat mengeja perkataan Hello](images/spell-word.png) + +## Input tidak sah + +Angka, ruang atau tanda baca bukan huruf. Program memberitahu perkara itu dan bertanya semula. + +![Perkataan dengan angka ditolak, kemudian perkataan yang sah dieja](images/retry-invalid.png) + +## Tekan Ctrl+C atau Ctrl+D + +Menamatkan input (Ctrl+D di Linux dan macOS, Ctrl+Z kemudian Enter di Windows) atau menekan Ctrl+C menutup program dengan senyap. diff --git a/wiki/Usage.md b/wiki/Usage.md new file mode 100644 index 0000000..e495c98 --- /dev/null +++ b/wiki/Usage.md @@ -0,0 +1,31 @@ +# Usage + +🌐 **English** · [Dansk](Usage-da) · [Bahasa Melayu](Usage-ms) + +Activate the virtual environment (see [Install](Install)), then start the program: + +```bash +python -m nato_alphabet +``` + +The installed command does the same: + +```bash +nato-alphabet +``` + +## Spell a word + +The program asks for a word and prints the NATO code word of each letter, in order. Upper and lower case give the same result. + +![The tool spelling the word Hello](images/spell-word.png) + +## Invalid input + +A digit, a space or a punctuation mark is not a letter. The program says so and asks again. + +![A word with a digit is rejected, then a valid word is spelled](images/retry-invalid.png) + +## Press Ctrl+C or Ctrl+D + +Ending the input (Ctrl+D on Linux and macOS, Ctrl+Z then Enter on Windows) or pressing Ctrl+C closes the program quietly. diff --git a/wiki/_Sidebar.md b/wiki/_Sidebar.md new file mode 100644 index 0000000..b84a665 --- /dev/null +++ b/wiki/_Sidebar.md @@ -0,0 +1,24 @@ +**NATO alphabet** + +- [Home](Home) +- [Install](Install) +- [Usage](Usage) +- [Development](Development) + +**Dansk** + +- [Hjem](Home-da) +- [Installation](Install-da) +- [Brug](Usage-da) +- [Udvikling](Development-da) + +**Bahasa Melayu** + +- [Utama](Home-ms) +- [Pemasangan](Install-ms) +- [Penggunaan](Usage-ms) +- [Pembangunan](Development-ms) + +**Links** + +- [Repository](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet) -- 2.54.0 From 92b954331c2deb7f9f641eb6266007496b404356 Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Tue, 13 Oct 2026 16:47:34 +0800 Subject: [PATCH 2/3] Review MIL-003 changes; shorten Doxygen file names for deep Windows paths Task: MIL-003#7 Refs #19 Refs #20 --- Doxyfile | 2 +- docs/artifact-registry.md | 2 +- .../sqa/reviews/rc-015-python-package-docs.md | 59 +++++++++++++++++++ docs/sqa/traceability-matrix.md | 9 ++- 4 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 docs/sqa/reviews/rc-015-python-package-docs.md diff --git a/Doxyfile b/Doxyfile index 10fa16b..66ccc09 100644 --- a/Doxyfile +++ b/Doxyfile @@ -32,7 +32,7 @@ INLINE_INHERITED_MEMB = NO FULL_PATH_NAMES = YES STRIP_FROM_PATH = STRIP_FROM_INC_PATH = -SHORT_NAMES = NO +SHORT_NAMES = YES JAVADOC_AUTOBRIEF = NO JAVADOC_BANNER = NO QT_AUTOBRIEF = NO diff --git a/docs/artifact-registry.md b/docs/artifact-registry.md index 23e096e..bc66611 100644 --- a/docs/artifact-registry.md +++ b/docs/artifact-registry.md @@ -15,7 +15,7 @@ document of a type. `Primary File` may contain a glob (e.g. | SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 | | PP | Project Plan | docs/project-plan.md | 002 | | MIL | Milestone / Gateway | docs/milestones/*.md | 004 | -| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 015 | +| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 016 | | TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 | | ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0003 | | UCD | Use Case Diagram | docs/use-case-diagram.md | 002 | diff --git a/docs/sqa/reviews/rc-015-python-package-docs.md b/docs/sqa/reviews/rc-015-python-package-docs.md new file mode 100644 index 0000000..11b32a5 --- /dev/null +++ b/docs/sqa/reviews/rc-015-python-package-docs.md @@ -0,0 +1,59 @@ +# SQA Review Record RC-015: PY-003 + +## Metadata +| Key | Value | +| --- | --- | +| ID | RC-015 | +| CrossReference | [PY-003], [QC-PY-001] | + +## Version History +| Date | Status | Author | Reviewer | Change | Commit | +| --- | --- | --- | --- | --- | --- | +| 2026-10-13 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Review of PY-003 | pending | + +--- + +## Artifact Under Review + +- Instance reviewed: [PY-003] +- Checklist used: [QC-PY-001] +- Scope: full review +- Language and domain: n/a (technical type) +- Language reviewer: none +- Mode: autonomous, not independent (the 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 | ruff rule set N passes; modules snake_case, classes PascalCase, `InvalidWordError` ends in Error. | +| 2 | Names state purpose in the domain's language; no unexplained abbreviations, no single-letter names outside tiny scopes | Pass | Names state purpose: `generate_phonetic`, `load_nato_dict`, `SpellWordSession`. | +| 3 | Code is produced by the project's formatter and passes its linter with no unexplained suppressions | Pass | ruff 0.17.0 check and format pass; no suppressions. | +| 4 | Every function and method signature is type-annotated, including `-> None` | Pass | All signatures annotated; mypy --strict passes. | +| 5 | No bare `except:`, no swallowed exceptions; specific exceptions are raised and the cause is kept (`raise ... from`) | Pass | `InvalidWordError` is raised `from` the KeyError; the console adapter maps only EOFError and KeyboardInterrupt to `InputClosedError`; no bare except. | +| 6 | No mutable default arguments and no shadowed builtins | Pass | No mutable defaults; `path` defaults to None. | +| 7 | Files, locks and connections are managed with context managers | Pass | The CSV is read inside a `with` block. | +| 8 | Public modules, classes and functions have docstrings that say what, not how | Pass | Every module, class, function and constant has a Doxygen docstring; doxygen reports 0 warnings. | +| 9 | Logging uses `logging`, not `print`; no secrets or personal data in log output | Pass | `print` and `input` are confined to `adapters/console.py` because the console is the program's output; there is no logging and no secret or personal data. | +| 10 | Classes and operations trace to the Design Class Diagram they implement; deviations are recorded | N-A | No design class diagram exists; MIL-002 plans none (the SSD and use case are the design artifacts). Classes follow the ports in `application/ports.py`. | +| 11 | Tests exist for new behaviour, are named for the behaviour, and do not depend on order or the network | Pass | 50 tests named for behaviour; `tmp_path` and fakes; no network; order independent. | +| 12 | Type checker runs in strict mode without errors; `Any` is justified in a comment | Pass | mypy --strict: no issues in 18 files; no `Any`. | +| 13 | Dependencies are declared and pinned in the project's dependency file, none unused | Pass | No runtime dependencies; development tools pinned with `==`. | +| 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 | Each literal is defined once in `constants.py`; the letter mapping is defined once in the CSV. | +| 15 | Imports point inward: business-rule modules import no framework, database, UI or delivery-mechanism code, and there are no import cycles | Pass | import-linter contract `Dependencies point inward` is kept; no cycles. | +| 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 | Ports are narrow Protocols; the session depends on them, adapters implement them. | + +## Overall Verdict + +Go — re-review of the package after MIL-003 changed only the package docstring (Doxygen main page) and the Doxyfile (`INPUT = src`, `SHORT_NAMES = YES`); the checks are repeated: pytest 50 passed, ruff, mypy --strict, import-linter and doxygen (0 warnings) pass, also in a fresh clone on Windows PowerShell. The README is checked with check-readme.sh (no QC checklist exists for it). + +## Action Items + +| Action | Owner | Due | +| --- | --- | --- | +| None | S01 | n/a | + +--- + +[PY-003]: ../../../src/nato_alphabet/__init__.py +[QC-PY-001]: ../../../framework/qc/code/qc-programming-python.md diff --git a/docs/sqa/traceability-matrix.md b/docs/sqa/traceability-matrix.md index 4cefe67..a9ac38b 100644 --- a/docs/sqa/traceability-matrix.md +++ b/docs/sqa/traceability-matrix.md @@ -9,8 +9,8 @@ ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-10-13 | Deprecated | Jens Tirsvad Nielsen | AI as stand-in for S01 | Added rows for ADR-0001, ADR-0002, the CI workflow and the Python skeleton (RC-006 to RC-009) | [a381791] | -| 2026-10-13 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Added rows for the MIL-002 use case artifacts and converter code (RC-010 to RC-014) | [1b7da4b] | +| 2026-10-13 | Deprecated | Jens Tirsvad Nielsen | AI as stand-in for S01 | Added rows for the MIL-002 use case artifacts and converter code (RC-010 to RC-014) | [1b7da4b] | +| 2026-10-13 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Added the package re-review row (RC-015) | pending | --- @@ -41,7 +41,8 @@ Tracks backward and forward links between artifact instances so that the cross-a | [US-001] | User Story | en | it | [UCD-001], [MIL-002] | [UC-001] | [RC-011] | | [UC-001] | Use Case | en | it | [UCD-001], [US-001] | [SSD-001], [PY-002] | [RC-012] | | [SSD-001] | System Sequence Diagram | en | it | [UC-001] | [PY-002] | [RC-013] | -| [PY-002] | Python source | - | - | [UC-001], [SSD-001], [ADR-0001] | - | [RC-014] | +| [PY-002] | Python source | - | - | [UC-001], [SSD-001], [ADR-0001] | [PY-003] | [RC-014] | +| [PY-003] | Python source | - | - | [PY-002], [MIL-003] | - | [RC-015] | | [PY-001] | Python source | - | - | [ADR-0001], [MIL-001] | - | [RC-009] | ## Coverage Notes @@ -85,3 +86,5 @@ Tracks backward and forward links between artifact instances so that the cross-a [RC-013]: ./reviews/rc-013-ssd-001.md [RC-014]: ./reviews/rc-014-python-converter.md [1b7da4b]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet/commit/1b7da4bcf95f69bf009b0a1f3d0ab740ae77e340 +[PY-003]: ../../src/nato_alphabet/__init__.py +[RC-015]: ./reviews/rc-015-python-package-docs.md -- 2.54.0 From 402f0c9e1a6b181ba931cb426aea4d4045d1529d Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Tue, 13 Oct 2026 16:47:35 +0800 Subject: [PATCH 3/3] Resolve pending commit links for MIL-003 --- docs/sqa/reviews/rc-015-python-package-docs.md | 3 ++- docs/sqa/traceability-matrix.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/sqa/reviews/rc-015-python-package-docs.md b/docs/sqa/reviews/rc-015-python-package-docs.md index 11b32a5..fc18164 100644 --- a/docs/sqa/reviews/rc-015-python-package-docs.md +++ b/docs/sqa/reviews/rc-015-python-package-docs.md @@ -9,7 +9,7 @@ ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-10-13 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Review of PY-003 | pending | +| 2026-10-13 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Review of PY-003 | [92b9543] | --- @@ -57,3 +57,4 @@ Go — re-review of the package after MIL-003 changed only the package docstring [PY-003]: ../../../src/nato_alphabet/__init__.py [QC-PY-001]: ../../../framework/qc/code/qc-programming-python.md +[92b9543]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet/commit/92b954331c2deb7f9f641eb6266007496b404356 diff --git a/docs/sqa/traceability-matrix.md b/docs/sqa/traceability-matrix.md index a9ac38b..f8fbaef 100644 --- a/docs/sqa/traceability-matrix.md +++ b/docs/sqa/traceability-matrix.md @@ -10,7 +10,7 @@ | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | | 2026-10-13 | Deprecated | Jens Tirsvad Nielsen | AI as stand-in for S01 | Added rows for the MIL-002 use case artifacts and converter code (RC-010 to RC-014) | [1b7da4b] | -| 2026-10-13 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Added the package re-review row (RC-015) | pending | +| 2026-10-13 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Added the package re-review row (RC-015) | [92b9543] | --- @@ -88,3 +88,4 @@ Tracks backward and forward links between artifact instances so that the cross-a [1b7da4b]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet/commit/1b7da4bcf95f69bf009b0a1f3d0ab740ae77e340 [PY-003]: ../../src/nato_alphabet/__init__.py [RC-015]: ./reviews/rc-015-python-package-docs.md +[92b9543]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/026-nato-alphabet/commit/92b954331c2deb7f9f641eb6266007496b404356 -- 2.54.0