From d4a4f42544b75af569f121209a310905f1922f7e Mon Sep 17 00:00:00 2001 From: Leynos Date: Wed, 22 Apr 2026 22:19:43 +0000 Subject: [PATCH 1/2] Close frontmatter API boundary Make the library frontmatter module crate-private and remove the\npublic re-export so external crates can no longer reach the\nhelper through mdtablefix internals.\n\nKeep the package binary behaviour unchanged by compiling the same\nfrontmatter helper source as a private module in src/main.rs.\nUpdate the helper docs to reflect its internal-only status.\n\ncloses #264 --- src/frontmatter.rs | 6 +++--- src/lib.rs | 5 +---- src/main.rs | 13 ++++++------- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/frontmatter.rs b/src/frontmatter.rs index 9c316cc..75848b4 100644 --- a/src/frontmatter.rs +++ b/src/frontmatter.rs @@ -19,8 +19,8 @@ /// /// # Examples /// -/// ``` -/// use mdtablefix::frontmatter::split_leading_yaml_frontmatter; +/// ```ignore +/// use crate::frontmatter::split_leading_yaml_frontmatter; /// /// let lines = vec![ /// "---".to_string(), @@ -34,7 +34,7 @@ /// assert_eq!(body[0], "# Heading"); /// ``` #[must_use] -pub fn split_leading_yaml_frontmatter(lines: &[String]) -> (&[String], &[String]) { +pub(crate) fn split_leading_yaml_frontmatter(lines: &[String]) -> (&[String], &[String]) { if lines.is_empty() { return (&[], &[]); } diff --git a/src/lib.rs b/src/lib.rs index 00c6e86..1df972c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,8 +27,7 @@ pub mod code_emphasis; pub mod ellipsis; pub mod fences; pub mod footnotes; -#[doc(hidden)] -pub mod frontmatter; +pub(crate) mod frontmatter; pub mod headings; mod html; pub mod io; @@ -50,8 +49,6 @@ pub use code_emphasis::fix_code_emphasis; pub use ellipsis::replace_ellipsis; pub use fences::{attach_orphan_specifiers, compress_fences}; pub use footnotes::convert_footnotes; -#[doc(hidden)] -pub use frontmatter::split_leading_yaml_frontmatter; pub use headings::convert_setext_headings; pub use html::convert_html_tables; pub use io::{rewrite, rewrite_no_wrap}; diff --git a/src/main.rs b/src/main.rs index 6263933..7f18e70 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,9 @@ //! rewritten in place. Without paths the tool reads from standard input and //! prints results to stdout while preserving the input order. +#[path = "frontmatter.rs"] +mod frontmatter; + use std::{ borrow::Cow, fs, @@ -14,15 +17,11 @@ use std::{ use anyhow::Context; use clap::Parser; -use mdtablefix::{ - Options, - format_breaks, - process::process_stream_inner, - renumber_lists, - split_leading_yaml_frontmatter, -}; +use mdtablefix::{Options, format_breaks, process::process_stream_inner, renumber_lists}; use rayon::prelude::*; +use crate::frontmatter::split_leading_yaml_frontmatter; + #[derive(Parser)] #[command(version, about = "Reflow broken markdown tables")] struct Cli { From 9aa7b7dddd0cb813637370de43f1123b35d95f4b Mon Sep 17 00:00:00 2001 From: Leynos Date: Wed, 22 Apr 2026 23:11:45 +0000 Subject: [PATCH 2/2] feat(frontmatter): add internal YAML frontmatter detection and preservation Introduce a new internal module `frontmatter` to detect and split leading YAML frontmatter blocks in Markdown documents. This helper is used throughout the library's formatting pipeline and the CLI to ensure the frontmatter block is preserved byte-for-byte while formatting the rest of the Markdown body. The module is marked `pub(crate)` for internal use in the library and privately included in the CLI binary to avoid reopening the public API surface. This change ensures that all transforms correctly skip the frontmatter prefix, maintaining the exact original block and improving architectural clarity. It includes documentation updates and test coverage for detection, wrapping, and CLI flags related to frontmatter handling. Also fixes code fences in documentation and enhances docs readability with minor formatting fixes. Co-authored-by: devboxerhub[bot] --- docs/architecture.md | 4 +- docs/developers-guide.md | 46 ++++++++ docs/documentation-style-guide.md | 4 +- docs/execplans/yaml-frontmatter.md | 133 ++++++++++------------ docs/rust-doctest-dry-guide.md | 6 +- docs/rust-testing-with-rstest-fixtures.md | 41 ++++--- src/main.rs | 3 + 7 files changed, 139 insertions(+), 98 deletions(-) create mode 100644 docs/developers-guide.md diff --git a/docs/architecture.md b/docs/architecture.md index efe6f6a..c3a6bd1 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -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 @@ -78,7 +78,7 @@ stateDiagram-v2 InHtmlTable --> InHtmlTable: Line inside table tag InCodeFence --> Streaming: Line is a fence delimiter -```` +``` Before: diff --git a/docs/developers-guide.md b/docs/developers-guide.md new file mode 100644 index 0000000..73bdca3 --- /dev/null +++ b/docs/developers-guide.md @@ -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. diff --git a/docs/documentation-style-guide.md b/docs/documentation-style-guide.md index fe343b8..1524890 100644 --- a/docs/documentation-style-guide.md +++ b/docs/documentation-style-guide.md @@ -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 @@ -106,7 +106,7 @@ contents of the manual. pub fn add(a: i32, b: i32) -> i32 { a + b } -```` +``` ## Diagrams and images diff --git a/docs/execplans/yaml-frontmatter.md b/docs/execplans/yaml-frontmatter.md index ca5156f..6cf223f 100644 --- a/docs/execplans/yaml-frontmatter.md +++ b/docs/execplans/yaml-frontmatter.md @@ -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: @@ -62,24 +62,20 @@ 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 @@ -87,50 +83,50 @@ according to the selected options. 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 @@ -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. @@ -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 @@ -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 @@ -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 @@ -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. diff --git a/docs/rust-doctest-dry-guide.md b/docs/rust-doctest-dry-guide.md index 31d0426..13adcf4 100644 --- a/docs/rust-doctest-dry-guide.md +++ b/docs/rust-doctest-dry-guide.md @@ -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 diff --git a/docs/rust-testing-with-rstest-fixtures.md b/docs/rust-testing-with-rstest-fixtures.md index e570c88..7f62865 100644 --- a/docs/rust-testing-with-rstest-fixtures.md +++ b/docs/rust-testing-with-rstest-fixtures.md @@ -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 @@ -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 @@ -1422,8 +1422,7 @@ Users Forum, accessed on June 12, 2025, [^21]: rstest-log - [crates.io](http://crates.io): Rust Package Registry, -accessed on June 12, 2025, - +accessed on June 12, 2025, [^22]: test-with - [crates.io](http://crates.io): Rust Package Registry, accessed on June 12, 2025, diff --git a/src/main.rs b/src/main.rs index 7f18e70..711a6a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,9 @@ //! rewritten in place. Without paths the tool reads from standard input and //! prints results to stdout while preserving the input order. +/// Detects and splits leading YAML frontmatter for CLI processing so command +/// handlers can preserve the prefix while applying transforms to the Markdown +/// body. #[path = "frontmatter.rs"] mod frontmatter;