From c9641c2f983112df88b0cea55a9629b93e01b736 Mon Sep 17 00:00:00 2001 From: Iuri Matias Date: Thu, 28 May 2026 09:51:54 -0400 Subject: [PATCH 01/12] add executable tutorials support; convert tutorial-wrapping-c-library to an executable tutorial add executable tutorials support; convert tutorial-wrapping-c-library to an executable tutorial add executable tutorials update tutorial; generate steps clarify workdir usage update/fix tutorial --- .github/workflows/ci.yml | 60 ++ README.md | 23 + docs/spec.md | 368 +++++++ tests/tutorial-wrapping-c-library.test.yaml | 1051 +++++++++++++++++++ tools/run-tutorial | 16 + tools/tutorial_runner.py | 956 +++++++++++++++++ 6 files changed, 2474 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 docs/spec.md create mode 100644 tests/tutorial-wrapping-c-library.test.yaml create mode 100755 tools/run-tutorial create mode 100755 tools/tutorial_runner.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e07e329 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,60 @@ +name: Tutorial Tests + +on: + pull_request: + branches: [master, main] + push: + branches: [master, main] + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + tutorial-tests: + name: Tutorial Tests (${{ matrix.os }}) + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@main + + - name: Setup Cachix + uses: cachix/cachix-action@v15 + with: + name: logos-co + authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' + + - name: Install PyYAML + run: pip3 install --break-system-packages pyyaml + + - name: Test - Wrapping a C Library (scaffold + files + build + inspect) + run: | + python3 tools/tutorial_runner.py run \ + tests/tutorial-wrapping-c-library.test.yaml \ + --phase scaffold,files,build,inspect \ + --verbose + + - name: Test - Wrapping a C Library (logoscore) + run: | + python3 tools/tutorial_runner.py run \ + tests/tutorial-wrapping-c-library.test.yaml \ + --phase logoscore \ + --verbose + continue-on-error: true + + - name: Verify markdown generation + run: | + python3 tools/tutorial_runner.py generate \ + tests/tutorial-wrapping-c-library.test.yaml \ + -o /tmp/generated.md + echo "Generated markdown successfully" diff --git a/README.md b/README.md index 5d70d9b..1ef2539 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,29 @@ Step-by-step tutorials that build on each other. Each creates a working module y - **logos-dev-boost:** [Scaffolding Modules with logos-dev-boost](tutorial-dev-boost.md) — use the `logos-dev-boost` CLI to auto-generate modules from C library directories. Wraps libcalc (source-only) and sqlcipher (pre-built `.so`), including integration tests that create encrypted databases. Covers `--type module`, `--type full-app`, and `--lib-dir`. +## Executable Tutorials + +Tutorials have YAML specs in `tests/` that can be both **executed** (to verify they work) and used to **generate** the `.md` files. See [docs/spec.md](docs/spec.md) for the full format reference. + +```bash +# Run a tutorial end-to-end, writing to a directory you can inspect afterwards +python3 tools/tutorial_runner.py run tests/tutorial-wrapping-c-library.test.yaml \ + --workdir /tmp/my-tutorial-test --verbose + +# Run only specific phases (scaffold, files, build, inspect, logoscore) +python3 tools/tutorial_runner.py run tests/tutorial-wrapping-c-library.test.yaml \ + --phase scaffold,files,build --verbose + +# Re-run a single phase against a previous build +python3 tools/tutorial_runner.py run tests/tutorial-wrapping-c-library.test.yaml \ + --workdir /tmp/my-tutorial-test --phase inspect --verbose + +# Generate the .md tutorial from the YAML spec +python3 tools/tutorial_runner.py generate tests/tutorial-wrapping-c-library.test.yaml +``` + +The `--workdir` flag lets you point the runner at a directory of your choice — all files, builds, and artifacts end up there so you can inspect or re-use them. Without it, a temp directory is created and deleted after the run (use `--keep-workdir` to preserve it). + ## Example Modules Working module source code used by the tutorials: diff --git a/docs/spec.md b/docs/spec.md new file mode 100644 index 0000000..4e83d66 --- /dev/null +++ b/docs/spec.md @@ -0,0 +1,368 @@ +# Tutorial YAML Spec Format + +This document describes the YAML format used by `tools/tutorial_runner.py` to define executable tutorials. Each `.test.yaml` file is the **single source of truth** — it drives both: + +- **Execution** (`run`): steps are executed in a temp directory, commands run, outputs verified +- **Markdown generation** (`generate`): a `.md` tutorial is produced from the same YAML + +## Quick example + +```yaml +name: "My Tutorial" +output: my-tutorial.md + +intro: | + One-paragraph description of what this tutorial covers. + +what_you_build: "A short sentence describing the end result." + +what_you_learn: + - First learning objective + - Second learning objective + +prerequisites: + - "**Nix** with flakes enabled." + +sections: + - title: "Set Up the Project" + phase: scaffold + text: | + Intro paragraph for this section. + steps: + - title: "Create the directory" + run: "mkdir -p my-project" + + - title: "Write the config" + text: "Create `config.json`:" + file: + path: config.json + language: json + content: | + { "name": "example" } + + - title: "Build" + run: "nix build" + expect_contains: + - "Build successful" +``` + +## Top-level fields + +| Field | Required | Type | Description | +|-------|----------|------|-------------| +| `name` | yes | string | Tutorial title. Used as the `# heading` in generated markdown and in runner output. | +| `output` | no | string | Default output filename for `generate` (relative to the tutorial directory, e.g., `tutorial-wrapping-c-library.md`). Can be overridden with `-o`. | +| `intro` | no | string | Introductory paragraph(s). Rendered after the title in the markdown. Supports full markdown. | +| `what_you_build` | no | string | One-line summary prefixed with "**What you'll build:**" in the markdown. | +| `what_you_learn` | no | list of strings | Bullet list prefixed with "**What you'll learn:**". | +| `comparison` | no | string | Free-form markdown block rendered after the learning objectives (useful for comparison tables). | +| `prerequisites` | no | list of strings | Rendered as a bullet list under a `## Prerequisites` heading. Each item can contain markdown (code blocks, links, etc.). | +| `build_overrides` | no | map | Nix `--override-input` flags for the runner. Keys are input names, values are relative paths to local repos. Only affects execution, not generation. | +| `sections` | yes | list | The tutorial content. See below. | + +## Sections + +Each section becomes a `## heading` in the markdown. Sections with a `phase` get auto-numbered as "Step N: Title". + +| Field | Required | Type | Description | +|-------|----------|------|-------------| +| `title` | yes | string | Section heading. | +| `phase` | no | string | Groups the section for selective execution. One of: `scaffold`, `files`, `build`, `inspect`, `logoscore`, `basecamp`. Sections with a phase are numbered as "Step N" in markdown. Sections without a phase render as plain `## Title`. | +| `text` | no | string | Introductory prose rendered before the steps. Supports full markdown (tables, blockquotes, code blocks, etc.). | +| `steps` | no | list | Ordered list of steps. See below. | + +Sections without `steps` are prose-only — the `text` is rendered as-is. This is useful for reference sections like "Troubleshooting" or "Common Patterns". + +## Steps + +Steps are the core building blocks. Each step can combine multiple fields. The rendering order in the generated markdown is: + +1. `title` → `### heading` +2. `text` → prose paragraph +3. `scaffold` → bash code block (template init) +4. `file` → code block with file contents +5. `run` → bash code block +6. `post_text` → prose after the action +7. `extra_run` → additional command block (no heading) + +A step without a `title` renders its content inline under the previous heading — useful for continuation content like "Then run:" followed by a code block. + +### Step fields + +#### `title` (string, optional) + +Rendered as a `### heading` in the markdown. Steps without a title don't get a heading — their content flows under the previous step's heading. + +#### `text` (string, optional) + +Prose rendered before any action. Supports full markdown. + +#### `scaffold` (object, optional) + +Initializes a project from a Nix flake template. + +| Subfield | Type | Description | +|----------|------|-------------| +| `template` | string | The flake template URL (e.g., `github:logos-co/logos-module-builder#with-external-lib`). | +| `code_block` | string | The exact bash content to show in the markdown. If omitted, just the `nix flake init -t