Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ incoming lines are buffered or emitted. Once the end of a table or fence is
reached, buffered lines are flushed and possibly reformatted. The simplified
behaviour is illustrated below.

````mermaid
```mermaid
stateDiagram-v2

[*] --> Streaming: Start
Expand All @@ -78,7 +78,7 @@ stateDiagram-v2
InHtmlTable --> InHtmlTable: Line inside table tag

InCodeFence --> Streaming: Line is a fence delimiter
````
```

Before:

Expand Down
46 changes: 46 additions & 0 deletions docs/developers-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Developers guide

## Frontmatter module visibility

The `frontmatter` module is an internal implementation detail of `mdtablefix`.
Its visibility is restricted to `pub(crate)` in the library so the crate does
not expose YAML frontmatter parsing as part of its supported public API.

This boundary matches the role of the module. The helper exists to shield a
leading YAML frontmatter block from Markdown transforms, including CLI-only
operations such as list renumbering and thematic break normalization. External
callers interact with that behaviour through higher-level formatting entry
points rather than by calling the frontmatter helper directly.

### Rationale

- Keep the public API focused on stable formatting operations rather than
document pre-processing internals.
- Reduce the risk of accidental API commitments around a narrowly scoped
helper that may need to change with the processing pipeline.
- Make the intended layering explicit: frontmatter detection supports stream
processing, but it is not a general-purpose parsing API.

### Implications for internal code organization

The package binary in [src/main.rs](../src/main.rs) is compiled as a separate
crate from the library, so it cannot use library items marked `pub(crate)`. To
preserve CLI access without reopening the library surface, the binary includes
[src/frontmatter.rs](../src/frontmatter.rs) privately via:

```rust
#[path = "frontmatter.rs"]
mod frontmatter;
```

This arrangement keeps the helper available to both the library and the CLI
while maintaining a closed external API boundary.

When working in this area:

- Prefer wiring new behaviour through `process_stream_inner` or other public
formatting APIs instead of exporting frontmatter helpers.
- Treat changes to frontmatter parsing rules as internal architectural changes
that should update this guide and any affected behaviour documentation.
- Keep module documentation in sync in both `src/frontmatter.rs` and the
private module declaration used by the binary.
4 changes: 2 additions & 2 deletions docs/documentation-style-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ contents of the manual.
they do not execute during documentation tests.
- Put function attributes after the doc comment.

````rust,no_run
```rust,no_run
/// Returns the sum of `a` and `b`.
///
/// # Parameters
Expand All @@ -106,7 +106,7 @@ contents of the manual.
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
````
```

## Diagrams and images

Expand Down
133 changes: 63 additions & 70 deletions docs/execplans/yaml-frontmatter.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ Status: DELIVERED

After this change, `mdtablefix` must accept a Markdown document that begins
with a YAML frontmatter block and leave that block byte-for-byte unchanged
while continuing to format the Markdown body normally. A user should be able
to run the formatter with flags such as `--wrap`, `--breaks`, or `--in-place`
and still see the opening delimiter, YAML keys, and closing delimiter exactly
as they were written.
while continuing to format the Markdown body normally. A user should be able to
run the formatter with flags such as `--wrap`, `--breaks`, or `--in-place` and
still see the opening delimiter, YAML keys, and closing delimiter exactly as
they were written.

The observable success case is a file that starts with:

Expand Down Expand Up @@ -62,75 +62,71 @@ according to the selected options.

- Risk: frontmatter might still be modified by CLI-only transforms such as
`renumber_lists` or `format_breaks` after the main stream processor returns.
Severity: high
Likelihood: medium
Mitigation: protect the body split at the highest shared pipeline boundary
and add a CLI regression that includes `--breaks`.
Severity: high Likelihood: medium Mitigation: protect the body split at the
highest shared pipeline boundary and add a CLI regression that includes
`--breaks`.

- Risk: delimiter detection can become too permissive and accidentally treat a
thematic break or ordinary `---` block as frontmatter.
Severity: medium
Likelihood: medium
Mitigation: only detect frontmatter when the very first line is a delimiter
and require a matching closing delimiter before shielding the block.
thematic break or ordinary `---` block as frontmatter. Severity: medium
Likelihood: medium Mitigation: only detect frontmatter when the very first
line is a delimiter and require a matching closing delimiter before shielding
the block.

- Risk: `src/process.rs` is already close to the repository's file-length
ceiling.
Severity: medium
Likelihood: high
Mitigation: place the detector and splitter logic in a new small module
instead of extending `src/process.rs` significantly.
ceiling. Severity: medium Likelihood: high Mitigation: place the detector and
splitter logic in a new small module instead of extending `src/process.rs`
significantly.

## Progress

- [x] (2026-04-05 22:45Z) Reviewed the current processing pipeline, test
layout, and user-facing documentation surfaces.
- [x] (2026-04-09) Add a shared helper for detecting and splitting leading YAML
frontmatter.
- [x] (2026-04-09) Thread the helper through the library and CLI formatting pipeline
- [x] (2026-04-09) Thread the helper through the library and CLI formatting
pipeline
so all transforms skip the frontmatter prefix.
- [x] (2026-04-09) Add unit and behavioural regression tests covering detection,
wrapping, and `--breaks`.
- [x] (2026-04-09) Update `README.md` and `docs/architecture.md`.
- [x] (2026-04-09) Run `make check-fmt`, `make lint`, `make test`, `make markdownlint`,
- [x] (2026-04-09) Run `make check-fmt`, `make lint`, `make test`,
`make markdownlint`,
and `make nixie` if Mermaid content changes.

## Surprises & discoveries

- Observation: `src/main.rs` applies `renumber_lists` and `format_breaks`
after `process_stream_opts`, so shielding only `process_stream_inner` would
still allow the frontmatter delimiters to be rewritten.
Evidence: `process_lines` in `src/main.rs`.
Impact: the plan must protect the body before or around CLI-only transforms,
not just inside `src/process.rs`.
still allow the frontmatter delimiters to be rewritten. Evidence:
`process_lines` in `src/main.rs`. Impact: the plan must protect the body
before or around CLI-only transforms, not just inside `src/process.rs`.

- Observation: `src/process.rs` is 343 lines before this feature.
Evidence: `leta files` output for `src/process.rs`.
Impact: new helper logic should live in its own module to stay within the
repository limit and keep tests readable.
Evidence: `leta files` output for `src/process.rs`. Impact: new helper logic
should live in its own module to stay within the repository limit and keep
tests readable.

## Decision log

- Decision: use a shared internal splitter for leading YAML frontmatter rather
than adding special cases separately in each transform.
Rationale: one detector keeps the delimiter rules consistent and reduces the
chance that a later pipeline stage mutates the protected prefix.
Date/Author: 2026-04-05 22:45Z / Droid
than adding special cases separately in each transform. Rationale: one
detector keeps the delimiter rules consistent and reduces the chance that a
later pipeline stage mutates the protected prefix. Date/Author: 2026-04-05
22:45Z / Droid

- Decision: treat unmatched opening delimiters as ordinary Markdown instead of
partially shielding the document.
Rationale: this avoids swallowing the entire file into a special mode and
preserves current behaviour for malformed input.
Date/Author: 2026-04-05 22:45Z / Droid
partially shielding the document. Rationale: this avoids swallowing the
entire file into a special mode and preserves current behaviour for malformed
input. Date/Author: 2026-04-05 22:45Z / Droid

## Outcomes & retrospective

The frontmatter splitter was successfully implemented in the `frontmatter`
module and integrated through both the `process` module and `main` module.
Test coverage was added covering detection, wrapping, and `--breaks` flags
for both library and CLI paths. All transforms now correctly skip the
frontmatter prefix, preserving the leading YAML block exactly while
formatting the Markdown body.
module and integrated through both the `process` module and `main` module. Test
coverage was added covering detection, wrapping, and `--breaks` flags for both
library and CLI paths. All transforms now correctly skip the frontmatter
prefix, preserving the leading YAML block exactly while formatting the Markdown
body.

## Context and orientation

Expand Down Expand Up @@ -184,9 +180,9 @@ The CLI test should enable `--breaks` and one ordinary formatting option such
as `--wrap` so it proves both preservation and continued formatting.

Stage E updates the docs. Add a short YAML frontmatter note and example to
`README.md` so users know the block is preserved. Update
`docs/architecture.md` to describe the leading-frontmatter split before the
rest of the formatting pipeline.
`README.md` so users know the block is preserved. Update `docs/architecture.md`
to describe the leading-frontmatter split before the rest of the formatting
pipeline.

Each stage ends with focused validation before moving on.

Expand All @@ -204,8 +200,8 @@ Expected:
/home/leynos/Projects/mdtablefix.worktrees/yaml-frontmatter
```

Add the detector and its focused tests, then run the smallest relevant test
set first:
Add the detector and its focused tests, then run the smallest relevant test set
first:

```bash
cargo test frontmatter --lib
Expand Down Expand Up @@ -260,8 +256,8 @@ make markdownlint
make nixie
```

If `docs/architecture.md` does not change any Mermaid content, `make nixie`
may be skipped.
If `docs/architecture.md` does not change any Mermaid content, `make nixie` may
be skipped.

## Validation and acceptance

Expand Down Expand Up @@ -289,8 +285,8 @@ Quality criteria:

The planned edits are safe to repeat because the detector only changes control
flow, not persisted state outside the repository. If a step goes wrong, revert
the affected file and rerun the focused tests before continuing. The manual
CLI example is read-only and may be rerun as many times as needed.
the affected file and rerun the focused tests before continuing. The manual CLI
example is read-only and may be rerun as many times as needed.

## Artifacts and notes

Expand All @@ -315,30 +311,27 @@ Do not add dependencies.
Add a new internal module at `src/frontmatter.rs` with a helper shaped like:

```rust
#[doc(hidden)]
pub mod frontmatter;
#[doc(hidden)]
pub use frontmatter::split_leading_yaml_frontmatter;
pub(crate) mod frontmatter;
```

The helper `split_leading_yaml_frontmatter` returns `(prefix, body)`, where
`prefix` is the untouched leading YAML block, or an empty slice if no valid
block exists. The module and helper are marked `#[doc(hidden)]` to keep them
out of the public API documentation while remaining accessible to the binary
crate.

`src/process.rs` calls the helper in `process_stream`, `process_stream_no_wrap`,
and `process_stream_opts` before existing body processing. `src/main.rs` calls
the same helper in `process_lines` before CLI-only transforms (`--renumber`,
`--breaks`).

Interface note: The `frontmatter` module is exported as `pub` with
`#[doc(hidden)]` rather than `pub(crate)` because the binary crate (`main.rs`)
requires access to `split_leading_yaml_frontmatter`. The binary and library are
separate crate targets, so `pub(crate)` would not allow the binary to access
the symbol. Using `#[doc(hidden)]` prevents the API from appearing in docs
while maintaining the necessary visibility.
block exists. The library keeps the helper internal, and the binary includes
the same source file privately so CLI code can continue to use it without
reopening the public API.

`src/process.rs` calls the helper in `process_stream`,
`process_stream_no_wrap`, and `process_stream_opts` before existing body
processing. `src/main.rs` calls the same helper in `process_lines` before
CLI-only transforms (`--renumber`, `--breaks`).

Interface note: The library now declares `frontmatter` as `pub(crate)` and does
not re-export `split_leading_yaml_frontmatter`. The binary crate (`main.rs`)
remains able to use the helper by including `src/frontmatter.rs` privately with
`#[path = "frontmatter.rs"] mod frontmatter;`. This keeps YAML frontmatter
detection available to internal callers while closing the external library
surface.

Revision note: Delivered. The implementation follows the plan with the
visibility adjustment noted above. All tests pass and the feature is ready
for use.
visibility adjustment noted above. All tests pass and the feature is ready for
use.
6 changes: 3 additions & 3 deletions docs/rust-doctest-dry-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,9 @@ Doctests reside within documentation comments. Rust recognizes two types:

Within these comments, a code block is denoted by triple back-ticks (```).
While `rustdoc` defaults to Rust syntax, explicitly add the `rust` language
specifier for clarity.[^3] A doctest "passes" when it compiles and runs
without panicking. To assert specific outcomes, use the standard macros
`assert!`, `assert_eq!`, and `assert_ne!`.[^3]
specifier for clarity.[^3] A doctest "passes" when it compiles and runs without
panicking. To assert specific outcomes, use the standard macros `assert!`,
`assert_eq!`, and `assert_ne!`.[^3]

### 2.2 The Philosophy of a Good Example

Expand Down
41 changes: 20 additions & 21 deletions docs/rust-testing-with-rstest-fixtures.md
Original file line number Diff line number Diff line change
Expand Up @@ -1198,13 +1198,13 @@ The following table summarizes key differences:
**Table 1:** `rstest` **vs. Standard Rust** `#[test]` **for Fixture Management
and Parameterization**

| Feature | Standard #[test] Approach | rstest Approach |
| Feature | Standard #[test] Approach | rstest Approach |
| ---------------------------------------- | ------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| Fixture Injection | Manual calls to setup functions within each test. | Fixture name as argument in #[rstest] function; fixture defined with #[fixture]. |
| Parameterized Tests (Specific Cases) | Loop inside one test, or multiple distinct #[test] functions. | #[case(…)] attributes on #[rstest] function. |
| Parameterized Tests (Value Combinations) | Nested loops inside one test, or complex manual generation. | #[values(…)] attributes on arguments of #[rstest] function. |
| Async Fixture Setup | Manual async block and .await calls inside test. | async fn fixtures, with #[future] and #[awt] for ergonomic .awaiting. |
| Reusing Parameter Sets | Manual duplication of cases or custom helper macros. | rstest_reuse crate with #[template] and #[apply] attributes. |
| Fixture Injection | Manual calls to setup functions within each test. | Fixture name as argument in #[rstest] function; fixture defined with #[fixture]. |
| Parameterized Tests (Specific Cases) | Loop inside one test, or multiple distinct #[test] functions. | #[case(…)] attributes on #[rstest] function. |
| Parameterized Tests (Value Combinations) | Nested loops inside one test, or complex manual generation. | #[values(…)] attributes on arguments of #[rstest] function. |
| Async Fixture Setup | Manual async block and .await calls inside test. | async fn fixtures, with #[future] and #[awt] for ergonomic .awaiting. |
| Reusing Parameter Sets | Manual duplication of cases or custom helper macros. | rstest_reuse crate with #[template] and #[apply] attributes. |

This comparison highlights how `rstest`'s attribute-based, declarative approach
streamlines common testing patterns, reducing manual effort and improving the
Expand Down Expand Up @@ -1342,20 +1342,20 @@ provided by `rstest`:

**Table 2: Key** `rstest` **Attributes Quick Reference**

| Attribute | Core Purpose |
| Attribute | Core Purpose |
| ---------------------------- | -------------------------------------------------------------------------------------------- |
| #[rstest] | Marks a function as a rstest test; enables fixture injection and parameterization. |
| #[fixture] | Defines a function that provides a test fixture (setup data or services). |
| #[case(…)] | Defines a single parameterized test case with specific input values. |
| #[values(…)] | Defines a list of values for an argument, generating tests for each value or combination. |
| #[once] | Marks a fixture to be initialized only once and shared (as a static reference) across tests. |
| #[future] | Simplifies async argument types by removing impl Future boilerplate. |
| #[awt] | (Function or argument level) Automatically .awaits future arguments in async tests. |
| #[from(original_name)] | Allows renaming an injected fixture argument in the test function. |
| #[with(…)] | Overrides default arguments of a fixture for a specific test. |
| #[default(…)] | Provides default values for arguments within a fixture function. |
| #[timeout(…)] | Sets a timeout for an asynchronous test. |
| #[files("glob_pattern",…)] | Injects file paths (or contents, with mode=) matching a glob pattern as test arguments. |
| #[rstest] | Marks a function as a rstest test; enables fixture injection and parameterization. |
| #[fixture] | Defines a function that provides a test fixture (setup data or services). |
| #[case(…)] | Defines a single parameterized test case with specific input values. |
| #[values(…)] | Defines a list of values for an argument, generating tests for each value or combination. |
| #[once] | Marks a fixture to be initialized only once and shared (as a static reference) across tests. |
| #[future] | Simplifies async argument types by removing impl Future boilerplate. |
| #[awt] | (Function or argument level) Automatically .awaits future arguments in async tests. |
| #[from(original_name)] | Allows renaming an injected fixture argument in the test function. |
| #[with(…)] | Overrides default arguments of a fixture for a specific test. |
| #[default(…)] | Provides default values for arguments within a fixture function. |
| #[timeout(…)] | Sets a timeout for an asynchronous test. |
| #[files("glob_pattern",…)] | Injects file paths (or contents, with mode=) matching a glob pattern as test arguments. |

Mastering `rstest` can significantly elevate the quality and efficiency of
testing practices for Rust developers, leading to more reliable and
Expand Down Expand Up @@ -1422,8 +1422,7 @@ Users Forum, accessed on June 12, 2025,
<https://users.rust-lang.org/t/is-there-any-point-in-avoiding-std-when-testing-a-no-std-library/122731>

[^21]: rstest-log - [crates.io](http://crates.io): Rust Package Registry,
accessed on June 12, 2025,
<https://crates.io/crates/rstest-log/dependencies>
accessed on June 12, 2025, <https://crates.io/crates/rstest-log/dependencies>

[^22]: test-with - [crates.io](http://crates.io): Rust Package Registry,
accessed on June 12, 2025, <https://crates.io/crates/test-with>
Loading
Loading