{"id":36974795,"url":"https://github.com/guyernest/mdbook-exercises","last_synced_at":"2026-01-13T22:02:13.587Z","repository":{"id":330669402,"uuid":"1123523360","full_name":"guyernest/mdbook-exercises","owner":"guyernest","description":"Parser and renderer for exercise sections for mdbook","archived":false,"fork":false,"pushed_at":"2025-12-28T05:51:37.000Z","size":144,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-28T20:31:04.785Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/guyernest.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-27T03:57:50.000Z","updated_at":"2025-12-28T06:03:42.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/guyernest/mdbook-exercises","commit_stats":null,"previous_names":["guyernest/mdbook-exercises"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/guyernest/mdbook-exercises","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guyernest%2Fmdbook-exercises","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guyernest%2Fmdbook-exercises/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guyernest%2Fmdbook-exercises/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guyernest%2Fmdbook-exercises/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/guyernest","download_url":"https://codeload.github.com/guyernest/mdbook-exercises/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guyernest%2Fmdbook-exercises/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28400564,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T14:36:09.778Z","status":"ssl_error","status_checked_at":"2026-01-13T14:35:19.697Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-13T22:02:13.523Z","updated_at":"2026-01-13T22:02:13.581Z","avatar_url":"https://github.com/guyernest.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mdbook-exercises\n\nA preprocessor for [mdBook](https://rust-lang.github.io/mdBook/) that adds interactive exercise blocks with hints, solutions, and optional Rust Playground integration for testing.\n\n## Features\n\n- **Exercise metadata** - Difficulty levels, time estimates, prerequisites\n- **Learning objectives** - Structured thinking/doing outcomes\n- **Discussion prompts** - Reflection questions before coding\n- **Starter code** - Editable code blocks with syntax highlighting\n- **Progressive hints** - Collapsible, leveled hints that reveal incrementally\n- **Solutions** - Hidden by default, reveal on demand\n- **Test integration** - Run tests via Rust Playground or locally\n- **Progress tracking** - LocalStorage-based completion tracking\n- **Accessible** - Keyboard navigation, screen reader support\n\n## Installation\n\n### From crates.io\n\n```bash\ncargo install mdbook-exercises\n```\n\n### From source\n\n```bash\ngit clone https://github.com/guyernest/mdbook-exercises\ncd mdbook-exercises\ncargo install --path .\n```\n\n### From GitHub directly\n\n```bash\ncargo install --git https://github.com/guyernest/mdbook-exercises\n```\n\n## Quick Start\n\n### 1. Add to your book.toml\n\n```toml\n[preprocessor.exercises]\n```\n\n### 2. Include the CSS and JavaScript assets\n\nCopy the assets to your book's theme directory:\n\n```bash\nmkdir -p src/theme\ncp /path/to/mdbook-exercises/assets/exercises.css src/theme/\ncp /path/to/mdbook-exercises/assets/exercises.js src/theme/\n```\n\nThen add to your `book.toml`:\n\n```toml\n[output.html]\nadditional-css = [\"theme/exercises.css\"]\nadditional-js = [\"theme/exercises.js\"]\n```\n\n### 3. Create an exercise in Markdown\n\n````markdown\n# Exercise: Hello World\n\n::: exercise\nid: hello-world\ndifficulty: beginner\ntime: 10 minutes\n:::\n\nWrite a function that returns a greeting.\n\n::: starter file=\"src/lib.rs\"\n```rust\n/// Returns a greeting for the given name\npub fn greet(name: \u0026str) -\u003e String {\n    // TODO: Return \"Hello, {name}!\"\n    todo!()\n}\n```\n:::\n\n::: hint level=1\nUse the `format!` macro to create a formatted string.\n:::\n\n::: hint level=2\n```rust\nformat!(\"Hello, {}!\", name)\n```\n:::\n\n::: solution\n```rust\npub fn greet(name: \u0026str) -\u003e String {\n    format!(\"Hello, {}!\", name)\n}\n```\n:::\n\n::: tests mode=playground\n```rust\n#[test]\nfn test_greet() {\n    assert_eq!(greet(\"World\"), \"Hello, World!\");\n}\n\n#[test]\nfn test_greet_name() {\n    assert_eq!(greet(\"Alice\"), \"Hello, Alice!\");\n}\n```\n:::\n````\n\n### 4. Build your book\n\n```bash\nmdbook build\n```\n\n## Examples and Live Demo\n\n- Browse ready-to-run examples in the `examples/` folder:\n  - `hello-world.md` — Beginner Rust exercise with hints, solution, and Playground tests\n  - `calculator.md` — Intermediate Rust with local tests\n  - `multilang-python.md` — Python exercise (mode=local)\n  - `multilang-js.md` — JavaScript exercise (mode=local)\n  - `solution-reveal.md` — Demonstrates `reveal=always`\n  - `ch02-environment-setup.md` — Setup exercise (ID suffix `-setup`, position `00`)\n- Minimal sample mdBook (includes examples via `{{#exercise ...}}`): see `sample-book/`\n- Live Demo: https://guyernest.github.io/mdbook-exercises/\n\n## Directive Reference\n\n### Exercise Block\n\nDefines metadata for the exercise:\n\n````markdown\n::: exercise\nid: unique-exercise-id\ndifficulty: beginner | intermediate | advanced\ntime: 20 minutes\nprerequisites:\n  - exercise-id-1\n  - exercise-id-2\n:::\n````\n\n### Objectives Block\n\nLearning outcomes in two categories:\n\n````markdown\n::: objectives\nthinking:\n  - Understand concept X\n  - Recognize pattern Y\n\ndoing:\n  - Implement function Z\n  - Write tests for edge cases\n:::\n````\n\n### Discussion Block\n\nPre-exercise reflection prompts:\n\n````markdown\n::: discussion\n- Why might we want to do X?\n- What are the tradeoffs of approach Y?\n:::\n````\n\n### Starter Block\n\nEditable code for the student to complete:\n\n````markdown\n::: starter file=\"src/main.rs\" language=rust\n```rust\nfn main() {\n    // TODO: Your code here\n}\n```\n:::\n````\n\n**Attributes:**\n- `file` - Suggested filename (displayed in header)\n- `language` - Syntax highlighting language (default: rust)\n\nCode fence info:\n- You can also include the language and optional attributes in the fenced code block info string.\n- Supported keys: `filename` or `file` for suggested filename.\n\nExamples:\n\n````markdown\n::: starter\n```rust,filename=src/lib.rs\npub fn run() {}\n```\n:::\n````\n\n````markdown\n::: starter file=\"src/main.rs\"\n```rust\nfn main() {}\n```\n:::\n````\n\nPrecedence:\n- If both the directive and the fence info specify the same property, the directive attribute wins. For example, `file=\"src/main.rs\"` overrides `filename=...` in the fence.\n\n### Hint Block\n\nProgressive hints with levels:\n\n````markdown\n::: hint level=1 title=\"Getting Started\"\nFirst, consider...\n:::\n\n::: hint level=2\nHere's more detail...\n:::\n\n::: hint level=3 title=\"Almost There\"\n```rust\n// Nearly complete solution\n```\n:::\n````\n\n**Attributes:**\n- `level` - Hint number (1, 2, 3, etc.)\n- `title` - Optional title for the hint\n\n### Solution Block\n\nThe complete solution, hidden by default:\n\n````markdown\n::: solution\n```rust\nfn solution() {\n    // Complete implementation\n}\n```\n\n### Explanation\n\nWhy this solution works...\n:::\n````\n\nAttributes:\n- `reveal` — `on-demand` | `always` | `never` (controls visibility)\n\nRendering:\n- The `reveal` attribute controls visibility:\n  - `on-demand`: Hidden by default unless globally configured to reveal\n  - `always`: Shown expanded regardless of global config\n  - `never`: Kept hidden; the UI hides the toggle\n- Hidden-by-default solutions have a \"Show Solution\" control\n\n### Tests Block\n\nTest code that can optionally run in the browser:\n\n````markdown\n::: tests mode=playground\n```rust\n#[test]\nfn test_example() {\n    assert!(true);\n}\n```\n:::\n````\n\n**Attributes:**\n- `mode` - Either `playground` (run in browser) or `local` (display only)\n- `language` - Programming language (defaults to the fence language, if present)\n\nWhen `mode=playground`:\n- A \"Run Tests\" button appears\n- User code is combined with test code\n- Sent to play.rust-lang.org for execution\n- Results displayed inline\n\nCode fence info:\n- The fence language (e.g., ```` ```rust ````) sets `language` if the directive doesn’t specify it.\n- If both are present, the directive attribute `language=...` takes precedence over the fence.\n\nImplementation details:\n- Playground execution runs tests as a library (crateType `lib`), combining starter code with test code.\n\n### Reflection Block\n\nPost-exercise questions:\n\n````markdown\n::: reflection\n- What did you learn from this exercise?\n- How would you extend this solution?\n:::\n````\n\n## Browser Features\n\n### Test Execution\n\nWhen tests have `mode=playground`, the preprocessor generates JavaScript that:\n\n1. Captures the user's code from the editable starter block\n2. Combines it with the test code\n3. Sends to the Rust Playground API\n4. Displays compilation errors or test results\n\n**Limitations:**\n- Only works with `std` library (no external crates)\n- Subject to playground rate limits\n- Requires internet connection\n- ~5 second execution timeout\n\nFor exercises requiring external crates, use `mode=local` and guide users to run `cargo test` locally.\n\n### Progress Tracking\n\nExercise completion is tracked in localStorage:\n\n- Checkboxes next to learning objectives\n- \"Mark Complete\" button for exercises\n- Progress persists across sessions\n- No server required\n\n### Accessibility\n\n- All interactive elements are keyboard-accessible\n- Collapsible sections use proper ARIA attributes\n- High contrast mode supported\n- Screen reader announcements for test results\n\n## Configuration\n\n### book.toml options\n\n```toml\n[preprocessor.exercises]\n# Enable/disable the preprocessor\nenabled = true\n# Show all hints by default (useful for instructor view)\nreveal_hints = false\n\n# Show solutions by default\nreveal_solutions = false\n\n# Enable playground integration\nplayground = true\n\n# Custom playground URL (for private instances)\nplayground_url = \"https://play.rust-lang.org\"\n\n# Enable progress tracking\nprogress_tracking = true\n\n# Automatically copy CSS/JS assets to your book's theme directory\nmanage_assets = false\n```\n\n## Library Usage\n\n`mdbook-exercises` can be used as a library for parsing exercise markdown:\n\n```rust\nuse mdbook_exercises::{parse_exercise, Exercise};\n\nlet markdown = std::fs::read_to_string(\"exercise.md\")?;\nlet exercise = parse_exercise(\u0026markdown)?;\n\nprintln!(\"Exercise: {}\", exercise.metadata.id);\nprintln!(\"Difficulty: {:?}\", exercise.metadata.difficulty);\nprintln!(\"Hints: {}\", exercise.hints.len());\n```\n\n### Feature Flags\n\n```toml\n[dependencies]\n# Parser only (no rendering, no mdBook dependency)\nmdbook-exercises = { version = \"0.1\", default-features = false }\n\n# With HTML rendering (no mdBook dependency)\nmdbook-exercises = { version = \"0.1\", default-features = false, features = [\"render\"] }\n\n# Full mdBook preprocessor (default)\nmdbook-exercises = { version = \"0.1\" }\n```\n\n## Integration with MCP Servers\n\nFor AI-assisted learning experiences, exercise files can be paired with `.ai.toml` files containing AI-specific instructions. The parser extracts structured data that MCP servers can use:\n\n```rust\nuse mdbook_exercises::{parse_exercise, Exercise};\n\n// In your MCP server\nlet exercise = parse_exercise(\u0026markdown)?;\n\n// Access structured data for AI guidance\nlet starter_code = \u0026exercise.starter.as_ref().unwrap().code;\nlet hints: Vec\u003c\u0026str\u003e = exercise.hints.iter().map(|h| h.content.as_str()).collect();\nlet solution = \u0026exercise.solution.as_ref().unwrap().code;\n```\n\nSee [DESIGN.md](./DESIGN.md) for details on MCP integration patterns.\n\n## Examples\n\nSee the [examples](./examples) directory for complete exercise examples:\n\n- `hello-world.md` - Basic exercise structure\n- `calculator.md` - Multi-hint exercise with tests\n- `multilang-python.md` - Non-Rust example (Python), local tests\n- `double-exercise.md` - Two exercises in one chapter (use include syntax in mdBook for best results)\n\n**Live Demo:** View the rendered examples at [guyernest.github.io/mdbook-exercises](https://guyernest.github.io/mdbook-exercises/)\n\n### Course Developer Guides\n\n- Organizing and integrating exercises (include-only pattern): see `docs/exercises-integration.md`.\n- Setup exercises (ID suffix `-setup`, position `00`): see `docs/setup-exercises.md`.\n\n### Sample mdBook\n\nSee the [sample-book](./sample-book) directory for a minimal mdBook configured to use mdbook-exercises (and optionally mdbook-quiz). It includes:\n- `book.toml` with `[preprocessor.exercises]` and optional `[preprocessor.quiz]`\n- `src/SUMMARY.md`, `src/intro.md`, and `src/exercises.md`\n- `src/exercises.md` demonstrates including exercises via `{{#exercise ...}}` from this repository’s examples.\n\n### Using with mdbook-quiz\n\nYou can use mdbook-exercises alongside mdbook-quiz. A common book.toml setup looks like:\n\n```toml\n[preprocessor.quiz]\n# mdbook-quiz configuration here\n\n[preprocessor.exercises]\nenabled = true\nmanage_assets = true   # copies exercises.css/js to src/theme/\nreveal_hints = false\nreveal_solution = false\nplayground = true\nprogress_tracking = true\n\n[output.html]\n# mdBook will load the installed assets from your theme dir\nadditional-css = [\"theme/exercises.css\"]\nadditional-js  = [\"theme/exercises.js\"]\n```\n\nAt build time you will see a startup log message similar to mdbook-quiz:\n\n```\n[INFO] (mdbook-exercises): Running the mdbook-exercises preprocessor (vX.Y.Z)\n```\n\n## Precedence Rules\n\n- Starter:\n  - Directive attributes override fence info (e.g., `file=\"...\"` beats `filename=...`).\n  - Fence language sets default when `language` attribute is omitted.\n- Tests:\n  - Directive `language=...` overrides fence language. Fence language sets default when omitted.\n  - `mode` is taken from directive attributes.\n- Solution:\n  - `reveal` on the solution overrides global config (`reveal_solution`).\n\n## Contributing\n\nContributions are welcome! Please open an issue or submit a pull request at [GitHub](https://github.com/guyernest/mdbook-exercises).\n\n## License\n\nMIT OR Apache-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fguyernest%2Fmdbook-exercises","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fguyernest%2Fmdbook-exercises","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fguyernest%2Fmdbook-exercises/lists"}