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/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..711a6a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,12 @@ //! 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; + use std::{ borrow::Cow, fs, @@ -14,15 +20,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 {