We resolved two issues when running turtle programs with `--export-svg`:
1. **Missing filename**: Previously, running `--export-svg` without a
filename caused the CLI parser to ignore the flag and open the GUI
window. Now, it emits `Error: --export-svg: option requires an
argument` and terminates with exit code 1.
2. **Missing `svg` feature**: Previously, running `--export-svg` without
the `svg` feature enabled ignored the flag if no filename was passed
and opened the window. Now, any invocation of `--export-svg` without
`--features svg` emits `Error: SVG export feature is not enabled.
Please rebuild with --features svg` and terminates with exit code 1.
---
- Added [`pico-args = { version = "0.5", features = ["eq-separator"]
}`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/Cargo.toml)
under `[dependencies]`.
- In
[`turtle-lib/src/export.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/export.rs):
- Added `MissingFeature(String)` to `ExportError` for clean error
display.
- Implemented `parse_svg_export_from_args(args: &mut
pico_args::Arguments) -> Result<Option<String>, pico_args::Error>`
to parse `--export-svg <file>` and `--export-svg=<file>`, treating
empty or flag arguments as missing values (`OptionWithoutAValue`).
- Updated `parse_svg_export_arg() -> Option<String>` for backward
compatibility.
- In `handle_svg_export`:
- Under `#[cfg(not(feature = "svg"))]`: detects any presence of
`--export-svg`, outputs `Error: SVG export feature is not enabled.
Please rebuild with --features svg`, and exits with code 1.
- Under `#[cfg(feature = "svg")]`: handles missing arguments with
`Error: --export-svg: option requires an argument` (exit 1), valid
exports (exit 0), and leaves unknown arguments untouched so
Macroquad or user flags continue to work.
- Added unit tests covering all flag parsing variants and error
displays.
- In
[`turtle-lib-macros/src/lib.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib-macros/src/lib.rs):
- Simplified the macro expansion in `turtle_main` to delegate directly
to `turtle_lib::export::handle_svg_export(&mut build_commands);`.
- Updated documentation comments to match.
---
```bash
cargo test --package turtle-lib
cargo test --package turtle-lib --features svg
cargo test --package turtle-lib-macros
cargo clippy --package turtle-lib -- -Wclippy::pedantic
-Aclippy::cast_precision_loss -Aclippy::cast_sign_loss
-Aclippy::cast_possible_truncation
cargo clippy --package turtle-lib --features svg -- -Wclippy::pedantic
-Aclippy::cast_precision_loss -Aclippy::cast_sign_loss
-Aclippy::cast_possible_truncation
cargo clippy --package turtle-lib-macros -- -Wclippy::pedantic
```
- **Unit tests**: All 29 unit tests in `turtle-lib` and 18 unit tests in
`turtle-lib-macros` passed.
- **Doctests**: All 34 doctests passed.
- **Clippy**: Passed with zero warnings across all crates and feature
configurations.
| Scenario | Command | Result | Exit Code | Window Opened? |
|---|---|---|---|---|
| Missing feature, missing filename | `cargo run --example hello_turtle
-- --export-svg` | `Error: SVG export feature is not enabled. Please
rebuild with --features svg` | 1 | No |
| Missing feature, with filename | `cargo run --example hello_turtle --
--export-svg test.svg` | `Error: SVG export feature is not enabled.
Please rebuild with --features svg` | 1 | No |
| Feature enabled, missing filename | `cargo run --example hello_turtle
--features svg -- --export-svg` | `Error: --export-svg: option requires
an argument` | 1 | No |
| Feature enabled, with filename | `cargo run --example hello_turtle
--features svg -- --export-svg /tmp/pico_test.svg` | `SVG exported
successfully to: /tmp/pico_test.svg` | 0 | No |
| Feature enabled, equals syntax | `cargo run --example hello_turtle
--features svg -- --export-svg=/tmp/pico_test_eq.svg` | `SVG exported
successfully to: /tmp/pico_test_eq.svg` | 0 | No |
| Feature enabled, extra flags | `cargo run --example hello_turtle
--features svg -- --export-svg /tmp/pico_test_extra.svg --custom-flag` |
`SVG exported successfully to: /tmp/pico_test_extra.svg` | 0 | No |
| Normal run (no flag) | `cargo run --example hello_turtle` |
Interactive window opens normally | 0 | Yes |
Clarified and improved error reporting when drawing routines invoke
Macroquad window or rendering functions (such as `screen_width()` or
`screen_height()`) during headless SVG export (`--export-svg`).
[export.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/export.rs)
- Added `ExportError::Execution(String)` variant to represent drawing
execution failures and panics.
- Implemented `std::fmt::Display` and `std::error::Error` for
`ExportError`.
- Created `PanicHookGuard` RAII struct ensuring any installed panic
hooks are automatically restored after execution.
- In `run_headless_svg_export`:
- Chains onto the existing panic hook to preserve standard panic
backtrace and line number information.
- Detects if the panic originated from uninitialized Macroquad context
(`THREAD_ID.is_some()`).
- Emits a clear, prominent diagnostic banner explaining that
window/GUI functions are unavailable in headless mode.
- Catches the panic via `std::panic::catch_unwind` and returns
`Err(ExportError::Execution(...))`.
- In `handle_svg_export`, uses `{e}` (Display) rather than `{e:?}`
(Debug) for cleaner error reporting.
[lib.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib-macros/src/lib.rs)
- Updated the expanded `main` function generated by `#[turtle_main]` to
format errors with `{}` (Display) instead of `{:?}` (Debug).
[sierpinski_triangle.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/examples/sierpinski_triangle.rs)
- Added documentation notes to the module header and
`sierpinski_triangle_auto` noting that the example requires an active
graphics window and cannot be exported to SVG headlessly because it
queries window dimensions.
---
Executed:
```bash
RUST_BACKTRACE=1 cargo run --example sierpinski_triangle --features svg
-- --export-svg sier.svg
```
Output:
```
thread 'main' (49702) panicked at
/home/dietrich/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/macroquad-0.4.16/src/lib.rs:172:13:
assertion failed: THREAD_ID.is_some()
stack backtrace:
0: __rustc::rust_begin_unwind
...
5: macroquad::window::screen_width
6: sierpinski_triangle::sierpinski_triangle_auto
at ./turtle-lib/examples/sierpinski_triangle.rs:100:20
7: sierpinski_triangle::draw_sierpinski
at ./turtle-lib/examples/sierpinski_triangle.rs:39:5
...
================================================================================
Headless SVG Export Note:
A Macroquad window/rendering function (e.g. `screen_width()`,
`screen_height()`,
or input check) was called while running in headless export mode.
Headless export does not initialize a graphics window. To resolve this:
- Use relative turtle commands or fixed coordinates instead of window
queries, or
- Run the program in windowed GUI mode without the `--export-svg`
flag.
================================================================================
Error exporting SVG: execution error: Drawing function called Macroquad
window/GUI functions (e.g. `screen_width()`, `screen_height()`) which
are unavailable in headless SVG export mode.
```
- Process exited cleanly with exit code 1.
Executed:
```bash
cargo run --example koch --features svg -- --export-svg /tmp/koch.svg
```
Output:
```
SVG exported successfully to: /tmp/koch.svg
```
Exit code 0.
```bash
cargo test --package turtle-lib
cargo clippy --package turtle-lib --features svg -- -Wclippy::pedantic
-Aclippy::cast_precision_loss -Aclippy::cast_sign_loss
-Aclippy::cast_possible_truncation
```
- 17 unit tests + 34 doctests passed (1 ignored doctest).
- Clippy completed with zero warnings.
validate_parameter_type(&syn::Type):
Verifies the type is a reference (syn::Type::Reference).
Ensures the reference is mutable (type_ref.mutability.is_some()),
reporting: #[turtle_main] parameter must be a mutable reference: '&mut
TurtlePlan'.
Checks that the target type (type_ref.elem) is a path whose trailing
identifier is TurtlePlan, allowing both &mut TurtlePlan and qualified
paths like &mut turtle_lib::TurtlePlan.
Rejects other types (like value: i32 or owned t: TurtlePlan), reporting:
Integration into validate_input: Called
validate_parameter_type(&pat_type.ty)?; directly within
syn::FnArg::Typed(pat_type).
Unit Tests Added:
test_valid_qualified_type: verifies &mut turtle_lib::TurtlePlan is
accepted.
test_rejects_wrong_type: verifies value: i32 is rejected with an
informative error.
test_rejects_immutable_reference: verifies t: &TurtlePlan is rejected.
test_rejects_owned_type: verifies t: TurtlePlan is rejected.
test_rejects_wrong_reference_type: verifies t: &mut i32 is rejected.
turtle-lib-macros
Summary of Changes
Preserve Parameter Pattern in Macro Expansion:
In
turtle-lib-macros/src/lib.rs
, updated the helper function generation when has_turtle_param is true:
rust
let param = &input_fn.sig.inputs[0];
quote! {
}
This retains the exact parameter pattern and identifier (e.g. t: &mut
TurtlePlan, mut t: &mut TurtlePlan, etc.) rather than replacing it with
turtle: &mut turtle_lib::TurtlePlan.
Preserves user function visibility (#fn_vis) for named non-main helper
functions.
Validation of Parameters:
In validate_input, explicitly reject self receivers (FnArg::Receiver)
with an informative error message.
Reject unsupported patterns (e.g., tuple destructuring or struct
patterns) during validation, only accepting identifier patterns
(syn::Pat::Ident) and wildcards (syn::Pat::Wild).
Macro Expansion Testing:
Factored macro expansion logic into turtle_main_impl(&args, input) ->
Result<proc_macro2::TokenStream, syn::Error> so macro expansions can be
parsed into syn::File and verified directly in unit tests.
Added unit tests verifying:
Expansion with custom parameter names like t: &mut TurtlePlan preserves
t
Expansion with mutable parameters like mut t: &mut TurtlePlan
Expansion when the function name is main renames to __turtle_main_draw
while preserving parameter t
Expansion for zero-argument functions generates parameter turtle
Rejection of &mut self
Rejection of unsupported destructuring patterns like (a, b)
Verification
cargo test --package turtle-lib-macros: All 13 tests passed.
cargo clippy --package turtle-lib-macros -- -Wclippy::pedantic: Passed
with 0 warnings.
cargo test --all-targets --all-features: All unit and doc tests across
the workspace passed.
cargo check --package turtle-lib --examples: All 30 examples compiled
cleanly.
Fixed the timing clock divergence between
[tweening.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/tweening.rs)
and
[drawing.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/drawing.rs)
by delegating animation progress evaluation exclusively to
`TweenController`.
`current_time()` in `tweening.rs` was using a monotonic `Instant` epoch
(to support headless execution and unit tests without panicking on
missing Macroquad context), while `drawing.rs` was measuring progress
via `macroquad::time::get_time() - tween.start_time`. Because these two
clocks operate on completely different epochs, subtracting them caused
negative elapsed times, premature clamping, jitter, or animation lockup.
In
[`turtle-lib/src/tweening.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/tweening.rs):
- Added `pub(crate) progress: f32` to
[`CommandTween`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/tweening.rs#L55-L68)
to track the current evaluated, eased progress in `[0.0, 1.0]`.
- Made `start_time: f64` and `duration: f64` private to `tweening.rs` so
outside modules cannot accidentally perform desynchronized clock
arithmetic.
- Initialized `progress: 0.0` when constructing a new `CommandTween`.
- In `TweenController::update`, evaluated and clamped `tween.progress =
tween.heading_tweener.move_to(elapsed).clamp(0.0, 1.0)`.
In
[`turtle-lib/src/drawing.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/drawing.rs):
- Removed `use tween::CubicInOut;` and duplicate easing calculations.
- Replaced `get_time() - tween.start_time` and `CubicInOut.tween(...)`
in in-flight fill preview with `tween.progress`.
- Replaced `get_time() - tween.start_time` and `CubicInOut.tween(...)`
in `draw_tween_arc` with `tween.progress`.
- Ensured `drawing.rs` has zero clock queries and strictly renders the
state provided by `TweenController`.
In
[`turtle-lib/src/tweening.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/tweening.rs#L540-L579):
- Updated `test_animated_mode_pops_to_current_tween` to assert that
`progress` initializes to `0.0`.
- Added `test_animated_mode_progress_advances_and_clamps` to verify that
`progress` advances monotonically across frames and stays within
`[0.0, 1.0]`.
---
```bash
cargo test --package turtle-lib --features svg
```
Output:
```text
running 17 tests
test circle_geometry::tests::test_circle_left_geometry ... ok
test circle_geometry::tests::test_circle_right_geometry ... ok
test command_behavior::tests::test_goto_duration_cartesian_inversion ...
ok
test
command_behavior::tests::test_set_heading_degrees_and_instant_duration
... ok
test execution::tests::test_forward_left_forward ... ok
test general::angle::tests::degrees_to_radians_roundtrip ... ok
test general::angle::tests::from_f64 ... ok
test general::angle::tests::from_integer ... ok
test general::angle::tests::negation ... ok
test general::fontsize::tests::font_size_conversions ... ok
test general::length::tests::test_length_conversions_and_negation ... ok
test general::tests::animation_speed_conversions ... ok
test tweening::tests::test_animated_mode_pops_to_current_tween ... ok
test tweening::tests::test_instant_mode_drains_queue ... ok
test
tweening::tests::test_instant_mode_respects_batch_limit_and_retains_pending
... ok
test tweening::tests::test_streaming_append_commands_does_not_accumulate
... ok
test tweening::tests::test_animated_mode_progress_advances_and_clamps
... ok
test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered
out; finished in 0.02s
Doc-tests turtle_lib: 34 passed; 0 failed; 1 ignored; 0 measured; 0
filtered out; finished in 0.26s
```
```bash
cargo clippy --package turtle-lib --features svg -- -Wclippy::pedantic \
-Aclippy::cast_precision_loss -Aclippy::cast_sign_loss
-Aclippy::cast_possible_truncation
```
Output:
```text
Finished `dev` profile [optimized + debuginfo] target(s) in 0.82s
```
**0 warnings.**
```bash
cargo run --example yinyang --features svg -- --export-svg
/tmp/yinyang_test.svg
```
Output:
```text
SVG exported successfully to: /tmp/yinyang_test.svg
```
```bash
cargo build --package turtle-lib --examples
```
Output:
```text
Finished `dev` profile [optimized + debuginfo] target(s) in 6.74s
```
**All 30 examples compiled cleanly with 0 errors and 0 warnings.**
All issues identified across Sections 3.1, 3.2, and 3.3 have been
resolved, verified with unit tests, automated compilation checks under
`-D float_literal_f32_fallback`, and headless SVG export runs.
-
**[angle.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/general/angle.rs)**:
- Implemented `From<f64>` and `From<usize>` for `Degrees`.
- Added unit tests `from_integer` and `from_f64` to verify conversion
accuracy.
-
**[fontsize.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/general/fontsize.rs)**:
- Implemented `From<f64>` for `FontSize`.
- Refactored `FontSize::value(self)` to pass Copy type by value.
- Added unit test `font_size_conversions`.
-
**[general.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/general.rs)**:
- Implemented `From<f64>`, `From<i32>`, and `From<usize>` for
`AnimationSpeed`.
- Added unit test `animation_speed_conversions`.
- Re-exported `macroquad` crate (`pub use macroquad;`) so downstream
code and macro expansions have reliable direct access.
-
**[export.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/export.rs)**:
- Made `parse_svg_export_arg()` public.
- Implemented `run_headless_svg_export<F>(mut build_commands: F,
filename: &str) -> Result<(), ExportError>` that executes commands
using `app.step_animations()`, avoiding all window/GUI dependencies
and never calling `std::process::exit`.
- Updated `handle_svg_export` to delegate to
`run_headless_svg_export`.
-
**[lib.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/lib.rs)**:
- Extracted `pub fn step_animations(&mut self)` from `update(&mut
self)`, allowing command queue draining and tween stepping
headlessly without querying window mouse position or events.
-
**[state.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/state.rs)**:
- Changed `TurtleWorld::new()` to initialize camera with
`Camera2D::default()` instead of querying `screen_width()` /
`screen_height()`, eliminating panics when running without a
Macroquad window.
-
**[turtle-lib-macros/src/lib.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib-macros/src/lib.rs)**:
- Added `validate_input` helper providing clean compile diagnostics
with spans for:
- Multiple arguments: `#[turtle_main] functions must take either 0
arguments or a single &mut TurtlePlan`
- Non-unit return types: `#[turtle_main] functions cannot have a
return type`
- Async functions: `#[turtle_main] functions cannot be async`
- Replaced `#[macroquad::main]` wrapper expansion with a native `fn
main()` that inspects CLI arguments first. If `--export-svg` is
present, it runs `run_headless_svg_export` directly and returns
cleanly without opening a window. Otherwise, it launches
`macroquad::Window::new(#window_title, async { ... })`.
- Added 5 unit tests in `turtle-lib-macros` testing signature
validation.
-
**[.vscode/launch.json](file:///home/dietrich/Projekte/Source/turtlers/.vscode/launch.json)**:
- Removed stale references to nonexistent `turtle-example` and
`turtle-ui`.
- Added debug configurations for `turtle-lib` tests,
`turtle-lib-macros` tests, `hello_turtle`, and `breadboard`.
-
**[breadboard.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/examples/breadboard.rs)**:
- Refactored example to use `#[turtle_main("Breadboard")]`.
- Removed `#[cfg(feature = "svg")]` and the early-exit message; the
example now renders directly on screen by default and supports
`--features svg -- --export-svg breadboard.svg`.
-
**[README.md](file:///home/dietrich/Projekte/Source/turtlers/README.md)**:
- Documented optional user-level `~/.cargo/config.toml` mold/lld
fast-linking configuration under "Building and Running".
---
```bash
cargo test --workspace
```
- **Result**: 21 passed (16 in `turtle-lib`, 5 in `turtle-lib-macros`),
34 doctests passed, 0 failed.
```bash
RUSTFLAGS="-D float_literal_f32_fallback" cargo check --workspace
--all-targets --all-features
RUSTFLAGS="-D float_literal_f32_fallback" cargo check --package
turtle-lib --examples --all-features
```
- **Result**: Passed with 0 errors and 0 fallback warnings across all
workspace crates and all 30 examples.
```bash
cargo run --package turtle-lib --example hello_turtle --features svg --
--export-svg hello.svg
cargo run --package turtle-lib --example breadboard --features svg --
--export-svg breadboard.svg
```
- **Result**: Both exported SVG files successfully and exited with code
0 without creating or flashing a graphical window.
```bash
cargo check --package turtle-lib --example breadboard
```
- **Result**: Compiled cleanly with 0 errors when SVG feature is
disabled.