@@ -0,0 +1,675 @@
#!/usr/bin/env python3
""" Generate the ten C++20 cards for Year 2, semester 1.
The lesson scope comes from /home/user/dev/keys/cxx.md. The generated cards
share the same runtime, React viewer and PDF contract as the existing STEM
cards, while every task contains an independently compilable C++ experiment.
"""
from __future__ import annotations
import json
import os
from pathlib import Path
import shutil
import stat
import textwrap
import uuid
ROOT = Path ( __file__ ) . resolve ( ) . parents [ 3 ]
OUTPUT = ROOT / " series " / " cpp "
TEMPLATE = ROOT / " series " / " console " / " lab-console-busybox "
UUID_NAMESPACE = uuid . UUID ( " 1b43af4e-354f-51dc-a42a-67593c614624 " )
def clean ( text : str ) - > str :
return textwrap . dedent ( text ) . lstrip ( )
LESSONS = [
{
" id " : " CPP01 " ,
" number " : " 01 " ,
" repo " : " lab-cpp-build-namespaces " ,
" slug " : " build-namespaces " ,
" title " : " Od C do C++: oddzielna kompilacja i przestrzenie nazw " ,
" topic " : " Deklaracja, definicja, translation unit, linkage i namespace " ,
" chapter " : " A Tour of C++ 3e: rozdziały 1 i 3 " ,
" mission " : " Zbudować program z wielu jednostek translacji i odczytać granice nazw oraz linkage. " ,
" tasks " : [
( " Deklaracja i definicja " , " Rozdziel deklarację funkcji, definicję i punkt wejścia między trzy pliki. " , " sum=7 " , {
" calculator.hpp " : """ #pragma once
namespace lab { int add(int left, int right); }
""" ,
" calculator.cpp " : """ #include " calculator.hpp "
namespace lab { int add(int left, int right) { return left + right; } }
""" ,
" main.cpp " : """ #include " calculator.hpp "
#include <iostream>
int main() { std::cout << " sum= " << lab::add(3, 4) << ' \\ n ' ; }
""" ,
} ) ,
( " Linkage zewnętrzny i wewnętrzny " , " Porównaj symbol dostępny między plikami z nazwą ukrytą w anonimowej przestrzeni nazw. " , " external=5 internal_bias_result=8 " , {
" api.hpp " : """ #pragma once
int global_value();
int shifted_value();
""" ,
" global.cpp " : """ #include " api.hpp "
int global_value() { return 5; }
""" ,
" shifted.cpp " : """ #include " api.hpp "
namespace { constexpr int bias = 3; }
int shifted_value() { return global_value() + bias; }
""" ,
" main.cpp " : """ #include " api.hpp "
#include <iostream>
int main() { std::cout << " external= " << global_value() << " internal_bias_result= " << shifted_value() << ' \\ n ' ; }
""" ,
} ) ,
( " Kwalifikacja przestrzeni nazw " , " Użyj dwóch funkcji o tej samej nazwie bez kolizji i wywołaj je kwalifikowaną nazwą. " , " sensor=21 network=204 " , {
" main.cpp " : """ #include <iostream>
namespace sensor { int read() { return 21; } }
namespace network { int read() { return 204; } }
int main() { std::cout << " sensor= " << sensor::read() << " network= " << network::read() << ' \\ n ' ; }
""" ,
} ) ,
] ,
} ,
{
" id " : " CPP02 " , " number " : " 02 " , " repo " : " lab-cpp-types-initialization " , " slug " : " types-initialization " ,
" title " : " Typy, inicjalizacja, auto, const i enum class " ,
" topic " : " Bezpieczna inicjalizacja i typy definiowane przez użytkownika " ,
" chapter " : " A Tour of C++ 3e: rozdziały 1 i 2 " ,
" mission " : " Rozpoznać typy wyrażeń i używać inicjalizacji, const oraz enum class bez niejawnego mieszania domen. " ,
" tasks " : [
( " Inicjalizacja i auto " , " Zastosuj inicjalizację klamrową i potwierdź typ dedukowany przez auto. " , " count=4 twice=8 type=int " , {
" main.cpp " : """ #include <iostream>
#include <type_traits>
int main() {
int count {4} ;
auto twice = count * 2;
static_assert(std::is_same_v<decltype(twice), int>);
std::cout << " count= " << count << " twice= " << twice << " type=int \\ n " ;
}
""" ,
} ) ,
( " const i constexpr " , " Oddziel wartość obliczaną w czasie kompilacji od obiektu tylko do odczytu. " , " square=49 limit=12 " , {
" main.cpp " : """ #include <iostream>
constexpr int square(int value) { return value * value; }
int main() {
constexpr int result = square(7);
const int limit {12} ;
static_assert(result == 49);
std::cout << " square= " << result << " limit= " << limit << ' \\ n ' ;
}
""" ,
} ) ,
( " enum class i struktura " , " Zbuduj typ rekordu oraz silnie typowany stan bez niejawnej konwersji do int. " , " sample=23 state=ready " , {
" main.cpp " : """ #include <iostream>
enum class State { idle, ready };
struct Sample { int value; State state; };
int main() {
Sample sample { 23, State::ready};
std::cout << " sample= " << sample.value << " state= " << (sample.state == State::ready ? " ready " : " idle " ) << ' \\ n ' ;
}
""" ,
} ) ,
] ,
} ,
{
" id " : " CPP03 " , " number " : " 03 " , " repo " : " lab-cpp-pointers-references " , " slug " : " pointers-references " ,
" title " : " Wskaźniki, referencje, tablice i nullptr " ,
" topic " : " Tożsamość obiektu, dostęp pośredni i przekazywanie argumentów " ,
" chapter " : " A Tour of C++ 3e: sekcje 1.7 i 3.4 " ,
" mission " : " Odróżnić wskaźnik od referencji i tablicę od wskaźnika podczas przekazywania danych. " ,
" tasks " : [
( " Wskaźnik i nullptr " , " Sprawdź brak obiektu przed dereferencją, a następnie zmodyfikuj wskazywaną wartość. " , " null=1 value=9 " , {
" main.cpp " : """ #include <iostream>
int main() {
int value {7} ;
int* pointer = nullptr;
const bool was_null = pointer == nullptr;
pointer = &value;
*pointer += 2;
std::cout << " null= " << was_null << " value= " << value << ' \\ n ' ;
}
""" ,
} ) ,
( " Referencja jako alias " , " Przekaż dwa obiekty przez referencję i zamień ich wartości. " , " left=8 right=3 " , {
" main.cpp " : """ #include <iostream>
void swap_values(int& left, int& right) { const int temp = left; left = right; right = temp; }
int main() { int left {3} ; int right {8} ; swap_values(left, right); std::cout << " left= " << left << " right= " << right << ' \\ n ' ; }
""" ,
} ) ,
( " Tablica i jej rozmiar " , " Policz elementy w referencji do tablicy, zanim argument zaniknie do wskaźnika. " , " count=4 sum=20 " , {
" main.cpp " : """ #include <cstddef>
#include <iostream>
template <std::size_t N> int sum(const int (&values)[N]) { int result {} ; for (int value : values) result += value; return result; }
int main() { int values[] { 2, 4, 6, 8}; std::cout << " count= " << (sizeof(values) / sizeof(values[0])) << " sum= " << sum(values) << ' \\ n ' ; }
""" ,
} ) ,
] ,
} ,
{
" id " : " CPP04 " , " number " : " 04 " , " repo " : " lab-cpp-functions-overloading " , " slug " : " functions-overloading " ,
" title " : " Funkcje, przeciążanie, wartości zwracane i modułowość " ,
" topic " : " Sygnatura funkcji, wybór przeciążenia, zakres i granica modułu " ,
" chapter " : " A Tour of C++ 3e: rozdziały 1 i 3 " ,
" mission " : " Zaprojektować mały interfejs funkcji i sprawdzić wybór przeciążenia oraz zwracanie wartości. " ,
" tasks " : [
( " Przeciążanie " , " Wywołaj dwie funkcje o tej samej nazwie i różnych typach parametrów. " , " int=6 double=3.5 " , {
" main.cpp " : """ #include <iostream>
int twice(int value) { return value * 2; }
double twice(double value) { return value * 2.0; }
int main() { std::cout << " int= " << twice(3) << " double= " << twice(1.75) << ' \\ n ' ; }
""" ,
} ) ,
( " Zwracanie przez wartość " , " Zwróć kompletny wynik obliczenia jako wartość, bez ujawniania obiektu lokalnego. " , " quotient=3 remainder=2 " , {
" main.cpp " : """ #include <iostream>
struct Division { int quotient; int remainder; };
Division divide(int value, int by) { return { value / by, value % by}; }
int main() { const Division result = divide(17, 5); std::cout << " quotient= " << result.quotient << " remainder= " << result.remainder << ' \\ n ' ; }
""" ,
} ) ,
( " Interfejs modułu " , " Oddziel publiczne deklaracje statystyk od ich implementacji. " , " min=2 max=9 " , {
" stats.hpp " : """ #pragma once
namespace stats { int minimum(int left, int right); int maximum(int left, int right); }
""" ,
" stats.cpp " : """ #include " stats.hpp "
namespace stats { int minimum(int left, int right) { return left < right ? left : right; } int maximum(int left, int right) { return left > right ? left : right; } }
""" ,
" main.cpp " : """ #include " stats.hpp "
#include <iostream>
int main() { std::cout << " min= " << stats::minimum(9, 2) << " max= " << stats::maximum(9, 2) << ' \\ n ' ; }
""" ,
} ) ,
] ,
} ,
{
" id " : " CPP05 " , " number " : " 05 " , " repo " : " lab-cpp-classes-objects " , " slug " : " classes-objects " ,
" title " : " Klasa i obiekt: interfejs oraz implementacja " ,
" topic " : " Pola, metody, kontrola dostępu i zachowanie obiektu " ,
" chapter " : " A Tour of C++ 3e: rozdziały 2 i 5 " ,
" mission " : " Zamknąć stan i operacje w klasie oraz oddzielić interfejs publiczny od implementacji. " ,
" tasks " : [
( " Klasa w dwóch plikach " , " Zdefiniuj interfejs licznika w nagłówku, a metody w osobnej jednostce translacji. " , " counter=3 " , {
" counter.hpp " : """ #pragma once
class Counter { public: explicit Counter(int start); void increment(); int value() const; private: int value_; };
""" ,
" counter.cpp " : """ #include " counter.hpp "
Counter::Counter(int start) : value_ {start} {}
void Counter::increment() { ++value_; }
int Counter::value() const { return value_; }
""" ,
" main.cpp " : """ #include " counter.hpp "
#include <iostream>
int main() { Counter counter {2} ; counter.increment(); std::cout << " counter= " << counter.value() << ' \\ n ' ; }
""" ,
} ) ,
( " Stan prywatny " , " Udostępnij operacje rachunku bez publicznego zapisu pola balance. " , " accepted=1 rejected=1 balance=15 " , {
" main.cpp " : """ #include <iostream>
class Account {
public:
explicit Account(int balance) : balance_ {balance} {}
bool deposit(int amount) { if (amount <= 0) return false; balance_ += amount; return true; }
int balance() const { return balance_; }
private: int balance_;
};
int main() { Account account {10} ; const bool accepted = account.deposit(5); const bool rejected = !account.deposit(-2); std::cout << " accepted= " << accepted << " rejected= " << rejected << " balance= " << account.balance() << ' \\ n ' ; }
""" ,
} ) ,
( " Metody opisują zachowanie " , " Przenieś obliczenie pola i skalowanie do obiektu prostokąta. " , " area=24 scaled=96 " , {
" main.cpp " : """ #include <iostream>
class Rectangle { public: Rectangle(int width, int height) : width_ {width} , height_ {height} {} int area() const { return width_ * height_; } void scale(int factor) { width_ *= factor; height_ *= factor; } private: int width_; int height_; };
int main() { Rectangle rectangle { 6, 4}; const int before = rectangle.area(); rectangle.scale(2); std::cout << " area= " << before << " scaled= " << rectangle.area() << ' \\ n ' ; }
""" ,
} ) ,
] ,
} ,
{
" id " : " CPP06 " , " number " : " 06 " , " repo " : " lab-cpp-invariants " , " slug " : " invariants " ,
" title " : " Hermetyzacja, niezmienniki i odpowiedzialność klasy " ,
" topic " : " Poprawny stan obiektu przed i po każdej operacji publicznej " ,
" chapter " : " A Tour of C++ 3e: sekcja 4.3 i rozdział 5 " ,
" mission " : " Zdefiniować niezmiennik klasy i utrzymywać go we wszystkich publicznych operacjach. " ,
" tasks " : [
( " Walidowana zmiana stanu " , " Odrzuć temperaturę spoza przyjętego zakresu bez uszkodzenia obiektu. " , " accepted=1 rejected=1 value=25 " , {
" main.cpp " : """ #include <iostream>
class Temperature { public: explicit Temperature(int value) : value_ {value} {} bool set(int value) { if (value < -50 || value > 150) return false; value_ = value; return true; } int value() const { return value_; } private: int value_; };
int main() { Temperature temperature {20} ; const bool accepted = temperature.set(25); const bool rejected = !temperature.set(400); std::cout << " accepted= " << accepted << " rejected= " << rejected << " value= " << temperature.value() << ' \\ n ' ; }
""" ,
} ) ,
( " Pojemność kolejki " , " Nie dopuść, by liczba elementów przekroczyła pojemność lub spadła poniżej zera. " , " pushes=2 overflow=1 pops=2 empty=1 depth=0 " , {
" main.cpp " : """ #include <iostream>
class QueueDepth { public: explicit QueueDepth(int capacity) : capacity_ {capacity} {} bool push() { if (depth_ == capacity_) return false; ++depth_; return true; } bool pop() { if (depth_ == 0) return false; --depth_; return true; } int depth() const { return depth_; } private: int capacity_; int depth_ {} ; };
int main() { QueueDepth queue {2} ; int pushes {} ; pushes += queue.push(); pushes += queue.push(); const bool overflow = !queue.push(); int pops {} ; pops += queue.pop(); pops += queue.pop(); const bool empty = !queue.pop(); std::cout << " pushes= " << pushes << " overflow= " << overflow << " pops= " << pops << " empty= " << empty << " depth= " << queue.depth() << ' \\ n ' ; }
""" ,
} ) ,
( " Normalizacja przedziału " , " Skonstruuj przedział zawsze spełniający start <= end. " , " start=3 end=10 width=7 " , {
" main.cpp " : """ #include <iostream>
class Interval { public: Interval(int first, int second) : start_ { first < second ? first : second}, end_ { first < second ? second : first} {} int start() const { return start_; } int end() const { return end_; } int width() const { return end_ - start_; } private: int start_; int end_; };
int main() { Interval interval { 10, 3}; std::cout << " start= " << interval.start() << " end= " << interval.end() << " width= " << interval.width() << ' \\ n ' ; }
""" ,
} ) ,
] ,
} ,
{
" id " : " CPP07 " , " number " : " 07 " , " repo " : " lab-cpp-construction-destruction " , " slug " : " construction-destruction " ,
" title " : " Konstruktory, destruktory i inicjalizacja obiektu " ,
" topic " : " Kolejność inicjalizacji, delegowanie konstruktorów i porządek destrukcji " ,
" chapter " : " A Tour of C++ 3e: rozdziały 5 i 6 " ,
" mission " : " Zaobserwować pełny cykl tworzenia i niszczenia obiektu oraz kolejność jego części. " ,
" tasks " : [
( " Kolejność konstrukcji " , " Zaobserwuj, że pole jest konstruowane przed ciałem konstruktora obiektu. " , " member \n object \n ready " , {
" main.cpp " : """ #include <iostream>
struct Member { Member() { std::cout << " member \\ n " ; } };
struct Object { Member member; Object() { std::cout << " object \\ n " ; } };
int main() { Object object; (void)object; std::cout << " ready \\ n " ; }
""" ,
} ) ,
( " Destrukcja w odwrotnej kolejności " , " Utwórz dwa obiekty automatyczne i sprawdź kolejność destruktorów. " , " enter \n construct=A \n construct=B \n destroy=B \n destroy=A \n leave " , {
" main.cpp " : """ #include <iostream>
struct Trace { const char* name; explicit Trace(const char* value) : name {value} { std::cout << " construct= " << name << ' \\ n ' ; } ~Trace() { std::cout << " destroy= " << name << ' \\ n ' ; } };
void scope() { Trace first { " A " }; Trace second { " B " }; }
int main() { std::cout << " enter \\ n " ; scope(); std::cout << " leave \\ n " ; }
""" ,
} ) ,
( " Konstruktor delegujący " , " Skieruj konstruktor domyślny do jednego konstruktora utrzymującego reguły inicjalizacji. " , " point=0,0 other=3,4 " , {
" main.cpp " : """ #include <iostream>
class Point { public: Point() : Point { 0, 0} {} Point(int x, int y) : x_ {x} , y_ {y} {} int x() const { return x_; } int y() const { return y_; } private: int x_; int y_; };
int main() { Point point; Point other { 3, 4}; std::cout << " point= " << point.x() << ' , ' << point.y() << " other= " << other.x() << ' , ' << other.y() << ' \\ n ' ; }
""" ,
} ) ,
] ,
} ,
{
" id " : " CPP08 " , " number " : " 08 " , " repo " : " lab-cpp-scope-lifetime " , " slug " : " scope-lifetime " ,
" title " : " Zakres i czas życia obiektów " ,
" topic " : " Obiekty automatyczne, statyczne i dynamiczne oraz przesłanianie nazw " ,
" chapter " : " A Tour of C++ 3e: sekcja 1.5 i rozdział 6 " ,
" mission " : " Powiązać miejsce przechowywania obiektu z początkiem i końcem jego czasu życia. " ,
" tasks " : [
( " Automatyczny i statyczny " , " Porównaj nowy obiekt automatyczny z zachowującym stan obiektem statycznym. " , " auto=7 static_calls=1,2 " , {
" main.cpp " : """ #include <iostream>
int next_call() { static int calls {} ; return ++calls; }
int main() { int automatic {7} ; const int first = next_call(); const int second = next_call(); std::cout << " auto= " << automatic << " static_calls= " << first << ' , ' << second << ' \\ n ' ; }
""" ,
} ) ,
( " Obiekt dynamiczny " , " Jawnie zestaw new z delete i obserwuj moment wykonania destruktora. " , " construct \n value=11 \n destroy " , {
" main.cpp " : """ #include <iostream>
struct Dynamic { explicit Dynamic(int value) : value {value} { std::cout << " construct \\ n " ; } ~Dynamic() { std::cout << " destroy \\ n " ; } int value; };
int main() { Dynamic* object = new Dynamic {11} ; std::cout << " value= " << object->value << ' \\ n ' ; delete object; }
""" ,
} ) ,
( " Zakres i przesłanianie " , " Pokaż, że nazwa wewnętrzna nie zmienia obiektu o tej samej nazwie w zakresie zewnętrznym. " , " outer=7 \n inner=9 \n outer=7 " , {
" main.cpp " : """ #include <iostream>
int main() { int value {7} ; std::cout << " outer= " << value << ' \\ n ' ; { int value {9} ; std::cout << " inner= " << value << ' \\ n ' ; } std::cout << " outer= " << value << ' \\ n ' ; }
""" ,
} ) ,
] ,
} ,
{
" id " : " CPP09 " , " number " : " 09 " , " repo " : " lab-cpp-raii " , " slug " : " raii " ,
" title " : " RAII: zasób związany z czasem życia obiektu " ,
" topic " : " Pozyskanie w konstruktorze, zwolnienie w destruktorze i wszystkie wyjścia z zakresu " ,
" chapter " : " A Tour of C++ 3e: sekcja 6.3 " ,
" mission " : " Zapewnić zwolnienie zasobu przez strukturę programu, także przy wczesnym return. " ,
" tasks " : [
( " Uchwyt jako obiekt " , " Zwiąż stan otwarcia uchwytu z konstruktorem i destruktorem. " , " acquire \n open=1 \n release \n open=0 " , {
" main.cpp " : """ #include <iostream>
bool resource_open {} ;
class Handle { public: Handle() { resource_open = true; std::cout << " acquire \\ n " ; } ~Handle() { resource_open = false; std::cout << " release \\ n " ; } Handle(const Handle&) = delete; Handle& operator=(const Handle&) = delete; };
int main() { { Handle handle; (void)handle; std::cout << " open= " << resource_open << ' \\ n ' ; } std::cout << " open= " << resource_open << ' \\ n ' ; }
""" ,
} ) ,
( " Wczesny return " , " Potwierdź zwolnienie strażnika przy wyjściu z funkcji przed jej końcem. " , " acquire \n work=short \n release \n after " , {
" main.cpp " : """ #include <iostream>
struct Guard { Guard() { std::cout << " acquire \\ n " ; } ~Guard() { std::cout << " release \\ n " ; } };
void work(bool short_path) { Guard guard; if (short_path) { std::cout << " work=short \\ n " ; return; } std::cout << " work=long \\ n " ; }
int main() { work(true); std::cout << " after \\ n " ; }
""" ,
} ) ,
( " Zagnieżdżone zasoby " , " Sprawdź zwalnianie zagnieżdżonych zasobów w odwrotnej kolejności. " , " acquire=A \n acquire=B \n inside \n release=B \n release=A " , {
" main.cpp " : """ #include <iostream>
struct Resource { const char* name; explicit Resource(const char* value) : name {value} { std::cout << " acquire= " << name << ' \\ n ' ; } ~Resource() { std::cout << " release= " << name << ' \\ n ' ; } };
int main() { Resource first { " A " }; { Resource second { " B " }; std::cout << " inside \\ n " ; } }
""" ,
} ) ,
] ,
} ,
{
" id " : " CPP10 " , " number " : " 10 " , " repo " : " lab-cpp-copy-move " , " slug " : " copy-move " ,
" title " : " Kopiowanie, przenoszenie i Rule of Zero/Five " ,
" topic " : " Głębokie kopiowanie, transfer własności i typy składowe zarządzające zasobem " ,
" chapter " : " A Tour of C++ 3e: sekcje 6.2– 6.4 " ,
" mission " : " Rozróżnić kopię od przeniesienia i wybrać Rule of Zero dla bezpiecznego typu zasobowego. " ,
" tasks " : [
( " Głęboka kopia " , " Zaimplementuj kopię bufora tak, aby dwa obiekty nie współdzieliły jednej komórki. " , " original=7 copy=9 distinct=1 " , {
" main.cpp " : """ #include <iostream>
class Buffer { public: explicit Buffer(int value) : data_ { new int {value} } {} ~Buffer() { delete data_; } Buffer(const Buffer& other) : data_ { new int { *other.data_}} {} Buffer& operator=(const Buffer&) = delete; int value() const { return *data_; } void set(int value) { *data_ = value; } const int* address() const { return data_; } private: int* data_; };
int main() { Buffer original {7} ; Buffer copy {original} ; copy.set(9); std::cout << " original= " << original.value() << " copy= " << copy.value() << " distinct= " << (original.address() != copy.address()) << ' \\ n ' ; }
""" ,
} ) ,
( " Przeniesienie własności " , " Przenieś bufor move-only i pozostaw obiekt źródłowy w poprawnym stanie pustym. " , " src_empty=1 value=42 " , {
" main.cpp " : """ #include <iostream>
#include <utility>
class Buffer { public: explicit Buffer(int value) : data_ { new int {value} } {} ~Buffer() { delete data_; } Buffer(const Buffer&) = delete; Buffer& operator=(const Buffer&) = delete; Buffer(Buffer&& other) noexcept : data_ { std::exchange(other.data_, nullptr)} {} bool empty() const { return data_ == nullptr; } int value() const { return *data_; } private: int* data_; };
int main() { Buffer source {42} ; Buffer target { std::move(source)}; std::cout << " src_empty= " << source.empty() << " value= " << target.value() << ' \\ n ' ; }
""" ,
} ) ,
( " Rule of Zero " , " Zbuduj typ z bezpiecznych składowych standardowych bez własnego destruktora i operacji copy/move. " , " copy=alpha original=beta moved=alpha " , {
" main.cpp " : """ #include <iostream>
#include <string>
#include <utility>
struct Document { std::string text; };
int main() { Document original { " alpha " }; Document copy = original; original.text = " beta " ; Document moved = std::move(copy); std::cout << " copy= " << moved.text << " original= " << original.text << " moved= " << moved.text << ' \\ n ' ; }
""" ,
} ) ,
] ,
} ,
]
CARD_ACTION = r ''' #!/usr/bin/env bash
set -euo pipefail
action= " $ { 1:?action is required} "
root= " $ { STEM_REPO:-$ { CARD_ROOT:-$(cd -- " $(dirname -- " $ {BASH_SOURCE[0]} " )/.. " && pwd)}} "
profile= " $ { STEM_PROFILE:-native-amd64} "
target= " $ { STEM_TARGET:-native} "
selector= " $ { STEM_TASK:-task1} "
card_id= " $(sed -n ' s/^card: cpp \ ///p ' " $root/lesson-flow.yaml " | head -n1) "
resolve_task() {
local value= " $ { 1,,} "
if [[ " $value " =~ ^(task|t)?[-_]?0*([1-3])($|[-_].*) ]]; then printf ' task %02d \ n ' " $ {BASH_REMATCH[2]} " ; return; fi
[[ " $value " =~ ^task0[1-3]$ ]] && { printf ' %s \ n ' " $value " ; return; }
printf ' Unknown task selector: %s \ n ' " $1 " >&2; exit 2
}
task= " $(resolve_task " $selector " ) "
artifact_dir= " $root/.stem/artifacts/$profile/$target/$task "
binary= " $artifact_dir/program "
mkdir -p " $artifact_dir "
compile_task() {
local selected= " $1 " output= " $2 "
mapfile -t sources < <(find " $root/src/$selected " -maxdepth 1 -type f -name ' *.cpp ' | sort)
[[ $ { #sources[@]} -gt 0 ]] || { printf ' No C++ sources for %s \ n ' " $selected " >&2; exit 2; }
" $ { CXX:-g++} " -std=c++20 -Wall -Wextra -Wpedantic -Werror -O0 -g3 -fno-omit-frame-pointer -I " $root/src/$selected " " $ {sources[@]} " -o " $output "
}
verify_task() {
local selected= " $1 " output= " $2 " actual= " $artifact_dir/actual.txt "
compile_task " $selected " " $output "
" $output " > " $actual "
diff -u " $root/tests/expected/$selected.txt " " $actual "
}
case " $profile:$target:$action " in
native-amd64:native:build)
for item in task01 task02 task03; do
current= " $root/.stem/artifacts/$profile/$target/$item "
mkdir -p " $current "
compile_task " $item " " $current/program "
done
printf ' BUILD %s standard=c++20 tasks=3 \ n ' " $card_id "
;;
native-amd64:native:test|native-amd64:native:run)
verify_task " $task " " $binary "
printf ' PASS %s %s standard=c++20 \ n ' " $card_id " " $task "
;;
native-amd64:native:debug)
verify_task " $task " " $binary "
trace= " $artifact_dir/debug.log "
if command -v gdb >/dev/null 2>&1; then
gdb -q -batch -ex ' set pagination off ' -ex ' set debuginfod enabled off ' -ex ' break main ' -ex run -ex ' info source ' -ex ' info args ' -ex continue " $binary " > " $trace " 2>&1
else
{ printf ' gdb unavailable; binary evidence follows \ n ' ; " $ { NM:-nm} " -C " $binary " | head -n 80; " $binary " ; } > " $trace " 2>&1
fi
cat " $trace "
[[ -s " $trace " ]]
printf ' TRACE %s %s log= %s \ n ' " $card_id " " $task " " $trace "
;;
*) printf ' Unsupported profile/target/action: %s / %s / %s \ n ' " $profile " " $target " " $action " >&2; exit 2 ;;
esac
'''
def write ( path : Path , content : str , executable : bool = False ) - > None :
path . parent . mkdir ( parents = True , exist_ok = True )
path . write_text ( clean ( content ) , encoding = " utf-8 " )
if executable :
path . chmod ( path . stat ( ) . st_mode | stat . S_IXUSR | stat . S_IXGRP | stat . S_IXOTH )
def card_source ( lesson : dict , document_uuid : str ) - > dict :
prefix = lesson [ " id " ]
sections = [ ]
tasks = { }
scope_rows = [ ]
for index , ( title , prompt , expected , _files ) in enumerate ( lesson [ " tasks " ] , 1 ) :
task_id = f " task { index : 02d } "
expected_tex = expected . replace ( " _ " , " \\ _ " )
sections . append ( {
" title " : f " Task { index : 02d } · { title } " ,
" content_kind " : " prose " ,
" content_tex " : f " { prompt } Najpierw przewidź wynik, następnie skompiluj program z ostrzeżeniami traktowanymi jako błędy i porównaj dokładny stdout z kontraktem: { expected_tex . replace ( chr ( 10 ) , ' ; ' ) } . " ,
" educational_requirement_refs " : [ f " { prefix } .WE01 " ] ,
" learning_effect_refs " : [ f " { prefix } .EN01 " , f " { prefix } .EK01 " ] ,
" assessment_criterion_refs " : [ f " { prefix } .KW01 " ] ,
" area_tree_refs " : [ f " { prefix } .WE01.OG.IP.01 " , f " { prefix } .WE01.TECH.I4.01 " ] ,
" steps " : [
{ " id " : f " T { index : 02d } -PREDICT " , " title " : " Zapisz przewidywany wynik i wskaż regułę języka. " , " tree_refs " : [ f " { prefix } .WE01.OG.IP.01 " ] } ,
{ " id " : f " T { index : 02d } -VERIFY " , " title " : " Uruchom test i zachowaj mierzalny dowód wykonania. " , " tree_refs " : [ f " { prefix } .WE01.TECH.I4.01 " ] } ,
] ,
} )
tasks [ task_id ] = {
" prompt_tex " : prompt ,
" criterion " : expected_tex . replace ( " \n " , " ; " ) ,
" assessment_criterion_ref " : f " { prefix } .KW01 " ,
}
scope_rows . append ( { " chapter " : " 1 " , " task " : f " Task { index : 02d } " , " idea_tex " : title , " priority " : " obowiązkowe " , " status " : " ready " , " version " : " v00.01 " } )
return {
" $schema " : " ../../../../tools/card-layouts/schemas/card-source.schema.json " ,
" schema " : " esc-card-source.v1 " ,
" card " : {
" id " : f " mpabi-inf-cpp-y2s1- { lesson [ ' number ' ] } - { lesson [ ' slug ' ] } " ,
" series " : " cpp " ,
" series_title " : " C++ · Rok 2 · Semestr 1 " ,
" number " : lesson [ " number " ] , " count " : " 10 " , " slug " : lesson [ " slug " ] ,
" title " : f " { lesson [ ' id ' ] } · { lesson [ ' title ' ] } " , " topic " : lesson [ " topic " ] ,
" project " : " C++20 · fundamenty języka i modelu obiektu " , " subject " : " Informatyka " ,
" level " : " Rok 2 · Semestr 1 " , " revision_date " : " 2026-07-21T00:00:00+02:00 " ,
" status " : " Gotowa do wykonania " , " version " : " v00.01 " , " uuid " : document_uuid ,
" author " : " M. Pabiszczak " , " year " : " 2026 " ,
} ,
" generated " : { " tex " : " doc/generated/main.tex " , " html " : " web/index.html " , " html_css " : " web/style.css " , " react_app " : True , " html_tree_inspector " : False } ,
" template " : " templates/karta-klasyczna.json " ,
" title_block " : {
" category " : " KARTA PRACY · INFORMATYKA · C++20 " , " standard " : " ISO 7200 " ,
" prepared_by " : " M. Pabiszczak " , " prepared_on " : " 21.07.2026 " , " checked_by " : " testy automatyczne " , " approved_by " : " — " ,
" url " : " http://localhost:8080 " , " repository_url " : f " https://zsl-gitea.mpabi.pl/edu-cpp/ { lesson [ ' repo ' ] } " ,
" revision " : " 1 " , " issued_on " : " 21.07.2026 " , " series " : f " CPP-Y2S1- { lesson [ ' number ' ] } " , " document_type " : " karta pracy " ,
" tool " : " card-layouts " , " sheet " : " 1 / 1 " , " show_qr " : False , " show_repository_qr " : False ,
" height_cm " : 3 , " repeat_on_every_page " : True , " replace_front_matter " : True ,
} ,
" front_page_scope " : { " title " : " Cel karty " , " content_tex " : lesson [ " mission " ] , " scope_title " : " Zakres tasków " , " scope_table " : { " headers " : [ " Krok " , " Numer tasku " , " Najważniejsza idea " , " Priorytet " , " Status " , " Version " ] , " rows " : scope_rows } } ,
" learning_effects " : {
f " { prefix } .EN01 " : { " text " : f " Uczeń wyjaśnia reguły C++20 związane z tematem: { lesson [ ' topic ' ] } . " , " label " : " Model języka " , " bloom_level " : " Analiza " , " assessment_criteria " : [ f " { prefix } .KW01 " ] } ,
f " { prefix } .EK01 " : { " text " : " Uczeń implementuje, kompiluje i weryfikuje trzy małe eksperymenty C++20. " , " label " : " Eksperyment C++ " , " bloom_level " : " Zastosowanie " , " assessment_criteria " : [ f " { prefix } .KW01 " ] } ,
} ,
" assessment_criteria " : { f " { prefix } .KW01 " : { " text " : " Trzy taski kompilują się bez ostrzeżeń i zwracają dokładny, testowany wynik. " , " learning_effects " : [ f " { prefix } .EN01 " , f " { prefix } .EK01 " ] } } ,
" educational_requirements " : {
f " { prefix } .WE01 " : { " text " : lesson [ " mission " ] , " label " : lesson [ " topic " ] , " learning_effects " : [ f " { prefix } .EN01 " , f " { prefix } .EK01 " ] ,
" learning_tree " : { " schema " : " we-learning-tree.v1 " , " policy " : " Każde twierdzenie o języku ma dowód w kodzie, wyniku programu albo symbolach binarnych. " ,
" ogolne " : [ { " tree_id " : f " { prefix } .WE01.OG.IP.01 " , " effect_ref " : f " { prefix } .EN01 " , " display " : f " EN IP { prefix } " , " text " : " Uczeń przewiduje zachowanie programu na podstawie reguł języka. " , " kw " : [ { " criterion_ref " : f " { prefix } .KW01 " , " display " : f " KW IP { prefix } " , " text " : " Uzasadnia przewidywany wynik. " } ] } ] ,
" zawodowe " : [ { " tree_id " : f " { prefix } .WE01.TECH.I4.01 " , " effect_ref " : f " { prefix } .EK01 " , " display " : f " EK INF04 { prefix } " , " text " : " Uczeń buduje i testuje program C++20. " , " kw " : [ { " criterion_ref " : f " { prefix } .KW01 " , " display " : f " KW INF04 { prefix } " , " text " : " Uzyskuje trzy komunikaty PASS. " } ] } ] ,
} ,
}
} ,
" sections " : sections , " tasks " : tasks , " tasks_order " : [ " task01 " , " task02 " , " task03 " ] ,
" mission " : lesson [ " mission " ] ,
" objectives " : [ " Wyjaśnisz badaną regułę C++20. " , " Skompilujesz program z pełnym zestawem ostrzeżeń. " , " Porównasz dokładny wynik i ślad debuggera. " ] ,
" agenda " : [ { " time " : " 0--15 min " , " work " : " Model i przewidywanie. " } , { " time " : " 15--45 min " , " work " : " Trzy eksperymenty kodowe. " } , { " time " : " 45--60 min " , " work " : " Debug, wnioski i raport. " } ] ,
" tech_stack " : { " software " : [ { " name " : " C++20 " , " use " : " standard języka " } , { " name " : " GCC/GDB " , " use " : " kompilacja i dowód wykonania " } , { " name " : " stemctl " , " use " : " izolowany profil native-amd64 " } ] , " hardware " : [ { " name " : " Brak " , " use " : " laboratorium kontenerowe " } ] } ,
" container_test " : { " title " : " Test w profilu native-amd64 " , " commands " : [ f " stemctl test native-amd64 cpp { lesson [ ' id ' ] } 1 " , f " stemctl test native-amd64 cpp { lesson [ ' id ' ] } 2 " , f " stemctl debug native-amd64 cpp { lesson [ ' id ' ] } 3 " ] , " pass_condition " : " Trzy wyniki zgodne z expected i niepusty debug.log. " } ,
" safety_rules " : [ { " title " : " Brak niezdefiniowanego zachowania " , " body " : " Kod testowy nie dereferencjonuje nullptr i nie używa obiektu po zakończeniu czasu życia. " } , { " title " : " Jedna zmienna naraz " , " body " : " Każdy task izoluje jedną regułę języka i ma deterministyczny stdout. " } ] ,
" hardware_procedure " : [ { " step " : " 1 " , " action " : " Przewidź wynik. " , " condition " : " Reguła języka zapisana przed uruchomieniem. " } , { " step " : " 2 " , " action " : " Uruchom trzy taski. " , " condition " : " Trzy komunikaty PASS. " } , { " step " : " 3 " , " action " : " Odczytaj debug.log. " , " condition " : " Debugger zatrzymał program w main. " } ] ,
" references " : [ ] ,
}
def generate_lesson ( lesson : dict ) - > None :
repo = OUTPUT / lesson [ " repo " ]
if repo . exists ( ) and ( repo / " .git " ) . exists ( ) :
raise SystemExit ( f " Refusing to overwrite Git repository: { repo } " )
if repo . exists ( ) :
shutil . rmtree ( repo )
repo . mkdir ( parents = True )
shutil . copytree ( TEMPLATE / " scripts " , repo / " scripts " )
shutil . copy2 ( TEMPLATE / " .gitignore " , repo / " .gitignore " )
( repo / " web " ) . mkdir ( parents = True )
shutil . copy2 ( TEMPLATE / " web " / " app.css " , repo / " web " / " app.css " )
( repo / " doc " / " pdf " ) . mkdir ( parents = True )
write ( repo / " doc " / " pdf " / " .gitkeep " , " " )
package = json . loads ( ( TEMPLATE / " package.json " ) . read_text ( encoding = " utf-8 " ) )
package [ " name " ] = f " mpabi-inf-cpp-y2s1- { lesson [ ' number ' ] } - { lesson [ ' slug ' ] } "
write ( repo / " package.json " , json . dumps ( package , ensure_ascii = False , indent = 2 ) + " \n " )
lock = json . loads ( ( TEMPLATE / " package-lock.json " ) . read_text ( encoding = " utf-8 " ) )
lock [ " name " ] = package [ " name " ]
if " packages " in lock and " " in lock [ " packages " ] :
lock [ " packages " ] [ " " ] [ " name " ] = package [ " name " ]
write ( repo / " package-lock.json " , json . dumps ( lock , ensure_ascii = False , indent = 2 ) + " \n " )
write ( repo / " stem-card.yaml " , """
schema: 1
targets:
native: { profile: native-amd64, actions: [build, test, run, debug]}
actions:
build: [bash, tools/card-action.sh, build]
test: [bash, tools/card-action.sh, test]
run: [bash, tools/card-action.sh, run]
debug: [bash, tools/card-action.sh, debug]
artifacts: { directory: .stem/artifacts}
""" )
write ( repo / " tools " / " card-action.sh " , CARD_ACTION , executable = True )
write ( repo / " Makefile " , """
TASK ?= task01
.PHONY: build test run trace clean
build:
\t @CARD_ROOT= " $(CURDIR) " tools/card-action.sh build
test:
\t @CARD_ROOT= " $(CURDIR) " STEM_TASK= " $(TASK) " tools/card-action.sh test
run: test
trace:
\t @CARD_ROOT= " $(CURDIR) " STEM_TASK= " $(TASK) " tools/card-action.sh debug
clean:
\t @find .stem/artifacts -mindepth 1 -maxdepth 7 -type f -delete 2>/dev/null || true
""" )
flow = [ " schema: 1 " , f " card: cpp/ { lesson [ ' id ' ] } " , f " title: { lesson [ ' title ' ] } " , " steps: " ]
for index , ( title , _prompt , _expected , _files ) in enumerate ( lesson [ " tasks " ] , 1 ) :
flow . extend ( [
f " - id: task- { index : 02d } " , f " task: task { index : 02d } " , " action: test " , " profile: native-amd64 " , " target: native " ,
f " command: stemctl test native-amd64 cpp { lesson [ ' id ' ] } { index } " ,
f " board: " , f " show: [source, stdout, binary] " , f " highlight: [cxx20, { lesson [ ' slug ' ] . replace ( ' - ' , ' _ ' ) } ] " ,
f " question: { title } — jaka reguła języka wyjaśnia dokładny wynik? " ,
f " evidence: [compile_warnings_zero, expected_stdout, exit_zero] " ,
] )
write ( repo / " lesson-flow.yaml " , " \n " . join ( flow ) + " \n " )
for index , ( _title , _prompt , expected , files ) in enumerate ( lesson [ " tasks " ] , 1 ) :
task = f " task { index : 02d } "
for name , source in files . items ( ) :
write ( repo / " src " / task / name , source )
write ( repo / " tests " / " expected " / f " { task } .txt " , expected + " \n " )
document_uuid = str ( uuid . uuid5 ( UUID_NAMESPACE , lesson [ " repo " ] ) )
source = card_source ( lesson , document_uuid )
write ( repo / " json " / " card_source.json " , json . dumps ( source , ensure_ascii = False , indent = 2 ) + " \n " )
write ( repo / " doc " / " main.tex " , f """
\\ newcommand {{ \\ PublisherDomain }} {{ mpabi }}
\\ newcommand {{ \\ CardArea }} {{ inf }}
\\ newcommand {{ \\ CardSeries }} {{ cpp-y2s1 }}
\\ newcommand {{ \\ CardNumber }} {{ { lesson [ ' number ' ] } }}
\\ newcommand {{ \\ CardSlug }} {{ { lesson [ ' slug ' ] } }}
\\ newcommand {{ \\ CardVersion }} {{ v00.01 }}
\\ newcommand {{ \\ DocumentUUID }} {{ { document_uuid } }}
""" )
expected_rows = " \n " . join ( f " | Task { index : 02d } | ` { title } ` | ` { expected . replace ( chr ( 10 ) , ' / ' ) } ` | " for index , ( title , _prompt , expected , _files ) in enumerate ( lesson [ " tasks " ] , 1 ) )
write ( repo / " README.md " , f """
# { lesson [ ' id ' ] } — { lesson [ ' title ' ] }
Karta { lesson [ ' number ' ] } /10 kursu **C++ · Rok 2 · Semestr 1**. Standard: C++20.
Oś merytoryczna: { lesson [ ' chapter ' ] } . Materiał książkowy jest parafrazowany;
repozytorium zawiera samodzielne eksperymenty i testy, a nie kopię książki.
## Taski
| Task | Temat | Dokładny dowód stdout |
| --- | --- | --- |
{ expected_rows }
Każdy task jest osobnym programem w `src/task0N`. Test kompiluje wszystkie
jednostki translacji poleceniem zgodnym z `-std=c++20 -Wall -Wextra
-Wpedantic -Werror`, uruchamia program i porównuje dokładny wynik.
## Uruchomienie
```bash
stemctl build native-amd64 cpp { lesson [ ' id ' ] } 1
stemctl test native-amd64 cpp { lesson [ ' id ' ] } 1
stemctl test native-amd64 cpp { lesson [ ' id ' ] } 2
stemctl debug native-amd64 cpp { lesson [ ' id ' ] } 3
```
Lokalnie można użyć `make build`, `make test TASK=task02` i
`make trace TASK=task03`. Ślad debuggera trafia do `.stem/artifacts`.
## Granica semestru
Ta seria nie uczy API RTOS, zaawansowanych kontenerów STL ani
współbieżności. Są to osobne bloki następnych semestrów opisanych w
`/home/user/dev/keys/cxx.md`.
""" )
answer_lines = [ f " # Odpowiedzi — { lesson [ ' id ' ] } " , " " , lesson [ " mission " ] , " " ]
for index , ( title , prompt , expected , files ) in enumerate ( lesson [ " tasks " ] , 1 ) :
answer_lines . extend ( [
f " ## Task { index : 02d } — { title } " , " " , prompt , " " ,
f " Oczekiwany stdout: ` { expected . replace ( chr ( 10 ) , ' / ' ) } `. " , " " ,
f " Pliki dowodu: { ' , ' . join ( f ' `src/task { index : 02d } / { name } ` ' for name in sorted ( files ) ) } . " , " " ,
" Wynik nie jest oceniany wzrokowo: test kompiluje kod bez ostrzeżeń, uruchamia go i wykonuje dokładne `diff` z plikiem `tests/expected`. " , " " ,
] )
write ( repo / " answers " / " solutions.md " , " \n " . join ( answer_lines ) )
write ( repo / " tests " / " card_contract.test.mjs " , f """
import assert from ' node:assert/strict ' ;
import {{ access, readFile }} from ' node:fs/promises ' ;
import test from ' node:test ' ;
const source = JSON.parse(await readFile(new URL( ' ../json/card_source.json ' , import.meta.url), ' utf8 ' ));
test( ' { lesson [ ' id ' ] } identity and task order are stable ' , () => {{
assert.equal(source.card.number, ' { lesson [ ' number ' ] } ' );
assert.equal(source.card.uuid, ' { document_uuid } ' );
assert.deepEqual(source.tasks_order, [ ' task01 ' , ' task02 ' , ' task03 ' ]);
assert.match(JSON.stringify(source), /C \\ + \\ +20/);
}} );
test( ' all task programs and expected outputs exist ' , async () => {{
for (const task of [ ' task01 ' , ' task02 ' , ' task03 ' ]) {{
await access(new URL(`../src/$ {{ task }} /main.cpp`, import.meta.url));
await access(new URL(`./expected/$ {{ task }} .txt`, import.meta.url));
}}
}} );
""" )
shutil . copy2 ( TEMPLATE / " tests " / " card_state_db.test.mjs " , repo / " tests " / " card_state_db.test.mjs " )
def main ( ) - > None :
OUTPUT . mkdir ( parents = True , exist_ok = True )
for lesson in LESSONS :
generate_lesson ( lesson )
print ( f " generated { lesson [ ' id ' ] } { lesson [ ' repo ' ] } " )
if __name__ == " __main__ " :
main ( )