From 64c2b7f0fd328644bae51922a4a0a02302349722 Mon Sep 17 00:00:00 2001 From: dschlueter Date: Fri, 29 May 2026 19:06:36 +0200 Subject: [PATCH] feat: Demo-Examples (Python/Rust/Go/C) mit Protokoll-Templates und Restore-Skript Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 7 ++ examples/README.md | 74 +++++++++++++++++++ examples/c-linkedlist/PROTOKOLL.md | 35 +++++++++ examples/c-linkedlist/README.md | 48 ++++++++++++ examples/c-linkedlist/linked_list.c | 43 +++++++++++ examples/c-linkedlist/linked_list.h | 16 ++++ examples/c-linkedlist/main.c | 16 ++++ examples/go-fibonacci/PROTOKOLL.md | 37 ++++++++++ examples/go-fibonacci/README.md | 48 ++++++++++++ examples/go-fibonacci/go.mod | 3 + examples/go-fibonacci/main.go | 18 +++++ examples/go-fibonacci/main_test.go | 20 +++++ examples/python-calculator/PROTOKOLL.md | 19 +++++ examples/python-calculator/README.md | 51 +++++++++++++ examples/python-calculator/calculator.py | 11 +++ examples/python-calculator/test_calculator.py | 18 +++++ examples/restore-all.sh | 31 ++++++++ examples/rust-wordcount/Cargo.toml | 4 + examples/rust-wordcount/PROTOKOLL.md | 29 ++++++++ examples/rust-wordcount/README.md | 50 +++++++++++++ examples/rust-wordcount/src/main.rs | 36 +++++++++ 21 files changed, 614 insertions(+) create mode 100644 examples/README.md create mode 100644 examples/c-linkedlist/PROTOKOLL.md create mode 100644 examples/c-linkedlist/README.md create mode 100644 examples/c-linkedlist/linked_list.c create mode 100644 examples/c-linkedlist/linked_list.h create mode 100644 examples/c-linkedlist/main.c create mode 100644 examples/go-fibonacci/PROTOKOLL.md create mode 100644 examples/go-fibonacci/README.md create mode 100644 examples/go-fibonacci/go.mod create mode 100644 examples/go-fibonacci/main.go create mode 100644 examples/go-fibonacci/main_test.go create mode 100644 examples/python-calculator/PROTOKOLL.md create mode 100644 examples/python-calculator/README.md create mode 100644 examples/python-calculator/calculator.py create mode 100644 examples/python-calculator/test_calculator.py create mode 100755 examples/restore-all.sh create mode 100644 examples/rust-wordcount/Cargo.toml create mode 100644 examples/rust-wordcount/PROTOKOLL.md create mode 100644 examples/rust-wordcount/README.md create mode 100644 examples/rust-wordcount/src/main.rs diff --git a/.gitignore b/.gitignore index c99ffd2..59fc5e2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ *.bak node_modules/ .DS_Store + +# Build-Artefakte (Examples) +examples/rust-wordcount/target/ +examples/**/__pycache__/ +examples/**/.pytest_cache/ +examples/**/Cargo.lock +examples/**/ll_demo diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..93f647c --- /dev/null +++ b/examples/README.md @@ -0,0 +1,74 @@ +# pi-coder Beispielprojekte + +Vier kleine, eigenständige Projekte als Demonstrationsgrundlage für die pi-coder-Features. +Jedes Projekt startet bewusst unvollständig — genau der Ausgangspunkt, für den pi-coder gebaut ist. + +## Übersicht + +| Verzeichnis | Sprache | Demonstriert | +|---|---|---| +| `python-calculator/` | Python | `/optimize` mit `--test-cmd pytest` | +| `rust-wordcount/` | Rust | `/optimize` mit `--test-cmd "cargo test"` + `/version` | +| `go-fibonacci/` | Go | `/optimize --interactive` + `/continue` + `/shipit` | +| `c-linkedlist/` | C | `/quick_check` + `/fix` + `/patch` | + +## Demo-Workflow + +### Schritt 1 — Vorbereitung: Sub-Repos anlegen + +Jedes Example braucht ein eigenes git-Repo, damit pi-coder commit-basierte +Features nutzen kann (Loop-Erkennung, Diff-Anzeige, `/version`): + +```bash +for dir in python-calculator rust-wordcount go-fibonacci c-linkedlist; do + cd examples/$dir + git init && git add -A && git commit -m "feat: initial $dir" + cd ../.. +done +``` + +Für `/version` im rust-wordcount-Beispiel zusätzlich: + +```bash +cd examples/rust-wordcount && git tag v0.1.0 +``` + +### Schritt 2 — Demo ausführen + +In pi das jeweilige Unterverzeichnis als Arbeitsverzeichnis öffnen. +Die genauen Befehle stehen im README.md des jeweiligen Examples. +Zeitmessung: Systemuhr notieren oder Terminal-Kommando `time` nutzen. + +### Schritt 3 — Protokoll ausfüllen + +Jedes Example enthält eine `PROTOKOLL.md`. +Startzeit, Endzeit, Rundenanzahl und Endergebnis eintragen. + +### Schritt 4 — Ausgangszustand wiederherstellen + +```bash +bash examples/restore-all.sh +``` + +Das Skript löscht Sub-Repos, restauriert alle Quelldateien aus dem Haupt-Repo +und bereinigt Build-Artefakte (`target/`, `__pycache__` etc.). + +--- + +## Empfohlene Demo-Reihenfolge + +| # | Beispiel | Geschätzte Dauer | Highlights | +|---|---|---|---| +| 1 | `python-calculator` | ~5–10 min | Einstieg, Test-Loop | +| 2 | `c-linkedlist` | ~5 min | `/quick_check` + `/fix`, kein Loop | +| 3 | `rust-wordcount` | ~10–15 min | Loop + `/version` | +| 4 | `go-fibonacci` | ~15–20 min | `--interactive` + `/shipit` | + +--- + +## Weitere Details + +[python-calculator](python-calculator/README.md) · +[rust-wordcount](rust-wordcount/README.md) · +[go-fibonacci](go-fibonacci/README.md) · +[c-linkedlist](c-linkedlist/README.md) diff --git a/examples/c-linkedlist/PROTOKOLL.md b/examples/c-linkedlist/PROTOKOLL.md new file mode 100644 index 0000000..939ddd9 --- /dev/null +++ b/examples/c-linkedlist/PROTOKOLL.md @@ -0,0 +1,35 @@ +# Demo-Protokoll: c-linkedlist + +## Lauf 1 + +**Datum:** + +**Befehl /quick_check:** +``` +/quick_check "Gibt es Speicherlecks oder sonstige Probleme in diesem C-Projekt?" +``` +**Startzeit:** +**Endzeit:** +**Dauer (min):** +**Ergebnis:** OK / PROBLEM (Kurzbeschreibung): + +**Befehl /fix:** +``` +/fix "Implementiere list_free() korrekt, sodass valgrind --leak-check=full sauber ist." +``` +**Startzeit:** +**Endzeit:** +**Dauer (min):** +**Ergebnis:** erledigt / fehlgeschlagen + +**Befehl /patch (optional):** +``` +/patch "Ergänze list_search(head, value) in Header und Implementierung. + Gibt den ersten Node* mit dem gesuchten Wert zurück, oder NULL." +``` +**Startzeit:** +**Endzeit:** +**Dauer (min):** +**Besonderheiten / Beobachtungen:** + +--- diff --git a/examples/c-linkedlist/README.md b/examples/c-linkedlist/README.md new file mode 100644 index 0000000..7861652 --- /dev/null +++ b/examples/c-linkedlist/README.md @@ -0,0 +1,48 @@ +# C Linked List + +Vollständige einfach-verkettete Liste — bis auf `list_free()`, das als leerer Stub vorliegt. +Jeder Programmlauf leckt den gesamten Listen-Speicher. + +## Aktueller Stand + +``` +linked_list.h Interface: node_new, list_prepend, list_append, list_print, list_free, list_length +linked_list.c Alles implementiert — außer list_free() (Stub, tut nichts) +main.c Baut Liste 1–5, gibt sie aus, ruft list_free() auf (ohne Wirkung) +``` + +## Demo 1: `/quick_check` als Diagnose + +``` +/quick_check "Gibt es Speicherlecks oder sonstige Probleme in diesem C-Projekt?" +``` + +Der Judge analysiert den Code und identifiziert das leere `list_free()` als Speicherleck-Quelle. + +## Demo 2: `/fix` für gezieltes Nacharbeiten + +``` +/fix "Implementiere list_free() korrekt, sodass valgrind --leak-check=full sauber ist." +``` + +Coder implementiert die Funktion, committet. Kein vollständiger Judge-Loop — +ideal für kleine, klar abgegrenzte Fixes. + +## Demo 3: `/patch` für Minimal-Erweiterungen + +``` +/patch "Ergänze list_search(head, value) in Header und Implementierung. + Gibt den ersten Node* mit dem gesuchten Wert zurück, oder NULL." +``` + +pi-coder wendet einen unified diff an (`apply_patch`-Tool), ohne den vollständigen Loop. + +## Manueller Build + +```bash +gcc -Wall -Wextra -o ll_demo linked_list.c main.c +./ll_demo + +# Mit Leak-Check: +valgrind --leak-check=full ./ll_demo +``` diff --git a/examples/c-linkedlist/linked_list.c b/examples/c-linkedlist/linked_list.c new file mode 100644 index 0000000..e8d62ca --- /dev/null +++ b/examples/c-linkedlist/linked_list.c @@ -0,0 +1,43 @@ +#include +#include +#include "linked_list.h" + +Node *node_new(int value) { + Node *n = malloc(sizeof(Node)); + n->value = value; + n->next = NULL; + return n; +} + +Node *list_prepend(Node *head, int value) { + Node *n = node_new(value); + n->next = head; + return n; +} + +Node *list_append(Node *head, int value) { + Node *n = node_new(value); + if (!head) return n; + Node *cur = head; + while (cur->next) cur = cur->next; + cur->next = n; + return head; +} + +void list_print(const Node *head) { + for (const Node *cur = head; cur; cur = cur->next) + printf("%d ", cur->value); + printf("\n"); +} + +/* BUG: Speicher wird nicht freigegeben — valgrind meldet Leaks. */ +void list_free(Node *head) { + (void)head; /* TODO: implementieren */ +} + +int list_length(const Node *head) { + int len = 0; + for (const Node *cur = head; cur; cur = cur->next) + len++; + return len; +} diff --git a/examples/c-linkedlist/linked_list.h b/examples/c-linkedlist/linked_list.h new file mode 100644 index 0000000..9dc46ac --- /dev/null +++ b/examples/c-linkedlist/linked_list.h @@ -0,0 +1,16 @@ +#ifndef LINKED_LIST_H +#define LINKED_LIST_H + +typedef struct Node { + int value; + struct Node *next; +} Node; + +Node *node_new(int value); +Node *list_prepend(Node *head, int value); +Node *list_append(Node *head, int value); +void list_print(const Node *head); +void list_free(Node *head); +int list_length(const Node *head); + +#endif diff --git a/examples/c-linkedlist/main.c b/examples/c-linkedlist/main.c new file mode 100644 index 0000000..1f2f3ad --- /dev/null +++ b/examples/c-linkedlist/main.c @@ -0,0 +1,16 @@ +#include +#include "linked_list.h" + +int main(void) { + Node *list = NULL; + + for (int i = 1; i <= 5; i++) + list = list_append(list, i); + + printf("Liste: "); + list_print(list); + printf("Länge: %d\n", list_length(list)); + + list_free(list); /* leckt wegen unvollständigem TODO */ + return 0; +} diff --git a/examples/go-fibonacci/PROTOKOLL.md b/examples/go-fibonacci/PROTOKOLL.md new file mode 100644 index 0000000..ef9a63b --- /dev/null +++ b/examples/go-fibonacci/PROTOKOLL.md @@ -0,0 +1,37 @@ +# Demo-Protokoll: go-fibonacci + +## Lauf 1 + +**Datum:** +**Befehl:** +``` +/optimize "Ersetze die naive Rekursion durch Memoization. + fib(50) soll in unter 1ms abgeschlossen sein. + Bestehende Tests müssen weiterhin grün bleiben." \ + --test-cmd "go test ./..." --interactive +``` +**Startzeit:** +**Ende Loop (PASS):** +**Dauer Loop (min):** +**Runden:** +**Endergebnis Loop:** PASS / PASS WITH CONCERNS + +**Befehl im --interactive-Checkpoint:** +``` +/continue "Gib zusätzlich die Berechnungszeit in Mikrosekunden aus." +``` +*(oder: `/continue` ohne Zusatzauftrag)* + +**Startzeit /continue:** +**Ende /continue:** + +**Befehl /shipit:** +``` +/shipit +``` +**Startzeit /shipit:** +**Endzeit /shipit:** +**Endergebnis /shipit:** SHIP / NO-SHIP +**Besonderheiten / Beobachtungen:** + +--- diff --git a/examples/go-fibonacci/README.md b/examples/go-fibonacci/README.md new file mode 100644 index 0000000..7d1cd94 --- /dev/null +++ b/examples/go-fibonacci/README.md @@ -0,0 +1,48 @@ +# Go Fibonacci + +Naive rekursive Fibonacci-Implementierung — korrekt, aber exponentiell langsam. +`fib(45)` dauert mehrere Sekunden; `fib(50)` läuft praktisch nicht durch. + +## Aktueller Stand + +``` +main.go fib(n) — rekursiv, O(2^n) +main_test.go TestFib mit 5 Tabellen-Tests (alle grün) +``` + +## Demo 1: `/optimize --interactive` + +``` +/optimize "Ersetze die naive Rekursion durch Memoization. + fib(50) soll in unter 1ms abgeschlossen sein. + Bestehende Tests müssen weiterhin grün bleiben." \ + --test-cmd "go test ./..." --interactive +``` + +Nach dem ersten PASS hält pi-coder im **interaktiven Checkpoint** an. +Hier kann ein Zusatzauftrag erteilt werden: + +``` +/continue "Gib zusätzlich die Berechnungszeit in Mikrosekunden aus." +``` + +Oder einfach bestätigen: + +``` +/continue +``` + +## Demo 2: Abschluss mit `/shipit` + +``` +/shipit +``` + +Der Judge prüft nochmals explizit auf Produktionsreife und gibt SHIP oder NO-SHIP zurück. + +## Manueller Test + +```bash +go test ./... +go run main.go +``` diff --git a/examples/go-fibonacci/go.mod b/examples/go-fibonacci/go.mod new file mode 100644 index 0000000..18dcbe5 --- /dev/null +++ b/examples/go-fibonacci/go.mod @@ -0,0 +1,3 @@ +module fibonacci + +go 1.21 diff --git a/examples/go-fibonacci/main.go b/examples/go-fibonacci/main.go new file mode 100644 index 0000000..4e08244 --- /dev/null +++ b/examples/go-fibonacci/main.go @@ -0,0 +1,18 @@ +package main + +import "fmt" + +// fib berechnet die n-te Fibonacci-Zahl rekursiv. +// Korrekt, aber für n > 40 sehr langsam (exponentiell). +func fib(n int) int { + if n <= 1 { + return n + } + return fib(n-1) + fib(n-2) +} + +func main() { + for i := 0; i <= 10; i++ { + fmt.Printf("fib(%2d) = %d\n", i, fib(i)) + } +} diff --git a/examples/go-fibonacci/main_test.go b/examples/go-fibonacci/main_test.go new file mode 100644 index 0000000..621cd25 --- /dev/null +++ b/examples/go-fibonacci/main_test.go @@ -0,0 +1,20 @@ +package main + +import "testing" + +func TestFib(t *testing.T) { + cases := []struct { + n, want int + }{ + {0, 0}, + {1, 1}, + {2, 1}, + {5, 5}, + {10, 55}, + } + for _, c := range cases { + if got := fib(c.n); got != c.want { + t.Errorf("fib(%d) = %d, want %d", c.n, got, c.want) + } + } +} diff --git a/examples/python-calculator/PROTOKOLL.md b/examples/python-calculator/PROTOKOLL.md new file mode 100644 index 0000000..b7e8b9f --- /dev/null +++ b/examples/python-calculator/PROTOKOLL.md @@ -0,0 +1,19 @@ +# Demo-Protokoll: python-calculator + +## Lauf 1 + +**Datum:** +**Befehl:** +``` +/optimize "Ergänze multiply, divide (wirft ZeroDivisionError bei 0) und power. + Schreibe pytest-Tests für alle neuen Funktionen." \ + --test-cmd "pytest test_calculator.py -v" +``` +**Startzeit:** +**Endzeit:** +**Dauer (min):** +**Runden:** +**Endergebnis:** PASS / PASS WITH CONCERNS / SHIP / NO-SHIP +**Besonderheiten / Beobachtungen:** + +--- diff --git a/examples/python-calculator/README.md b/examples/python-calculator/README.md new file mode 100644 index 0000000..23853d0 --- /dev/null +++ b/examples/python-calculator/README.md @@ -0,0 +1,51 @@ +# Python Calculator + +Einfacher Taschenrechner mit `add()` und `subtract()`. +Multiply, divide, power und Fehlerbehandlung fehlen noch. + +## Aktueller Stand + +``` +calculator.py add(), subtract() +test_calculator.py 5 pytest-Tests (alle grün) +``` + +## Demo: `/optimize` mit Test-Integration + +``` +/optimize "Ergänze multiply, divide (wirft ZeroDivisionError bei 0) und power. + Schreibe pytest-Tests für alle neuen Funktionen." \ + --test-cmd "pytest test_calculator.py -v" +``` + +**Was pi-coder hier zeigt:** +- Coder implementiert, committet +- Extension führt `pytest` aus und übergibt das Ergebnis an den Judge +- Judge bewertet Korrektheit anhand der Testergebnisse +- Bei FAIL: Coder fixt, nächste Runde + +## Voraussetzungen + +```bash +pip install pytest +``` + +## Weitere Demo-Befehle nach dem `/optimize`-Lauf + +``` +/quick_check "Sind alle Randfälle (negative Zahlen, floats) korrekt behandelt?" +``` +Schnelle Einzel-Beurteilung ohne neuen Fix-Loop. + +``` +/update_doku +``` +Lässt den Coder Code-Kommentare ergänzen, README aktualisieren und eine +Bedienungsanleitung erzeugen. + +## Manueller Test + +```bash +pytest test_calculator.py -v +python calculator.py +``` diff --git a/examples/python-calculator/calculator.py b/examples/python-calculator/calculator.py new file mode 100644 index 0000000..34fc2f0 --- /dev/null +++ b/examples/python-calculator/calculator.py @@ -0,0 +1,11 @@ +def add(a, b): + return a + b + + +def subtract(a, b): + return a - b + + +if __name__ == "__main__": + print(add(3, 4)) # 7 + print(subtract(10, 3)) # 7 diff --git a/examples/python-calculator/test_calculator.py b/examples/python-calculator/test_calculator.py new file mode 100644 index 0000000..20b9369 --- /dev/null +++ b/examples/python-calculator/test_calculator.py @@ -0,0 +1,18 @@ +import pytest +from calculator import add, subtract + + +def test_add_positive(): + assert add(2, 3) == 5 + +def test_add_negative(): + assert add(-1, 1) == 0 + +def test_add_zero(): + assert add(0, 0) == 0 + +def test_subtract_basic(): + assert subtract(5, 3) == 2 + +def test_subtract_negative_result(): + assert subtract(3, 5) == -2 diff --git a/examples/restore-all.sh b/examples/restore-all.sh new file mode 100755 index 0000000..e28c3ff --- /dev/null +++ b/examples/restore-all.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Stellt den Ausgangszustand aller Examples wieder her. +# Löscht erzeugte Sub-Repos, restauriert Quelldateien aus dem Haupt-Repo +# und bereinigt Build-Artefakte. + +set -euo pipefail + +ROOT="$(git -C "$(dirname "$0")" rev-parse --show-toplevel)" +EXAMPLES="$ROOT/examples" + +echo "Stelle Examples-Ausgangszustand wieder her..." + +for dir in python-calculator rust-wordcount go-fibonacci c-linkedlist; do + path="$EXAMPLES/$dir" + if [ -d "$path/.git" ]; then + rm -rf "$path/.git" + echo " ✓ Sub-Repo entfernt: $dir" + fi + git -C "$ROOT" checkout -- "examples/$dir/" + echo " ✓ Dateien restauriert: $dir" +done + +# Build-Artefakte bereinigen +rm -rf "$EXAMPLES/rust-wordcount/target" +find "$EXAMPLES" -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true +find "$EXAMPLES" -name ".pytest_cache" -exec rm -rf {} + 2>/dev/null || true +find "$EXAMPLES" -name "ll_demo" -delete 2>/dev/null || true + +echo "" +echo "Fertig. Alle Examples sind im Ausgangszustand." +echo "git status zeigt examples/ als clean (wenn kein PROTOKOLL.md verändert wurde)." diff --git a/examples/rust-wordcount/Cargo.toml b/examples/rust-wordcount/Cargo.toml new file mode 100644 index 0000000..2845fab --- /dev/null +++ b/examples/rust-wordcount/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "wordcount" +version = "0.1.0" +edition = "2021" diff --git a/examples/rust-wordcount/PROTOKOLL.md b/examples/rust-wordcount/PROTOKOLL.md new file mode 100644 index 0000000..762f940 --- /dev/null +++ b/examples/rust-wordcount/PROTOKOLL.md @@ -0,0 +1,29 @@ +# Demo-Protokoll: rust-wordcount + +## Lauf 1 + +**Datum:** +**Befehl:** +``` +/optimize "Ergänze --lines (Zeilenzählung) und --chars (Zeichenzählung) als CLI-Flags. + Ohne Flag: Standardausgabe wie bisher (Wörter). + Schreibe Tests für alle drei Modi." \ + --test-cmd "cargo test" +``` +**Startzeit:** +**Endzeit:** +**Dauer /optimize (min):** +**Runden:** +**Endergebnis /optimize:** PASS / PASS WITH CONCERNS / SHIP / NO-SHIP + +**Befehl /version:** +``` +/version +``` +**Startzeit /version:** +**Endzeit /version:** +**Gewählter Bump:** patch / minor / major +**Gesetzter Tag:** +**Besonderheiten / Beobachtungen:** + +--- diff --git a/examples/rust-wordcount/README.md b/examples/rust-wordcount/README.md new file mode 100644 index 0000000..69a6c10 --- /dev/null +++ b/examples/rust-wordcount/README.md @@ -0,0 +1,50 @@ +# Rust Word Counter + +Liest stdin und gibt die Anzahl der Wörter aus. +Zeilen- und Zeichenzählung sowie CLI-Flags fehlen noch. + +## Aktueller Stand + +``` +src/main.rs count_words() — nur Wortzählung, kein Argument-Parsing +Cargo.toml Version 0.1.0 +``` + +## Demo 1: `/optimize` mit Cargo-Test-Integration + +``` +/optimize "Ergänze --lines (Zeilenzählung) und --chars (Zeichenzählung) als CLI-Flags. + Ohne Flag: Standardausgabe wie bisher (Wörter). + Schreibe Tests für alle drei Modi." \ + --test-cmd "cargo test" +``` + +**Was pi-coder hier zeigt:** +- Rust-Toolchain wird automatisch erkannt +- `cargo test`-Output geht an den Judge +- Mehrere Compile-Test-Fix-Zyklen möglich + +## Demo 2: `/version` nach dem Feature + +**Voraussetzung:** Das Verzeichnis muss ein git-Repo mit mindestens einem Commit sein. +Falls noch kein Repo existiert, vorher einmalig: + +```bash +git init && git add -A && git commit -m "feat: initial wordcount" +git tag v0.1.0 +``` + +``` +/version +``` + +Analysiert die Commits seit `v0.1.0`, erkennt `feat:`-Commits → schlägt `minor`-Bump vor +und setzt den Git-Tag `v0.2.0`. + +## Manueller Test + +```bash +cargo test +echo "Hallo Welt" | cargo run +echo -e "Zeile 1\nZeile 2" | cargo run -- --lines +``` diff --git a/examples/rust-wordcount/src/main.rs b/examples/rust-wordcount/src/main.rs new file mode 100644 index 0000000..257cc19 --- /dev/null +++ b/examples/rust-wordcount/src/main.rs @@ -0,0 +1,36 @@ +use std::io::{self, Read}; + +fn count_words(text: &str) -> usize { + text.split_whitespace().count() +} + +fn main() { + let mut input = String::new(); + io::stdin().read_to_string(&mut input).unwrap(); + println!("{} words", count_words(&input)); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_empty_input() { + assert_eq!(count_words(""), 0); + } + + #[test] + fn test_single_word() { + assert_eq!(count_words("hallo"), 1); + } + + #[test] + fn test_multiple_words() { + assert_eq!(count_words("eins zwei drei"), 3); + } + + #[test] + fn test_extra_whitespace() { + assert_eq!(count_words(" a b "), 2); + } +}