diff --git a/README.md b/README.md index 2753b2d..98d7628 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,27 @@ The repository also holds the small Flask programs of the day's lectures: URL pa | `src/higher_lower/` | The game, split in layers: `domain`, `application`, `infrastructure`, `presentation` | | `src/higher_lower/constants.py` | Every heading, colour, GIF and number range in one place | | `tests/` | pytest tests | +| `tools/` | Script that captures the lecture screenshots in a Docker container | | `docs/` | Project documents (business case, plan, milestones, reviews) | | `Doxyfile` | Settings to generate API documentation with Doxygen | +### Lecture steps + +One screenshot for each lecture step. The [wiki][wiki] explains what each step teaches, in English, Danish and Bahasa Malaysia. + +| Step | Code | Screenshot | +| --- | --- | --- | +| 1. Goals: what you build | `server.py` | ![Start page of the game](docs/screenshots/lecture-1-goals.png) | +| 2. URL paths and variable routes | `step_02_url_paths.py` | ![Route /username/Angela](docs/screenshots/lecture-2-url-paths.png) | +| 3. Rendering HTML | `step_03_html.py` | ![Heading, paragraph and images](docs/screenshots/lecture-3-html.png) | +| 4. Decorators for HTML tags | `step_04_decorators.py` | ![Route /bye in bold, emphasis and underline](docs/screenshots/lecture-4-decorators.png) | +| 5. Advanced decorators with `*args` and `**kwargs` | `step_05_authenticated.py` | ![Logged in and logged out user](docs/screenshots/lecture-5-authenticated.png) | +| 6. Final project: 3 is too low | `server.py` | ![3 is too low](docs/screenshots/lecture-6-too-low.png) | +| 6. Final project: 7 is too high | `server.py` | ![7 is too high](docs/screenshots/lecture-6-too-high.png) | +| 6. Final project: 5 is correct | `server.py` | ![5 is correct](docs/screenshots/lecture-6-correct.png) | + +The lecture modules are in `src/higher_lower/presentation/lectures/`; run one with `python -m higher_lower.presentation.lectures.step_02_url_paths`. The screenshots were taken in a Docker container with a headless browser (`bash tools/capture_screenshots.sh`), not on a desktop. + ## 📋 Requirements - Python 3.13 or later, to run the code. @@ -110,6 +128,7 @@ The documents a newcomer needs, in reading order. [repository]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower [documentation]: ./docs/ [issues]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/issues +[wiki]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/wiki [agents]: ./AGENTS.md [BC-001]: ./docs/business-case.md [SA-001]: ./docs/stakeholder-analysis.md diff --git a/docs/artifact-registry.md b/docs/artifact-registry.md index dbed37f..3eb49d7 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 | 005 | -| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 011 | +| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 013 | ## Languages diff --git a/docs/screenshots/lecture-1-goals.png b/docs/screenshots/lecture-1-goals.png new file mode 100644 index 0000000..8aed87d Binary files /dev/null and b/docs/screenshots/lecture-1-goals.png differ diff --git a/docs/screenshots/lecture-2-url-paths.png b/docs/screenshots/lecture-2-url-paths.png new file mode 100644 index 0000000..a3fa96a Binary files /dev/null and b/docs/screenshots/lecture-2-url-paths.png differ diff --git a/docs/screenshots/lecture-3-html.png b/docs/screenshots/lecture-3-html.png new file mode 100644 index 0000000..da13625 Binary files /dev/null and b/docs/screenshots/lecture-3-html.png differ diff --git a/docs/screenshots/lecture-4-decorators.png b/docs/screenshots/lecture-4-decorators.png new file mode 100644 index 0000000..d6f6ad8 Binary files /dev/null and b/docs/screenshots/lecture-4-decorators.png differ diff --git a/docs/screenshots/lecture-5-authenticated.png b/docs/screenshots/lecture-5-authenticated.png new file mode 100644 index 0000000..a92de1a Binary files /dev/null and b/docs/screenshots/lecture-5-authenticated.png differ diff --git a/docs/screenshots/lecture-6-correct.png b/docs/screenshots/lecture-6-correct.png new file mode 100644 index 0000000..7b64161 Binary files /dev/null and b/docs/screenshots/lecture-6-correct.png differ diff --git a/docs/screenshots/lecture-6-too-high.png b/docs/screenshots/lecture-6-too-high.png new file mode 100644 index 0000000..0a8f140 Binary files /dev/null and b/docs/screenshots/lecture-6-too-high.png differ diff --git a/docs/screenshots/lecture-6-too-low.png b/docs/screenshots/lecture-6-too-low.png new file mode 100644 index 0000000..7158c17 Binary files /dev/null and b/docs/screenshots/lecture-6-too-low.png differ diff --git a/docs/sqa/reviews/rc-011-wiki.md b/docs/sqa/reviews/rc-011-wiki.md new file mode 100644 index 0000000..b0c0d5e --- /dev/null +++ b/docs/sqa/reviews/rc-011-wiki.md @@ -0,0 +1,61 @@ +# RC-011 Wiki review + +## Metadata +| Key | Value | +| --- | --- | +| ID | RC-011 | +| CrossReference | [MIL-004], [QC-WIKI-001] | + +## Version History +| Date | Status | Author | Reviewer | Change | Commit | +| --- | --- | --- | --- | --- | --- | +| 2026-11-06 | Proposed | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | [1887fc3] | + +--- + +## Artifact Under Review + +- Instance reviewed: [MIL-004] +- Checklist used: [QC-WIKI-001] +- Scope: full review of docs/wiki/ (25 files), the deliverable of MIL-004 tasks 3 to 5 +- Language and domain: en, da, ms (wiki, not a PO-language artifact type) +- Language reviewer: none; the Danish and Bahasa Malaysia translations are by the AI and are not independently checked +- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`) + +## Checklist Results + +| # | Criterion | Status | Evidence/Notes | +| --- | --- | --- | --- | +| 1 | The wiki has a `_Sidebar.md`, because the host lists subpages only through it | Pass | docs/wiki/_Sidebar.md exists | +| 2 | The sidebar starts with the wiki's title in bold, then one list of the pages in the primary language (English), `Home` first | Pass | Sidebar starts with the bold title Higher or Lower, then one English page list with Home first | +| 3 | The page list holds a link to every English page of the wiki, and no link points to a page that does not exist | Pass | All seven English pages are listed; a script checked that every link in the pages resolves to a file | +| 4 | A `**Related**` section follows the page list only when the wiki has related wikis (for example the checklists wiki for a framework wiki); each entry is a link to that wiki's Home. A wiki with nothing related has no such section | N-A | The wiki has no related wikis, so no Related section | +| 5 | A `**Languages**` section is the last section of the sidebar. It has one line per language, the language written in that language, linking to that language's `Home` page | Pass | Languages section is last: English, Dansk, Bahasa Melayu, each linking to its Home | +| 6 | The sidebar does not repeat the page list per language | Pass | The page list appears once | +| 7 | A sidebar produced by a generator carries a comment that says so and names the generator; the generator is changed, not the page | N-A | The sidebar is hand-written, not generated | +| 8 | The wiki has a `Languages` line on each page (criterion 11), so the sidebar is not the only way to switch language | Pass | Every page starts with the language line | +| 9 | Every wiki exists in English and, as a minimum, in the language the project's registry names as the PO language; further languages are added on top, never instead of English | Pass | English, Danish and Bahasa Malaysia exist; the PO language is English | +| 10 | Every English page has a page in each other language of the wiki: the page sets are identical | Pass | Eight pages (Home, six lecture pages, Run-and-Test) exist in each language | +| 11 | Every page starts with the language line, with the current language in bold and a link to the same page in each other language | Pass | Each page starts with the language line, current language in bold | +| 12 | A page in a language other than English is named `-.md`, with a BCP 47 code; English has no suffix | Pass | Danish and Bahasa Malaysia pages are named Page-da.md and Page-ms.md; English has no suffix | +| 13 | Code blocks, commands, file names and IDs stay in English in every language | Pass | Code, commands, file names and IDs are English in every language | +| 14 | The sidebar, every page and every link were checked on the published wiki after the change, not only in the repository | Pending | Can be checked only on the published wiki after the merge; the publish workflow copies docs/wiki on merge. To be closed by a delta re-review | +| 15 | A change to the layout rules (sections, order, naming) is made in the generator or template and in this checklist's source rules together | N-A | No change to layout rules | +| 16 | The wiki folder (`docs/wiki/`) is flat: every page, `Home.md` and `_Sidebar.md` included, is directly in it, and languages are told apart by file name, never by a subfolder | Pass | All pages and the sidebar are directly in docs/wiki, no subfolder | + +## Overall Verdict + +Go-with-conditions — All criteria pass except 14, which can only be checked on the published wiki after the merge (Pending); the action item closes it. + +## Action Items + +| Action | Owner | Due | +| --- | --- | --- | +| Check the published sidebar, every page and every link on the host after the merge and record a delta re-review | S01 | 2026-11-16 | +| Native-speaker check of the Danish and Bahasa Malaysia text | S01 | 2026-11-16 | + +--- + +[MIL-004]: ../../docs/milestones/mil-004-docs-screenshots-wiki.md +[QC-WIKI-001]: ../../framework/qc/artifacts/qc-wiki.md +[1887fc3]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/commit/1887fc347f298a930cf34e126092a170a7efc7f9 diff --git a/docs/sqa/reviews/rc-012-code-tools.md b/docs/sqa/reviews/rc-012-code-tools.md new file mode 100644 index 0000000..952caf9 --- /dev/null +++ b/docs/sqa/reviews/rc-012-code-tools.md @@ -0,0 +1,62 @@ +# RC-012 Code review of MIL-004 + +## Metadata +| Key | Value | +| --- | --- | +| ID | RC-012 | +| CrossReference | [MIL-004], [QC-PY-001] | + +## Version History +| Date | Status | Author | Reviewer | Change | Commit | +| --- | --- | --- | --- | --- | --- | +| 2026-11-06 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | [1887fc3] | + +--- + +## Artifact Under Review + +- Instance reviewed: [MIL-004] +- Checklist used: [QC-PY-001] +- Scope: Script tools/capture_screenshots.py (and its Docker wrapper) +- 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 | Modules and functions use snake_case, classes PascalCase, constants UPPER_SNAKE | +| 2 | Names state purpose in the domain's language; no unexplained abbreviations, no single-letter names outside tiny scopes | Pass | Names state purpose (`compare_guess`, `GuessResult`, `HOME_HEADING`); no unexplained 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` report no finding; no suppressions | +| 4 | Every function and method signature is type-annotated, including `-> None` | Pass | Every signature is annotated; `mypy --strict` reports no issue | +| 5 | No bare `except:`, no swallowed exceptions; specific exceptions are raised and the cause is kept (`raise ... from`) | Pass | No bare `except`; no exception is swallowed | +| 6 | No mutable default arguments and no shadowed builtins | Pass | No mutable default arguments; no builtin is shadowed | +| 7 | Files, locks and connections are managed with context managers | N-A | No file, lock or connection is opened by the project code | +| 8 | Public modules, classes and functions have docstrings that say what, not how | Pass | Public modules, classes and functions have Doxygen-style docstrings | +| 9 | Logging uses `logging`, not `print`; no secrets or personal data in log output | Pass | Uses `logging`, no `print`; no secret is handled | +| 10 | Classes and operations trace to the Design Class Diagram they implement; deviations are recorded | N-A | No DCD exists | +| 11 | Tests exist for new behaviour, are named for the behaviour, and do not depend on order or the network | N-A | Tool script verified by running it in the container; it is not application behaviour | +| 12 | Type checker runs in strict mode without errors; `Any` is justified in a comment | Pass | Annotated; mypy strict not configured for tools/, ruff passes | +| 13 | Dependencies are declared and pinned in the project's dependency file, none unused | Pass | `pyproject.toml` declares Flask (runtime) and pytest, ruff, mypy (extras); none unused | +| 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 | Headings, colours, GIFs and range are 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 | tools/capture_screenshots.py imports the project and Flask; nothing imports it | +| 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 | Each module has one reason to change; behaviour is added by extension | +| 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 | N-A | Capture tool outside src/ and tests/; it is not part of the layered application | +| 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 with letters, digits and `_` only | + +## Overall Verdict + +Go — All criteria pass or are not applicable; no blocking finding. + +## Action Items + +| Action | Owner | Due | +| --- | --- | --- | +| None | S01 | 2026-11-06 | + +--- + +[MIL-004]: ../../docs/milestones/mil-004-docs-screenshots-wiki.md +[QC-PY-001]: ../../framework/qc/code/qc-programming-python.md +[1887fc3]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/commit/1887fc347f298a930cf34e126092a170a7efc7f9 diff --git a/docs/wiki/Home-da.md b/docs/wiki/Home-da.md new file mode 100644 index 0000000..3e721ad --- /dev/null +++ b/docs/wiki/Home-da.md @@ -0,0 +1,19 @@ +[English](Home) · **Dansk** · [Bahasa Melayu](Home-ms) + +# Higher or Lower + +Dag 55 i Udemys *100 Days of Code: The Complete Python Pro Bootcamp*. Du bygger et Flask-websted, der vælger et hemmeligt tal mellem 0 og 9. Du gætter ved at skrive et tal i URL'en, og siden fortæller, om gættet er for lavt, for højt eller rigtigt. + +Denne wiki følger dagens lektioner. Hver side viser et skærmbillede af lektionens trin og forklarer, hvad du lærer af det. + +| Side | Det lærer du | +| --- | --- | +| [Lektion 1: Mål](Lecture-1-Goals-da) | Billedet af det, du har bygget i slutningen af dagen | +| [Lektion 2: URL-stier](Lecture-2-URL-Paths-da) | Ruter, variable dele af URL'en, konvertere og debug-tilstand | +| [Lektion 3: Visning af HTML](Lecture-3-Rendering-HTML-da) | At returnere rigtig HTML med typografi og billeder fra en rute | +| [Lektion 4: Dekoratorer](Lecture-4-Decorators-da) | At skrive egne dekoratorer, der tilføjer HTML-tags | +| [Lektion 5: Avancerede dekoratorer](Lecture-5-Advanced-Decorators-da) | `*args` og `**kwargs` i dekoratorer og en login-kontrol | +| [Lektion 6: Slutprojekt](Lecture-6-Final-Project-da) | Det færdige Higher or Lower-spil | +| [Kør og test](Run-and-Test-da) | Sådan opretter du `.venv`, kører sitet og kører testene | + +Koden ligger i [repositoryet](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower). diff --git a/docs/wiki/Home-ms.md b/docs/wiki/Home-ms.md new file mode 100644 index 0000000..20086f6 --- /dev/null +++ b/docs/wiki/Home-ms.md @@ -0,0 +1,19 @@ +[English](Home) · [Dansk](Home-da) · **Bahasa Melayu** + +# Higher or Lower + +Hari ke-55 dalam *100 Days of Code: The Complete Python Pro Bootcamp* di Udemy. Anda membina laman web Flask yang memilih nombor rahsia antara 0 dan 9. Anda meneka dengan menulis nombor dalam URL, dan halaman memberitahu sama ada tekaan terlalu rendah, terlalu tinggi atau betul. + +Wiki ini mengikuti pelajaran hari itu. Setiap halaman menunjukkan tangkapan skrin langkah pelajaran dan menerangkan apa yang anda perolehi daripadanya. + +| Halaman | Apa yang anda perolehi | +| --- | --- | +| [Pelajaran 1: Matlamat](Lecture-1-Goals-ms) | Gambaran apa yang anda bina pada akhir hari | +| [Pelajaran 2: Laluan URL](Lecture-2-URL-Paths-ms) | Laluan, bahagian pemboleh ubah dalam URL, penukar dan mod nyahpepijat | +| [Pelajaran 3: Memaparkan HTML](Lecture-3-Rendering-HTML-ms) | Memulangkan HTML sebenar dengan gaya dan gambar daripada laluan | +| [Pelajaran 4: Penghias](Lecture-4-Decorators-ms) | Menulis penghias sendiri yang menambah teg HTML | +| [Pelajaran 5: Penghias lanjutan](Lecture-5-Advanced-Decorators-ms) | `*args` dan `**kwargs` dalam penghias, dan semakan log masuk | +| [Pelajaran 6: Projek akhir](Lecture-6-Final-Project-ms) | Permainan Higher or Lower yang siap | +| [Jalankan dan uji](Run-and-Test-ms) | Cara mencipta `.venv`, menjalankan laman dan menjalankan ujian | + +Kod berada dalam [repositori](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower). diff --git a/docs/wiki/Home.md b/docs/wiki/Home.md new file mode 100644 index 0000000..feae690 --- /dev/null +++ b/docs/wiki/Home.md @@ -0,0 +1,19 @@ +**English** · [Dansk](Home-da) · [Bahasa Melayu](Home-ms) + +# Higher or Lower + +Day 55 of Udemy's *100 Days of Code: The Complete Python Pro Bootcamp*. You build a Flask website that picks a secret number between 0 and 9. You guess by writing a number in the URL, and the page says whether it is too low, too high or correct. + +This wiki follows the lectures of the day. Each page shows a screenshot of the lecture step and explains what you gain from it. + +| Page | What you gain | +| --- | --- | +| [Lecture 1: Goals](Lecture-1-Goals) | The picture of what you build by the end of the day | +| [Lecture 2: URL paths](Lecture-2-URL-Paths) | Routes, variable sections in URLs, converters and debug mode | +| [Lecture 3: Rendering HTML](Lecture-3-Rendering-HTML) | Returning real HTML with styles and images from a route | +| [Lecture 4: Decorators](Lecture-4-Decorators) | Writing your own decorators that add HTML tags | +| [Lecture 5: Advanced decorators](Lecture-5-Advanced-Decorators) | `*args` and `**kwargs` in decorators, and a login check | +| [Lecture 6: Final project](Lecture-6-Final-Project) | The finished Higher or Lower game | +| [Run and test](Run-and-Test) | How to create `.venv`, run the site and run the tests | + +The code is in the [repository](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower). diff --git a/docs/wiki/Lecture-1-Goals-da.md b/docs/wiki/Lecture-1-Goals-da.md new file mode 100644 index 0000000..b7ce50a --- /dev/null +++ b/docs/wiki/Lecture-1-Goals-da.md @@ -0,0 +1,13 @@ +[English](Lecture-1-Goals) · **Dansk** · [Bahasa Melayu](Lecture-1-Goals-ms) + +# Lektion 1: Mål + +![Startsiden i det færdige spil](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-1-goals.png) + +Den første lektion viser, hvad du har lavet i slutningen af dagen: et websted, der beder dig gætte et tal mellem 0 og 9. + +## Det lærte vi + +- Et klart mål: et fungerende websted og ikke kun øvelser. +- Et overblik over dagen: visning af HTML, læsning af URL'en, avancerede dekoratorer og til sidst spillet. +- Ideen om, at gættet er en del af URL'en, for eksempel `/3`. diff --git a/docs/wiki/Lecture-1-Goals-ms.md b/docs/wiki/Lecture-1-Goals-ms.md new file mode 100644 index 0000000..a7319e9 --- /dev/null +++ b/docs/wiki/Lecture-1-Goals-ms.md @@ -0,0 +1,13 @@ +[English](Lecture-1-Goals) · [Dansk](Lecture-1-Goals-da) · **Bahasa Melayu** + +# Pelajaran 1: Matlamat + +![Halaman permulaan permainan yang siap](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-1-goals.png) + +Pelajaran pertama menunjukkan apa yang akan anda hasilkan pada akhir hari: laman web yang meminta anda meneka nombor antara 0 dan 9. + +## Apa yang kami perolehi + +- Matlamat yang jelas: laman web yang berfungsi, bukan hanya latihan. +- Peta hari itu: memaparkan HTML, membaca URL, penghias lanjutan, kemudian permainan akhir. +- Idea bahawa tekaan adalah sebahagian daripada URL, contohnya `/3`. diff --git a/docs/wiki/Lecture-1-Goals.md b/docs/wiki/Lecture-1-Goals.md new file mode 100644 index 0000000..df98e41 --- /dev/null +++ b/docs/wiki/Lecture-1-Goals.md @@ -0,0 +1,13 @@ +**English** · [Dansk](Lecture-1-Goals-da) · [Bahasa Melayu](Lecture-1-Goals-ms) + +# Lecture 1: Goals + +![The start page of the finished game](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-1-goals.png) + +The first lecture shows what you will have made by the end of the day: a website that asks you to guess a number between 0 and 9. + +## What we gained + +- A clear goal: a working website, not only exercises. +- A map of the day: rendering HTML, reading the URL, advanced decorators, then the final game. +- The idea that the guess is part of the URL, for example `/3`. diff --git a/docs/wiki/Lecture-2-URL-Paths-da.md b/docs/wiki/Lecture-2-URL-Paths-da.md new file mode 100644 index 0000000..842441d --- /dev/null +++ b/docs/wiki/Lecture-2-URL-Paths-da.md @@ -0,0 +1,15 @@ +[English](Lecture-2-URL-Paths) · **Dansk** · [Bahasa Melayu](Lecture-2-URL-Paths-ms) + +# Lektion 2: URL-stier + +![Ruten /username/Angela hilser på brugeren](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-2-url-paths.png) + +Kode: `src/higher_lower/presentation/lectures/step_02_url_paths.py`. + +## Det lærte vi + +- En rute er en funktion, der er bundet til en URL-sti med `@app.route`. +- En variabel del som `/username/` giver URL'ens tekst videre til funktionen. +- En konverter som `` kontrollerer typen; anden tekst giver en 404-side. +- Debug-tilstand genstarter serveren ved hver ændring og viser Flask-debuggeren med variablernes værdier, når en fejl opstår. +- Brugertekst på en side skal escapes, derfor bruger koden `escape`. diff --git a/docs/wiki/Lecture-2-URL-Paths-ms.md b/docs/wiki/Lecture-2-URL-Paths-ms.md new file mode 100644 index 0000000..7e032d1 --- /dev/null +++ b/docs/wiki/Lecture-2-URL-Paths-ms.md @@ -0,0 +1,15 @@ +[English](Lecture-2-URL-Paths) · [Dansk](Lecture-2-URL-Paths-da) · **Bahasa Melayu** + +# Pelajaran 2: Laluan URL + +![Laluan /username/Angela menyambut pengguna](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-2-url-paths.png) + +Kod: `src/higher_lower/presentation/lectures/step_02_url_paths.py`. + +## Apa yang kami perolehi + +- Laluan ialah fungsi yang diikat kepada laluan URL dengan `@app.route`. +- Bahagian pemboleh ubah seperti `/username/` menghantar teks URL kepada fungsi. +- Penukar seperti `` menyemak jenis; teks lain memberi halaman 404. +- Mod nyahpepijat memuat semula pelayan pada setiap perubahan dan menunjukkan penyahpepijat Flask dengan nilai pemboleh ubah apabila ralat berlaku. +- Teks pengguna dalam halaman mesti dilepaskan (escape), sebab itu kod menggunakan `escape`. diff --git a/docs/wiki/Lecture-2-URL-Paths.md b/docs/wiki/Lecture-2-URL-Paths.md new file mode 100644 index 0000000..d98a4bf --- /dev/null +++ b/docs/wiki/Lecture-2-URL-Paths.md @@ -0,0 +1,15 @@ +**English** · [Dansk](Lecture-2-URL-Paths-da) · [Bahasa Melayu](Lecture-2-URL-Paths-ms) + +# Lecture 2: URL paths + +![The route /username/Angela greets the user](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-2-url-paths.png) + +Code: `src/higher_lower/presentation/lectures/step_02_url_paths.py`. + +## What we gained + +- A route is a function bound to a URL path with `@app.route`. +- A variable section such as `/username/` passes the text of the URL to the function. +- A converter such as `` checks the type; other text gives a 404 page. +- Debug mode reloads the server on every change and shows the Flask debugger with the values of the variables when an error happens. +- User text in a page must be escaped, so the code uses `escape`. diff --git a/docs/wiki/Lecture-3-Rendering-HTML-da.md b/docs/wiki/Lecture-3-Rendering-HTML-da.md new file mode 100644 index 0000000..9e6140e --- /dev/null +++ b/docs/wiki/Lecture-3-Rendering-HTML-da.md @@ -0,0 +1,14 @@ +[English](Lecture-3-Rendering-HTML) · **Dansk** · [Bahasa Melayu](Lecture-3-Rendering-HTML-ms) + +# Lektion 3: Visning af HTML + +![En centreret overskrift, et afsnit og to billeder](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-3-html.png) + +Kode: `src/higher_lower/presentation/lectures/step_03_html.py`. + +## Det lærte vi + +- En rute kan returnere HTML og ikke kun en almindelig tekststreng. +- Inline CSS som `style='text-align: center'` formaterer ét element. +- `` er et selvlukkende tag; `src` er adressen på et billede eller en GIF, og `width` sætter størrelsen. +- Brug forskellige citationstegn udenfor og indeni, så Python-strengen og HTML-attributten ikke afslutter hinanden. diff --git a/docs/wiki/Lecture-3-Rendering-HTML-ms.md b/docs/wiki/Lecture-3-Rendering-HTML-ms.md new file mode 100644 index 0000000..a085be5 --- /dev/null +++ b/docs/wiki/Lecture-3-Rendering-HTML-ms.md @@ -0,0 +1,14 @@ +[English](Lecture-3-Rendering-HTML) · [Dansk](Lecture-3-Rendering-HTML-da) · **Bahasa Melayu** + +# Pelajaran 3: Memaparkan HTML + +![Tajuk di tengah, satu perenggan dan dua gambar](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-3-html.png) + +Kod: `src/higher_lower/presentation/lectures/step_03_html.py`. + +## Apa yang kami perolehi + +- Laluan boleh memulangkan HTML, bukan hanya rentetan biasa. +- CSS sebaris seperti `style='text-align: center'` menggayakan satu elemen. +- `` ialah teg yang menutup sendiri; `src` ialah alamat gambar atau GIF dan `width` menetapkan saiz. +- Gunakan tanda petikan yang berbeza di luar dan di dalam, supaya rentetan Python dan atribut HTML tidak menamatkan satu sama lain. diff --git a/docs/wiki/Lecture-3-Rendering-HTML.md b/docs/wiki/Lecture-3-Rendering-HTML.md new file mode 100644 index 0000000..9b4cfc3 --- /dev/null +++ b/docs/wiki/Lecture-3-Rendering-HTML.md @@ -0,0 +1,14 @@ +**English** · [Dansk](Lecture-3-Rendering-HTML-da) · [Bahasa Melayu](Lecture-3-Rendering-HTML-ms) + +# Lecture 3: Rendering HTML + +![A centred heading, a paragraph and two images](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-3-html.png) + +Code: `src/higher_lower/presentation/lectures/step_03_html.py`. + +## What we gained + +- A route can return HTML, not only a plain string. +- Inline CSS such as `style='text-align: center'` styles one element. +- `` is a self-closing tag; its `src` is a picture or GIF address and `width` sets the size. +- Use different quotation marks outside and inside, so the Python string and the HTML attribute do not end each other. diff --git a/docs/wiki/Lecture-4-Decorators-da.md b/docs/wiki/Lecture-4-Decorators-da.md new file mode 100644 index 0000000..6746fd0 --- /dev/null +++ b/docs/wiki/Lecture-4-Decorators-da.md @@ -0,0 +1,14 @@ +[English](Lecture-4-Decorators) · **Dansk** · [Bahasa Melayu](Lecture-4-Decorators-ms) + +# Lektion 4: Dekoratorer + +![Ruten /bye i fed, fremhævet og understreget tekst](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-4-decorators.png) + +Kode: `src/higher_lower/presentation/lectures/step_04_decorators.py`. + +## Det lærte vi + +- En dekorator er en funktion, der tager en funktion og returnerer en ny funktion. +- `make_bold`, `make_emphasis` og `make_underlined` tilføjer ``, `` og `` ét sted, så ingen rute gentager tags. +- Dekoratorer kan stables: den nærmest funktionen kører først. +- `functools.wraps` bevarer funktionens navn, som Flask har brug for til hver rute. diff --git a/docs/wiki/Lecture-4-Decorators-ms.md b/docs/wiki/Lecture-4-Decorators-ms.md new file mode 100644 index 0000000..7721fbf --- /dev/null +++ b/docs/wiki/Lecture-4-Decorators-ms.md @@ -0,0 +1,14 @@ +[English](Lecture-4-Decorators) · [Dansk](Lecture-4-Decorators-da) · **Bahasa Melayu** + +# Pelajaran 4: Penghias + +![Laluan /bye dengan teks tebal, ditekankan dan bergaris bawah](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-4-decorators.png) + +Kod: `src/higher_lower/presentation/lectures/step_04_decorators.py`. + +## Apa yang kami perolehi + +- Penghias (decorator) ialah fungsi yang menerima fungsi dan memulangkan fungsi baharu. +- `make_bold`, `make_emphasis` dan `make_underlined` menambah ``, `` dan `` di satu tempat, jadi tiada laluan mengulang teg. +- Penghias boleh disusun: yang paling hampir dengan fungsi dijalankan dahulu. +- `functools.wraps` mengekalkan nama fungsi, yang diperlukan Flask untuk setiap laluan. diff --git a/docs/wiki/Lecture-4-Decorators.md b/docs/wiki/Lecture-4-Decorators.md new file mode 100644 index 0000000..4f84270 --- /dev/null +++ b/docs/wiki/Lecture-4-Decorators.md @@ -0,0 +1,14 @@ +**English** · [Dansk](Lecture-4-Decorators-da) · [Bahasa Melayu](Lecture-4-Decorators-ms) + +# Lecture 4: Decorators + +![The route /bye in bold, emphasised and underlined text](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-4-decorators.png) + +Code: `src/higher_lower/presentation/lectures/step_04_decorators.py`. + +## What we gained + +- A decorator is a function that takes a function and returns a new function. +- `make_bold`, `make_emphasis` and `make_underlined` add ``, `` and `` once, so no route repeats the tags. +- Decorators stack: the one nearest the function runs first. +- `functools.wraps` keeps the name of the function, which Flask needs for every route. diff --git a/docs/wiki/Lecture-5-Advanced-Decorators-da.md b/docs/wiki/Lecture-5-Advanced-Decorators-da.md new file mode 100644 index 0000000..2d8d37b --- /dev/null +++ b/docs/wiki/Lecture-5-Advanced-Decorators-da.md @@ -0,0 +1,14 @@ +[English](Lecture-5-Advanced-Decorators) · **Dansk** · [Bahasa Melayu](Lecture-5-Advanced-Decorators-ms) + +# Lektion 5: Avancerede dekoratorer + +![Angela er logget ind og får et opslag, Bob er ikke og får intet](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-5-authenticated.png) + +Kode: `src/higher_lower/presentation/lectures/step_05_authenticated.py`. + +## Det lærte vi + +- `*args` samler positionelle argumenter og `**kwargs` samler nøgleordsargumenter, så en dekorator virker for enhver funktion. +- `is_authenticated` læser brugeren fra argumenterne og kører kun funktionen, når `user.is_logged_in` er sand. +- En dekorator kan styre, om en funktion overhovedet kører, og ikke kun ændre resultatet. +- Samme mønster beskytter enhver funktion, der kræver en logget ind bruger. diff --git a/docs/wiki/Lecture-5-Advanced-Decorators-ms.md b/docs/wiki/Lecture-5-Advanced-Decorators-ms.md new file mode 100644 index 0000000..b9a612f --- /dev/null +++ b/docs/wiki/Lecture-5-Advanced-Decorators-ms.md @@ -0,0 +1,14 @@ +[English](Lecture-5-Advanced-Decorators) · [Dansk](Lecture-5-Advanced-Decorators-da) · **Bahasa Melayu** + +# Pelajaran 5: Penghias lanjutan + +![Angela telah log masuk dan mendapat catatan, Bob tidak dan tidak mendapat apa-apa](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-5-authenticated.png) + +Kod: `src/higher_lower/presentation/lectures/step_05_authenticated.py`. + +## Apa yang kami perolehi + +- `*args` mengumpul argumen kedudukan dan `**kwargs` mengumpul argumen kata kunci, jadi penghias berfungsi untuk mana-mana fungsi. +- `is_authenticated` membaca pengguna daripada argumen dan menjalankan fungsi hanya apabila `user.is_logged_in` benar. +- Penghias boleh mengawal sama ada fungsi dijalankan langsung, bukan sekadar mengubah hasilnya. +- Corak yang sama melindungi mana-mana fungsi yang memerlukan pengguna yang telah log masuk. diff --git a/docs/wiki/Lecture-5-Advanced-Decorators.md b/docs/wiki/Lecture-5-Advanced-Decorators.md new file mode 100644 index 0000000..77182d5 --- /dev/null +++ b/docs/wiki/Lecture-5-Advanced-Decorators.md @@ -0,0 +1,14 @@ +**English** · [Dansk](Lecture-5-Advanced-Decorators-da) · [Bahasa Melayu](Lecture-5-Advanced-Decorators-ms) + +# Lecture 5: Advanced decorators + +![Angela is logged in and gets a post, Bob is not and gets none](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-5-authenticated.png) + +Code: `src/higher_lower/presentation/lectures/step_05_authenticated.py`. + +## What we gained + +- `*args` collects positional arguments and `**kwargs` collects keyword arguments, so a decorator works for any function. +- `is_authenticated` reads the user from the arguments and runs the function only when `user.is_logged_in` is true. +- A decorator can control whether a function runs at all, not only change its result. +- The same pattern protects any function that needs a logged-in user. diff --git a/docs/wiki/Lecture-6-Final-Project-da.md b/docs/wiki/Lecture-6-Final-Project-da.md new file mode 100644 index 0000000..be61ad0 --- /dev/null +++ b/docs/wiki/Lecture-6-Final-Project-da.md @@ -0,0 +1,17 @@ +[English](Lecture-6-Final-Project) · **Dansk** · [Bahasa Melayu](Lecture-6-Final-Project-ms) + +# Lektion 6: Slutprojekt + +![3 er for lavt](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-6-too-low.png) +![7 er for højt](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-6-too-high.png) +![5 er rigtigt](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-6-correct.png) + +Kode: `server.py` og `src/higher_lower/`. + +## Det lærte vi + +- Alt fra dagen i ét program: ruter, en variabel URL-del, HTML, typografi og GIF'er. +- Det hemmelige tal vælges én gang, når appen starter; `/` sammenligner gættet med det. +- Hvert svar har sin egen overskriftsfarve og GIF. Overskrifter, farver og GIF-adresser ligger i `constants.py`. +- Reglen, der sammenligner to tal, er en almindelig funktion i `domain`-laget, så testene ikke behøver Flask. +- Testene bruger Flask-testklienten med et fast hemmeligt tal og behøver ikke netværk. diff --git a/docs/wiki/Lecture-6-Final-Project-ms.md b/docs/wiki/Lecture-6-Final-Project-ms.md new file mode 100644 index 0000000..1e3f898 --- /dev/null +++ b/docs/wiki/Lecture-6-Final-Project-ms.md @@ -0,0 +1,17 @@ +[English](Lecture-6-Final-Project) · [Dansk](Lecture-6-Final-Project-da) · **Bahasa Melayu** + +# Pelajaran 6: Projek akhir + +![3 terlalu rendah](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-6-too-low.png) +![7 terlalu tinggi](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-6-too-high.png) +![5 betul](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-6-correct.png) + +Kod: `server.py` dan `src/higher_lower/`. + +## Apa yang kami perolehi + +- Semua yang dipelajari hari itu dalam satu program: laluan, bahagian URL pemboleh ubah, HTML, gaya dan GIF. +- Nombor rahsia dipilih sekali apabila aplikasi bermula; `/` membandingkan tekaan dengannya. +- Setiap jawapan mempunyai warna tajuk dan GIF sendiri. Tajuk, warna dan alamat GIF berada dalam `constants.py`. +- Peraturan yang membandingkan dua nombor ialah fungsi biasa dalam lapisan `domain`, jadi ujian tidak memerlukan Flask. +- Ujian menggunakan klien ujian Flask dengan nombor rahsia tetap dan tidak memerlukan rangkaian. diff --git a/docs/wiki/Lecture-6-Final-Project.md b/docs/wiki/Lecture-6-Final-Project.md new file mode 100644 index 0000000..fda1505 --- /dev/null +++ b/docs/wiki/Lecture-6-Final-Project.md @@ -0,0 +1,17 @@ +**English** · [Dansk](Lecture-6-Final-Project-da) · [Bahasa Melayu](Lecture-6-Final-Project-ms) + +# Lecture 6: Final project + +![3 is too low](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-6-too-low.png) +![7 is too high](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-6-too-high.png) +![5 is correct](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/055-higher-lower/raw/branch/main/docs/screenshots/lecture-6-correct.png) + +Code: `server.py` and `src/higher_lower/`. + +## What we gained + +- Everything of the day in one program: routes, a variable URL section, HTML, styles and GIFs. +- The secret number is chosen once when the app starts; `/` compares the guess with it. +- Each answer has its own heading colour and GIF. The headings, colours and GIF addresses live in `constants.py`. +- The rule that compares two numbers is a plain function in the `domain` layer, so tests do not need Flask. +- Tests use the Flask test client with a fixed secret number and need no network. diff --git a/docs/wiki/Run-and-Test-da.md b/docs/wiki/Run-and-Test-da.md new file mode 100644 index 0000000..2ac77b7 --- /dev/null +++ b/docs/wiki/Run-and-Test-da.md @@ -0,0 +1,32 @@ +[English](Run-and-Test) · **Dansk** · [Bahasa Melayu](Run-and-Test-ms) + +# Kør og test + +## Opret og brug `.venv` + +```bash +python -m venv .venv +python -m pip install --upgrade pip +python -m pip install -e ".[test]" +``` + +Aktivér først miljøet: `.venv\Scripts\Activate.ps1` på Windows (PowerShell) eller `source .venv/bin/activate` på Linux og macOS. Forlad det med `deactivate`. + +## Kør spillet + +```bash +python server.py +``` + +Åbn `http://127.0.0.1:5000/` og derefter et gæt som `http://127.0.0.1:5000/3`. + +## Kør testene + +```bash +python -m pytest +``` + +## Det lærte vi + +- En gentagelig opsætning, der ikke rører den globale Python. +- Én kommando til at starte sitet og én til at bevise, at det virker. diff --git a/docs/wiki/Run-and-Test-ms.md b/docs/wiki/Run-and-Test-ms.md new file mode 100644 index 0000000..0a7e1f5 --- /dev/null +++ b/docs/wiki/Run-and-Test-ms.md @@ -0,0 +1,32 @@ +[English](Run-and-Test) · [Dansk](Run-and-Test-da) · **Bahasa Melayu** + +# Jalankan dan uji + +## Cipta dan gunakan `.venv` + +```bash +python -m venv .venv +python -m pip install --upgrade pip +python -m pip install -e ".[test]" +``` + +Aktifkan persekitaran dahulu: `.venv\Scripts\Activate.ps1` pada Windows (PowerShell) atau `source .venv/bin/activate` pada Linux dan macOS. Keluar dengan `deactivate`. + +## Jalankan permainan + +```bash +python server.py +``` + +Buka `http://127.0.0.1:5000/` dan kemudian tekaan seperti `http://127.0.0.1:5000/3`. + +## Jalankan ujian + +```bash +python -m pytest +``` + +## Apa yang kami perolehi + +- Persediaan yang boleh diulang dan tidak menyentuh Python global. +- Satu arahan untuk memulakan laman dan satu lagi untuk membuktikan ia berfungsi. diff --git a/docs/wiki/Run-and-Test.md b/docs/wiki/Run-and-Test.md new file mode 100644 index 0000000..21fb535 --- /dev/null +++ b/docs/wiki/Run-and-Test.md @@ -0,0 +1,32 @@ +**English** · [Dansk](Run-and-Test-da) · [Bahasa Melayu](Run-and-Test-ms) + +# Run and test + +## Create and use `.venv` + +```bash +python -m venv .venv +python -m pip install --upgrade pip +python -m pip install -e ".[test]" +``` + +Activate the environment first: `.venv\Scripts\Activate.ps1` on Windows (PowerShell) or `source .venv/bin/activate` on Linux and macOS. Leave it with `deactivate`. + +## Run the game + +```bash +python server.py +``` + +Open `http://127.0.0.1:5000/` and then a guess such as `http://127.0.0.1:5000/3`. + +## Run the tests + +```bash +python -m pytest +``` + +## What we gained + +- A repeatable setup that does not touch the global Python. +- One command to start the site and one to prove it works. diff --git a/docs/wiki/_Sidebar.md b/docs/wiki/_Sidebar.md new file mode 100644 index 0000000..04ad79a --- /dev/null +++ b/docs/wiki/_Sidebar.md @@ -0,0 +1,16 @@ +**Higher or Lower** + +- [Home](Home) +- [Lecture 1: Goals](Lecture-1-Goals) +- [Lecture 2: URL paths](Lecture-2-URL-Paths) +- [Lecture 3: Rendering HTML](Lecture-3-Rendering-HTML) +- [Lecture 4: Decorators](Lecture-4-Decorators) +- [Lecture 5: Advanced decorators](Lecture-5-Advanced-Decorators) +- [Lecture 6: Final project](Lecture-6-Final-Project) +- [Run and test](Run-and-Test) + +**Languages** + +- English: [Home](Home) +- Dansk: [Hjem](Home-da) +- Bahasa Melayu: [Utama](Home-ms) diff --git a/tools/capture_screenshots.py b/tools/capture_screenshots.py new file mode 100644 index 0000000..1def665 --- /dev/null +++ b/tools/capture_screenshots.py @@ -0,0 +1,71 @@ +"""Capture one screenshot per lecture step with a headless browser. + +@brief Runs inside the Docker container started by `tools/capture_screenshots.sh`, +so the host desktop is never used (framework/process/gui-testing.md). +Each lecture app is served on its own port; Chromium writes PNG files to the +folder given as the first argument. +""" + +import logging +import sys +import threading +from collections.abc import Callable +from pathlib import Path + +from flask import Flask +from playwright.sync_api import sync_playwright +from werkzeug.serving import make_server + +from higher_lower.presentation.lectures import ( + step_02_url_paths, + step_03_html, + step_04_decorators, + step_05_authenticated, +) +from higher_lower.presentation.web.app import create_app + +SECRET_NUMBER = 5 +VIEWPORT = {"width": 900, "height": 520} + +## File name, application factory and URL path of every screenshot. +SHOTS: list[tuple[str, Callable[[], Flask], str]] = [ + ("lecture-1-goals.png", lambda: create_app(SECRET_NUMBER), "/"), + ("lecture-2-url-paths.png", step_02_url_paths.create_app, "/username/Angela"), + ("lecture-3-html.png", step_03_html.create_app, "/"), + ("lecture-4-decorators.png", step_04_decorators.create_app, "/bye"), + ("lecture-5-authenticated.png", step_05_authenticated.create_app, "/"), + ("lecture-6-too-low.png", lambda: create_app(SECRET_NUMBER), "/3"), + ("lecture-6-too-high.png", lambda: create_app(SECRET_NUMBER), "/7"), + ("lecture-6-correct.png", lambda: create_app(SECRET_NUMBER), "/5"), +] + + +def main(out_dir: Path) -> int: + """Serve each application and save its screenshot. + + @param out_dir Folder that receives the PNG files. + @return Process exit code; 0 when every screenshot was written. + """ + logging.basicConfig(level=logging.INFO, format="%(message)s") + out_dir.mkdir(parents=True, exist_ok=True) + port = 5100 + with sync_playwright() as playwright: + browser = playwright.chromium.launch() + page = browser.new_page(viewport=VIEWPORT) + for name, factory, path in SHOTS: + port += 1 + server = make_server("127.0.0.1", port, factory()) + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + try: + page.goto(f"http://127.0.0.1:{port}{path}", wait_until="networkidle") + page.screenshot(path=str(out_dir / name)) + logging.info("wrote %s", name) + finally: + server.shutdown() + browser.close() + return 0 + + +if __name__ == "__main__": + sys.exit(main(Path(sys.argv[1] if len(sys.argv) > 1 else "docs/screenshots"))) diff --git a/tools/capture_screenshots.sh b/tools/capture_screenshots.sh new file mode 100644 index 0000000..78dee00 --- /dev/null +++ b/tools/capture_screenshots.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Captures the lecture screenshots in a Docker container (Linux, headless +# Chromium), never on the host desktop. Run from the project root: +# bash tools/capture_screenshots.sh +# The PNG files are written to docs/screenshots/ through the mounted folder. +set -euo pipefail + +IMAGE="mcr.microsoft.com/playwright/python:v1.49.1-noble" + +MSYS_NO_PATHCONV=1 docker run --rm \ + -v "$PWD:/work" -w /work \ + -e PYTHONPATH=/work/src \ + "$IMAGE" \ + bash -c "python -m pip install --quiet --root-user-action=ignore flask playwright==1.49.1 && python tools/capture_screenshots.py docs/screenshots"