diff --git a/AGENTS.md b/AGENTS.md index 19ce51f7..df2b41c2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -95,8 +95,8 @@ ## Rust Specific Guidance This repository is written in Rust and uses Cargo for building and dependency -management. Contributors should follow these best practices when working on -the project: +management. Contributors should follow these best practices when working on the +project: - Run `make fmt`, `make lint`, and `make test` before committing. These targets wrap `cargo fmt`, `cargo clippy`, and `cargo test` with the appropriate flags. diff --git a/README.md b/README.md index a6b1acdc..e8c7b728 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ uniform width. It can wrap paragraphs and list items to 80 columns when the `--wrap` option is used. Hyphenated words are treated as single units during wrapping, so `very-long-word` moves to the next line rather than splitting at -the hyphen. The tool ignores fenced code blocks and respects escaped pipes (`\| -`), making it safe for mixed content. +the hyphen. The tool ignores fenced code blocks and respects escaped pipes ( +`\|`), making it safe for mixed content. ## Installation diff --git a/docs/rust-testing-with-rstest-fixtures.md b/docs/rust-testing-with-rstest-fixtures.md index b35ef015..ce586930 100644 --- a/docs/rust-testing-with-rstest-fixtures.md +++ b/docs/rust-testing-with-rstest-fixtures.md @@ -493,10 +493,10 @@ fn test_composed_fixture_with_override(#[with("special_")] configured_item: Stri In this example, `derived_value` depends on `base_value`, and `configured_item` depends on `derived_value`. When `test_composed_fixture` requests -`configured_item`, `rstest` first calls `base_value()`, then -`derived_value(10)`, and finally `configured_item(20, "item_".to_string())`. -This hierarchical dependency resolution mirrors good software design principles, -promoting modularity and maintainability in test setups. +`configured_item`, `rstest` first calls `base_value()`, then `derived_value(10)` +, and finally `configured_item(20, "item_".to_string())`. This hierarchical +dependency resolution mirrors good software design principles, promoting +modularity and maintainability in test setups. ### B. Controlling Fixture Initialization: `#[once]` for Shared State @@ -718,8 +718,8 @@ execution of these async tests. By default, `rstest` often uses `#[async_std::test]` to annotate the generated async test functions.9 However, it is designed to be largely runtime-agnostic and can be integrated with other popular async runtimes like Tokio or Actix. This is typically done by adding the -runtime's specific test attribute (e.g., `#[tokio::test]` or -`#[actix_rt::test]`) alongside `#[rstest]`.4 +runtime's specific test attribute (e.g., `#[tokio::test]` or `#[actix_rt::test]` +) alongside `#[rstest]`.4 ```rust @@ -761,9 +761,8 @@ To improve the ergonomics of working with async fixtures and values in tests, signature, removing the `impl Future` boilerplate. However, the value still needs to be `.await`ed explicitly within the test body or by using `#[awt]`.4 - `#[awt]` (or `#[future(awt)]`): This attribute, when applied to the entire - test function (`#[awt]`) or a specific `#[future]` argument - (`#[future(awt)]`), tells `rstest` to automatically insert `.await` calls for - those futures. + test function (`#[awt]`) or a specific `#[future]` argument (`#[future(awt)]` + ), tells `rstest` to automatically insert `.await` calls for those futures. ```rust @@ -1144,14 +1143,14 @@ for maintainability and scalability. - **Readability:** Utilize features like `#[from]` for renaming 12 and `#[default]` / `#[with]` for configurable fixtures to enhance the clarity of both fixture definitions and their usage in tests. -- **Utility Macros:** The integration tests define a `lines_vec!` macro for - quickly building `Vec` from string slices. Use it in fixtures to avoid - repetitive `.to_string()` calls. +- **Utility Macros:** The integration tests define a `string_vec!` macro for + quickly building `Vec` from string-like values. Use it in fixtures to + avoid repetitive `.to_string()` calls. ```rust #[fixture] fn example_table() -> Vec { - lines_vec!("a", "b", "c") + string_vec!["a", "b", "c"] } ``` @@ -1187,13 +1186,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 | -| ------------------------------------------------------------- | ------------------------------------------------------------- | -------------------------------------------------------------------------------- | -| 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. | +| 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. | This comparison highlights how `rstest`'s attribute-based, declarative approach streamlines common testing patterns, reducing manual effort and improving the diff --git a/docs/unicode-width.md b/docs/unicode-width.md index 2b1da2be..06130c42 100644 --- a/docs/unicode-width.md +++ b/docs/unicode-width.md @@ -1,7 +1,7 @@ # Unicode Width Handling -`mdtablefix` wraps paragraphs and list items while respecting the display width of -Unicode characters. The `unicode-width` crate is used to compute the width of +`mdtablefix` wraps paragraphs and list items while respecting the display width +of Unicode characters. The `unicode-width` crate is used to compute the width of strings when deciding where to break lines. This prevents emojis or other multi-byte characters from causing unexpected wraps or truncation. diff --git a/src/breaks.rs b/src/breaks.rs index b24e3db5..c81f24a6 100644 --- a/src/breaks.rs +++ b/src/breaks.rs @@ -14,21 +14,23 @@ static THEMATIC_BREAK_LINE: std::sync::LazyLock = std::sync::LazyLock::new(|| "_".repeat(THEMATIC_BREAK_LEN)); #[must_use] -pub fn format_breaks(lines: &[String]) -> Vec { +pub fn format_breaks(lines: &[String]) -> Vec> { + use std::borrow::Cow; + let mut out = Vec::with_capacity(lines.len()); let mut in_code = false; for line in lines { if is_fence(line) { in_code = !in_code; - out.push(line.clone()); + out.push(Cow::Borrowed(line.as_str())); continue; } if !in_code && THEMATIC_BREAK_RE.is_match(line.trim_end()) { - out.push(THEMATIC_BREAK_LINE.clone()); + out.push(Cow::Owned(THEMATIC_BREAK_LINE.clone())); } else { - out.push(line.clone()); + out.push(Cow::Borrowed(line.as_str())); } } @@ -50,7 +52,11 @@ mod tests { "_".repeat(THEMATIC_BREAK_LEN), "bar".to_string(), ]; - assert_eq!(format_breaks(&input), expected); + let result: Vec = format_breaks(&input) + .into_iter() + .map(std::borrow::Cow::into_owned) + .collect(); + assert_eq!(result, expected); } #[test] @@ -59,6 +65,10 @@ mod tests { .into_iter() .map(str::to_string) .collect::>(); - assert_eq!(format_breaks(&input), input); + let result: Vec = format_breaks(&input) + .into_iter() + .map(std::borrow::Cow::into_owned) + .collect(); + assert_eq!(result, input); } } diff --git a/src/main.rs b/src/main.rs index 43730dee..ec5952db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,7 +42,10 @@ fn process_lines(lines: &[String], opts: FormatOpts) -> Vec { out = renumber_lists(&out); } if opts.breaks { - out = format_breaks(&out); + out = format_breaks(&out) + .into_iter() + .map(std::borrow::Cow::into_owned) + .collect(); } out } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 7e90414b..72376456 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,12 +1,12 @@ //! Utility helpers shared across integration tests. -/// Build a `Vec` from a list of string slices. +/// Collect a list of string-like values into a `Vec`. /// -/// This macro is primarily used in tests to reduce boilerplate when -/// constructing example tables or other collections of lines. -macro_rules! lines_vec { - ($($line:expr),* $(,)?) => { - vec![$($line.to_string()),*] +/// Useful for building small inline datasets without verbose `.to_string()` +/// calls. +macro_rules! string_vec { + ( $($elem:expr),* $(,)? ) => { + vec![ $( ::std::string::ToString::to_string(&$elem) ),* ] }; } diff --git a/tests/integration.rs b/tests/integration.rs index fce5c95b..8b30bf98 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -28,7 +28,7 @@ mod common; /// assert_eq!(table[0], "| A | B | |"); /// ``` fn broken_table() -> Vec { - let lines = lines_vec!("| A | B | |", "| 1 | 2 | | 3 | 4 |",); + let lines = string_vec!("| A | B | |", "| 1 | 2 | | 3 | 4 |",); lines } @@ -48,29 +48,29 @@ fn broken_table() -> Vec { /// ); /// ``` fn malformed_table() -> Vec { - let lines = lines_vec!("| A | |", "| 1 | 2 | 3 |"); + let lines = string_vec!("| A | |", "| 1 | 2 | 3 |"); lines } #[fixture] fn header_table() -> Vec { - lines_vec!("| A | B | |", "| --- | --- |", "| 1 | 2 | | 3 | 4 |",) + string_vec!("| A | B | |", "| --- | --- |", "| 1 | 2 | | 3 | 4 |",) } #[fixture] fn escaped_pipe_table() -> Vec { - lines_vec!("| X | Y | |", "| a \\| b | 1 | | 2 | 3 |",) + string_vec!("| X | Y | |", "| a \\| b | 1 | | 2 | 3 |",) } #[fixture] fn indented_table() -> Vec { - let lines = lines_vec!(" | I | J | |", " | 1 | 2 | | 3 | 4 |",); + let lines = string_vec!(" | I | J | |", " | 1 | 2 | | 3 | 4 |",); lines } #[fixture] fn html_table() -> Vec { - lines_vec!( + string_vec!( "", "", "", @@ -80,7 +80,7 @@ fn html_table() -> Vec { #[fixture] fn html_table_with_attrs() -> Vec { - lines_vec!( + string_vec!( "
AB
12
", "", "", @@ -90,7 +90,7 @@ fn html_table_with_attrs() -> Vec { #[fixture] fn html_table_with_colspan() -> Vec { - lines_vec!( + string_vec!( "
AB
12
", "", "", @@ -100,7 +100,7 @@ fn html_table_with_colspan() -> Vec { #[fixture] fn html_table_no_header() -> Vec { - lines_vec!( + string_vec!( "
A
12
", "", "", @@ -110,7 +110,7 @@ fn html_table_no_header() -> Vec { #[fixture] fn html_table_empty_row() -> Vec { - lines_vec!( + string_vec!( "
AB
12
", "", "", @@ -120,7 +120,7 @@ fn html_table_empty_row() -> Vec { #[fixture] fn html_table_whitespace_header() -> Vec { - lines_vec!( + string_vec!( "
12
", "", "", @@ -130,7 +130,7 @@ fn html_table_whitespace_header() -> Vec { #[fixture] fn html_table_inconsistent_first_row() -> Vec { - lines_vec!( + string_vec!( "
12
", "", "", @@ -140,19 +140,19 @@ fn html_table_inconsistent_first_row() -> Vec { #[fixture] fn html_table_empty() -> Vec { - let lines = lines_vec!("
A
12
"); + let lines = string_vec!("
"); lines } #[fixture] fn html_table_unclosed() -> Vec { - let lines = lines_vec!("", ""); + let lines = string_vec!("
1
", ""); lines } #[fixture] fn html_table_uppercase() -> Vec { - lines_vec!( + string_vec!( "
1
", "", "", @@ -162,7 +162,7 @@ fn html_table_uppercase() -> Vec { #[fixture] fn html_table_mixed_case() -> Vec { - lines_vec!( + string_vec!( "
AB
12
", "", "", @@ -172,7 +172,7 @@ fn html_table_mixed_case() -> Vec { #[fixture] fn multiple_tables() -> Vec { - lines_vec!("| A | B |", "| 1 | 22 |", "", "| X | Y |", "| 3 | 4 |",) + string_vec!("| A | B |", "| 1 | 22 |", "", "| X | Y |", "| 3 | 4 |",) } #[rstest] @@ -191,7 +191,7 @@ fn multiple_tables() -> Vec { /// assert_eq!(reflow_table(&broken), expected); /// ``` fn test_reflow_basic(broken_table: Vec) { - let expected = vec!["| A | B |", "| 1 | 2 |", "| 3 | 4 |"]; + let expected = string_vec!["| A | B |", "| 1 | 2 |", "| 3 | 4 |"]; assert_eq!(reflow_table(&broken_table), expected); } @@ -207,7 +207,7 @@ fn test_reflow_malformed_returns_original(malformed_table: Vec) { #[rstest] fn test_reflow_preserves_header(header_table: Vec) { - let expected = vec!["| A | B |", "| --- | --- |", "| 1 | 2 |", "| 3 | 4 |"]; + let expected = string_vec!["| A | B |", "| --- | --- |", "| 1 | 2 |", "| 3 | 4 |"]; assert_eq!(reflow_table(&header_table), expected); } @@ -217,43 +217,43 @@ fn test_reflow_handles_escaped_pipes(escaped_pipe_table: Vec) { // pipe sequence (`a \| b`). After reflow the escaped pipe becomes a literal // `|` inside the first data cell, so the table has three columns and the // header row is padded to match. - let expected = vec!["| X | Y |", "| a | b | 1 |", "| 2 | 3 |"]; + let expected = string_vec!["| X | Y |", "| a | b | 1 |", "| 2 | 3 |"]; assert_eq!(reflow_table(&escaped_pipe_table), expected); } #[rstest] fn test_reflow_preserves_indentation(indented_table: Vec) { - let expected = vec![" | I | J |", " | 1 | 2 |", " | 3 | 4 |"]; + let expected = string_vec![" | I | J |", " | 1 | 2 |", " | 3 | 4 |"]; assert_eq!(reflow_table(&indented_table), expected); } #[rstest] fn test_process_stream_html_table(html_table: Vec) { - let expected = vec!["| A | B |", "| --- | --- |", "| 1 | 2 |"]; + let expected = string_vec!["| A | B |", "| --- | --- |", "| 1 | 2 |"]; assert_eq!(process_stream(&html_table), expected); } #[rstest] fn test_process_stream_html_table_with_attrs(html_table_with_attrs: Vec) { - let expected = vec!["| A | B |", "| --- | --- |", "| 1 | 2 |"]; + let expected = string_vec!["| A | B |", "| --- | --- |", "| 1 | 2 |"]; assert_eq!(process_stream(&html_table_with_attrs), expected); } #[rstest] fn test_process_stream_html_table_uppercase(html_table_uppercase: Vec) { - let expected = vec!["| A | B |", "| --- | --- |", "| 1 | 2 |"]; + let expected = string_vec!["| A | B |", "| --- | --- |", "| 1 | 2 |"]; assert_eq!(process_stream(&html_table_uppercase), expected); } #[rstest] fn test_process_stream_html_table_mixed_case(html_table_mixed_case: Vec) { - let expected = vec!["| A | B |", "| --- | --- |", "| 1 | 2 |"]; + let expected = string_vec!["| A | B |", "| --- | --- |", "| 1 | 2 |"]; assert_eq!(process_stream(&html_table_mixed_case), expected); } #[rstest] fn test_process_stream_multiple_tables(multiple_tables: Vec) { - let expected = lines_vec!( + let expected = string_vec!( "| A | B |", "| 1 | 22 |", String::new(), @@ -269,11 +269,11 @@ fn test_process_stream_multiple_tables(multiple_tables: Vec) { /// processing logic, ensuring their contents are not altered. #[rstest] fn test_process_stream_ignores_code_fences() { - let lines = lines_vec!("```rust", "| not | a | table |", "```"); + let lines = string_vec!("```rust", "| not | a | table |", "```"); assert_eq!(process_stream(&lines), lines); // Test with tilde-based code fences - let tilde_lines = lines_vec!("~~~", "| not | a | table |", "~~~"); + let tilde_lines = string_vec!("~~~", "| not | a | table |", "~~~"); assert_eq!(process_stream(&tilde_lines), tilde_lines); } @@ -353,14 +353,14 @@ fn test_cli_wrap_option() { #[test] fn test_uniform_example_one() { - let input = vec![ - "| Logical type | PostgreSQL | SQLite notes |".to_string(), - "|--------------|-------------------------|---------------------------------------------------------------------------------|".to_string(), - "| strings | `TEXT` (or `VARCHAR`) | `TEXT` - SQLite ignores the length specifier anyway |".to_string(), - "| booleans | `BOOLEAN DEFAULT FALSE` | declare as `BOOLEAN`; Diesel serialises to 0 / 1 so this is fine |".to_string(), - "| integers | `INTEGER` / `BIGINT` | ditto |".to_string(), - "| decimals | `NUMERIC` | stored as FLOAT in SQLite; Diesel `Numeric` round-trips, but beware precision |".to_string(), - "| blobs / raw | `BYTEA` | `BLOB` |".to_string(), + let input = string_vec![ + "| Logical type | PostgreSQL | SQLite notes |", + "|--------------|-------------------------|----------------------------------------------------|", + "| strings | `TEXT` (or `VARCHAR`) | `TEXT` - SQLite ignores the length specifier anyway |", + "| booleans | `BOOLEAN DEFAULT FALSE` | declare as `BOOLEAN`; Diesel serialises to 0 / 1 so this is fine |", + "| integers | `INTEGER` / `BIGINT` | ditto |", + "| decimals | `NUMERIC` | stored as FLOAT in SQLite; Diesel `Numeric` round-trips, but beware precision |", + "| blobs / raw | `BYTEA` | `BLOB` |", ]; let output = reflow_table(&input); assert!(!output.is_empty()); @@ -379,12 +379,12 @@ fn test_uniform_example_one() { #[test] fn test_uniform_example_two() { - let input = vec![ - "| Option | How it works | When to choose it |".to_string(), - "|--------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------|".to_string(), - "| **B. Pure-Rust migrations** | Implement `diesel::migration::Migration` in a Rust file (`up.rs` / `down.rs`) and compile with both `features = [\"postgres\", \"sqlite\"]`. The query builder emits backend-specific SQL at runtime. | You prefer the type-checked DSL and can live with slightly slower compile times. |".to_string(), - "| **C. Lowest-common-denominator SQL** | Write one `up.sql`/`down.sql` that *already* works on both engines. This demands avoiding SERIAL/IDENTITY, JSONB, `TIMESTAMPTZ`, etc. | Simple schemas, embedded use-case only, you are happy to supply integer primary keys manually. |".to_string(), - "| **D. Two separate migration trees** | Maintain `migrations/sqlite` and `migrations/postgres` directories with identical version numbers. Use `embed_migrations!(\"migrations/\")` to compile the right set. | You ship a single binary with migrations baked in. |".to_string(), + let input = string_vec![ + "| Option | How it works | When to choose it |", + "|--------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------|", + "| **B. Pure-Rust migrations** | Implement `diesel::migration::Migration` in a Rust file (`up.rs` / `down.rs`) and compile with both `features = [\"postgres\", \"sqlite\"]`. The query builder emits backend-specific SQL at runtime. | You prefer the type-checked DSL and can live with slightly slower compile times. |", + "| **C. Lowest-common-denominator SQL** | Write one `up.sql`/`down.sql` that *already* works on both engines. This demands avoiding SERIAL/IDENTITY, JSONB, `TIMESTAMPTZ`, etc. | Simple schemas, embedded use-case only, you are happy to supply integer primary keys manually. |", + "| **D. Two separate migration trees** | Maintain `migrations/sqlite` and `migrations/postgres` directories with identical version numbers. Use `embed_migrations!(\"migrations/\")` to compile the right set. | You ship a single binary with migrations baked in. |", ]; let output = reflow_table(&input); assert!(!output.is_empty()); @@ -403,27 +403,27 @@ fn test_uniform_example_two() { #[test] fn test_non_table_lines_unchanged() { - let input = vec![ - "# Title".to_string(), + let input = string_vec![ + "# Title", String::new(), - "Para text.".to_string(), + "Para text.", String::new(), - "| a | b |".to_string(), - "| 1 | 22 |".to_string(), + "| a | b |", + "| 1 | 22 |", String::new(), - "* bullet".to_string(), + "* bullet", String::new(), ]; let output = process_stream(&input); - let expected = vec![ - "# Title".to_string(), + let expected = string_vec![ + "# Title", String::new(), - "Para text.".to_string(), + "Para text.", String::new(), - "| a | b |".to_string(), - "| 1 | 22 |".to_string(), + "| a | b |", + "| 1 | 22 |", String::new(), - "* bullet".to_string(), + "* bullet", String::new(), ]; assert_eq!(output, expected); @@ -431,17 +431,13 @@ fn test_non_table_lines_unchanged() { #[test] fn test_convert_html_table_basic() { - let html_table = vec![ - "
AB
12
".to_string(), - "".to_string(), - "".to_string(), - "
AB
12
".to_string(), - ]; - let expected = vec![ - "| A | B |".to_string(), - "| --- | --- |".to_string(), - "| 1 | 2 |".to_string(), + let html_table = string_vec![ + "", + "", + "", + "
AB
12
", ]; + let expected = string_vec!["| A | B |", "| --- | --- |", "| 1 | 2 |",]; assert_eq!(convert_html_tables(&html_table), expected); } @@ -450,63 +446,63 @@ fn test_convert_html_table_basic() { #[case("~~~")] #[case("```rust")] fn test_convert_html_table_in_text_and_code(#[case] fence: &str) { - let lines = vec![ - "Intro".to_string(), - "".to_string(), - "".to_string(), - "".to_string(), - "
AB
12
".to_string(), - fence.to_string(), - "
x
".to_string(), - fence.to_string(), - "Outro".to_string(), + let lines = string_vec![ + "Intro", + "", + "", + "", + "
AB
12
", + fence, + "
x
", + fence, + "Outro", ]; - let expected = vec![ - "Intro".to_string(), - "| A | B |".to_string(), - "| --- | --- |".to_string(), - "| 1 | 2 |".to_string(), - fence.to_string(), - "
x
".to_string(), - fence.to_string(), - "Outro".to_string(), + let expected = string_vec![ + "Intro", + "| A | B |", + "| --- | --- |", + "| 1 | 2 |", + fence, + "
x
", + fence, + "Outro", ]; assert_eq!(convert_html_tables(&lines), expected); } #[test] fn test_convert_html_table_with_attrs_basic() { - let expected = vec!["| A | B |", "| --- | --- |", "| 1 | 2 |"]; + let expected = string_vec!["| A | B |", "| --- | --- |", "| 1 | 2 |"]; assert_eq!(convert_html_tables(&html_table_with_attrs()), expected); } #[test] fn test_convert_html_table_uppercase() { - let expected = vec!["| A | B |", "| --- | --- |", "| 1 | 2 |"]; + let expected = string_vec!["| A | B |", "| --- | --- |", "| 1 | 2 |"]; assert_eq!(convert_html_tables(&html_table_uppercase()), expected); } #[test] fn test_convert_html_table_with_colspan() { - let expected = vec!["| A |", "| --- |", "| 1 | 2 |"]; + let expected = string_vec!["| A |", "| --- |", "| 1 | 2 |"]; assert_eq!(convert_html_tables(&html_table_with_colspan()), expected); } #[test] fn test_convert_html_table_no_header() { - let expected = vec!["| A | B |", "| --- | --- |", "| 1 | 2 |"]; + let expected = string_vec!["| A | B |", "| --- | --- |", "| 1 | 2 |"]; assert_eq!(convert_html_tables(&html_table_no_header()), expected); } #[test] fn test_convert_html_table_empty_row() { - let expected = vec!["| 1 | 2 |", "| --- | --- |"]; + let expected = string_vec!["| 1 | 2 |", "| --- | --- |"]; assert_eq!(convert_html_tables(&html_table_empty_row()), expected); } #[test] fn test_convert_html_table_whitespace_header() { - let expected = vec!["| --- | --- |", "| --- | --- |", "| 1 | 2 |"]; + let expected = string_vec!["| --- | --- |", "| --- | --- |", "| 1 | 2 |"]; assert_eq!( convert_html_tables(&html_table_whitespace_header()), expected @@ -515,7 +511,7 @@ fn test_convert_html_table_whitespace_header() { #[test] fn test_convert_html_table_inconsistent_first_row() { - let expected = vec!["| A |", "| --- |", "| 1 | 2 |"]; + let expected = string_vec!["| A |", "| --- |", "| 1 | 2 |"]; assert_eq!( convert_html_tables(&html_table_inconsistent_first_row()), expected @@ -647,10 +643,9 @@ fn test_process_stream_option_table() { /// Ensures that a single long paragraph is split into multiple lines, each not exceeding 80 /// characters. fn test_wrap_paragraph() { - let input = vec![ + let input = string_vec![ "This is a very long paragraph that should be wrapped at eighty columns so it needs to \ contain enough words to exceed that limit." - .to_string(), ]; let output = process_stream(&input); assert!(output.len() > 1); @@ -659,9 +654,8 @@ fn test_wrap_paragraph() { #[test] fn test_wrap_list_item() { - let input = vec![ + let input = string_vec![ r"- This bullet item is exceptionally long and must be wrapped to keep prefix formatting intact." - .to_string(), ]; let output = process_stream(&input); common::assert_wrapped_list_item(&output, "- ", 2); @@ -673,7 +667,7 @@ fn test_wrap_list_item() { #[case("10. ", 3)] #[case("100. ", 3)] fn test_wrap_list_items_with_inline_code(#[case] prefix: &str, #[case] expected: usize) { - let input = vec![format!( + let input = string_vec![format!( "{prefix}`script`: A multi-line script declared with the YAML `|` block style. The entire \ block is passed to an interpreter. If the first line begins with `#!`, Netsuke executes \ the script verbatim, respecting the shebang." @@ -684,11 +678,10 @@ fn test_wrap_list_items_with_inline_code(#[case] prefix: &str, #[case] expected: #[test] fn test_wrap_preserves_inline_code_spans() { - let input = vec![ + let input = string_vec![ "- `script`: A multi-line script declared with the YAML `|` block style. The entire block \ is passed to an interpreter. If the first line begins with `#!`, Netsuke executes the \ script verbatim, respecting the shebang." - .to_string(), ]; let output = process_stream(&input); common::assert_wrapped_list_item(&output, "- ", 3); @@ -696,10 +689,9 @@ fn test_wrap_preserves_inline_code_spans() { #[test] fn test_wrap_multi_backtick_code() { - let input = vec![ + let input = string_vec![ "- ``cmd`` executes ```echo``` output with ``json`` format and prints results to the \ console" - .to_string(), ]; let output = process_stream(&input); common::assert_wrapped_list_item(&output, "- ", 2); @@ -707,10 +699,9 @@ fn test_wrap_multi_backtick_code() { #[test] fn test_wrap_multiple_inline_code_spans() { - let input = vec![ + let input = string_vec![ "- Use `foo` and `bar` inside ``baz`` for testing with additional commentary to exceed \ wrapping width" - .to_string(), ]; let output = process_stream(&input); common::assert_wrapped_list_item(&output, "- ", 2); @@ -718,26 +709,20 @@ fn test_wrap_multiple_inline_code_spans() { #[test] fn test_wrap_footnote_multiline() { - let input = vec![ - concat!( - "[^note]: This footnote is sufficiently long to require wrapping ", - "across multiple lines so we can verify indentation." - ) - .to_string(), - ]; + let input = string_vec![concat!( + "[^note]: This footnote is sufficiently long to require wrapping ", + "across multiple lines so we can verify indentation." + )]; let output = process_stream(&input); common::assert_wrapped_list_item(&output, "[^note]: ", 2); } #[test] fn test_wrap_footnote_with_inline_code() { - let input = vec![ - concat!( - " [^code_note]: A footnote containing inline `code` that should wrap ", - "across multiple lines without breaking the span." - ) - .to_string(), - ]; + let input = string_vec![concat!( + " [^code_note]: A footnote containing inline `code` that should wrap ", + "across multiple lines without breaking the span." + )]; let output = process_stream(&input); common::assert_wrapped_list_item(&output, " [^code_note]: ", 2); } @@ -748,7 +733,7 @@ fn test_wrap_footnote_with_inline_code() { /// unchanged when passed to `process_stream`. #[test] fn test_wrap_footnote_collection() { - let input = vec![ + let input = string_vec![ "[^1]: ", "[^2]: ", "[^3]: ", @@ -757,10 +742,7 @@ fn test_wrap_footnote_collection() { "[^6]: ", "[^7]: ", "[^8]: ", - ] - .into_iter() - .map(str::to_string) - .collect::>(); + ]; let output = process_stream(&input); assert_eq!(output, input); @@ -771,38 +753,33 @@ fn test_wrap_footnote_collection() { /// /// Ensures that a single-line bullet list item remains unchanged after processing. fn test_wrap_short_list_item() { - let input = vec!["- short item".to_string()]; + let input = string_vec!["- short item"]; let output = process_stream(&input); assert_eq!(output, input); } #[test] fn test_wrap_blockquote() { - let input = vec![ + let input = string_vec![ "> **Deprecated**: A :class:`WebSocketRouter` and its `add_route` API should be used to \ instantiate resources." - .to_string(), ]; let output = process_stream(&input); assert_eq!( output, vec![ - "> **Deprecated**: A :class:`WebSocketRouter` and its `add_route` API should be" - .to_string(), - "> used to instantiate resources.".to_string(), + "> **Deprecated**: A :class:`WebSocketRouter` and its `add_route` API should be", + "> used to instantiate resources.", ] ); } #[test] fn test_wrap_blockquote_nested() { - let input = vec![ - concat!( - "> > This nested quote contains enough text to require wrapping so that we ", - "can verify multi-level handling." - ) - .to_string(), - ]; + let input = string_vec![concat!( + "> > This nested quote contains enough text to require wrapping so that we ", + "can verify multi-level handling." + )]; let output = process_stream(&input); common::assert_wrapped_blockquote(&output, "> > ", 2); let joined = output @@ -815,20 +792,18 @@ fn test_wrap_blockquote_nested() { #[test] fn test_wrap_blockquote_with_blank_lines() { - let input = vec![ + let input = string_vec![ concat!( "> The first paragraph in this quote is deliberately long enough to wrap ", "across multiple lines so" - ) - .to_string(), - "> demonstrate the behaviour.".to_string(), - ">".to_string(), + ), + "> demonstrate the behaviour.", + ">", concat!( "> The second paragraph is also extended to trigger wrapping in order to ", "ensure blank lines" - ) - .to_string(), - "> are preserved correctly.".to_string(), + ), + "> are preserved correctly.", ]; let output = process_stream(&input); assert_eq!(output[3], ">"); @@ -838,10 +813,9 @@ fn test_wrap_blockquote_with_blank_lines() { #[test] fn test_wrap_blockquote_extra_whitespace() { - let input = vec![ + let input = string_vec![ "> Extra spacing should not prevent correct wrapping of this quoted text that exceeds \ the line width." - .to_string(), ]; let output = process_stream(&input); common::assert_wrapped_blockquote(&output, "> ", 2); @@ -855,7 +829,7 @@ fn test_wrap_blockquote_extra_whitespace() { #[test] fn test_wrap_blockquote_short() { - let input = vec!["> short".to_string()]; + let input = string_vec!["> short"]; let output = process_stream(&input); assert_eq!(output, input); } @@ -866,10 +840,7 @@ fn test_wrap_blockquote_short() { /// Ensures that the `process_stream` function does not remove or alter lines ending with Markdown /// hard line breaks. fn test_preserve_hard_line_breaks() { - let input = vec![ - "Line one with break. ".to_string(), - "Line two follows.".to_string(), - ]; + let input = string_vec!["Line one with break. ", "Line two follows.",]; let output = process_stream(&input); assert_eq!(output.len(), 2); assert_eq!(output[0], "Line one with break."); @@ -896,34 +867,15 @@ fn test_regression_complex_table() { #[test] fn test_renumber_basic() { - let input = vec![ - "1. first".to_string(), - "2. second".to_string(), - "7. third".to_string(), - ]; - let expected = vec!["1. first", "2. second", "3. third"] - .into_iter() - .map(str::to_string) - .collect::>(); + let input = string_vec!["1. first", "2. second", "7. third",]; + let expected = string_vec!["1. first", "2. second", "3. third"]; assert_eq!(renumber_lists(&input), expected); } #[test] fn test_renumber_with_fence() { - let input = vec![ - "1. item".to_string(), - "```".to_string(), - "code".to_string(), - "```".to_string(), - "9. next".to_string(), - ]; - let expected = vec![ - "1. item".to_string(), - "```".to_string(), - "code".to_string(), - "```".to_string(), - "2. next".to_string(), - ]; + let input = string_vec!["1. item", "```", "code", "```", "9. next",]; + let expected = string_vec!["1. item", "```", "code", "```", "2. next",]; assert_eq!(renumber_lists(&input), expected); } @@ -942,124 +894,101 @@ fn test_cli_renumber_option() { #[test] fn test_renumber_nested_lists() { - let input = vec![ + let input = string_vec![ "1. first", " 1. sub first", " 3. sub second", "2. second", - ] - .into_iter() - .map(str::to_string) - .collect::>(); + ]; - let expected = vec![ + let expected = string_vec![ "1. first", " 1. sub first", " 2. sub second", "2. second", - ] - .into_iter() - .map(str::to_string) - .collect::>(); + ]; assert_eq!(renumber_lists(&input), expected); } #[test] fn test_renumber_tabs_in_indent() { - let input = vec!["1. first", "\t1. sub first", "\t5. sub second", "2. second"] - .into_iter() - .map(str::to_string) - .collect::>(); + let input = string_vec!["1. first", "\t1. sub first", "\t5. sub second", "2. second"]; - let expected = vec!["1. first", "\t1. sub first", "\t2. sub second", "2. second"] - .into_iter() - .map(str::to_string) - .collect::>(); + let expected = string_vec!["1. first", "\t1. sub first", "\t2. sub second", "2. second"]; assert_eq!(renumber_lists(&input), expected); } #[test] fn test_renumber_mult_paragraph_items() { - let input = vec!["1. first", "", " still first paragraph", "", "2. second"] - .into_iter() - .map(str::to_string) - .collect::>(); + let input = string_vec!["1. first", "", " still first paragraph", "", "2. second"]; - let expected = vec!["1. first", "", " still first paragraph", "", "2. second"] - .into_iter() - .map(str::to_string) - .collect::>(); + let expected = string_vec!["1. first", "", " still first paragraph", "", "2. second"]; assert_eq!(renumber_lists(&input), expected); } #[test] fn test_renumber_table_in_list() { - let input = vec!["1. first", " | A | B |", " | 1 | 2 |", "5. second"] - .into_iter() - .map(str::to_string) - .collect::>(); + let input = string_vec!["1. first", " | A | B |", " | 1 | 2 |", "5. second"]; - let expected = vec!["1. first", " | A | B |", " | 1 | 2 |", "2. second"] - .into_iter() - .map(str::to_string) - .collect::>(); + let expected = string_vec!["1. first", " | A | B |", " | 1 | 2 |", "2. second"]; assert_eq!(renumber_lists(&input), expected); } #[test] fn test_format_breaks_basic() { - let input = vec!["foo", "***", "bar"] + let input = string_vec!["foo", "***", "bar"]; + let expected = string_vec!["foo", "_".repeat(THEMATIC_BREAK_LEN), "bar",]; + let result: Vec = format_breaks(&input) .into_iter() - .map(str::to_string) - .collect::>(); - let expected = vec![ - "foo".to_string(), - "_".repeat(THEMATIC_BREAK_LEN), - "bar".to_string(), - ]; - assert_eq!(format_breaks(&input), expected); + .map(std::borrow::Cow::into_owned) + .collect(); + assert_eq!(result, expected); } #[test] fn test_format_breaks_ignores_code() { - let input = vec!["```", "---", "```"] + let input = string_vec!["```", "---", "```"]; + let result: Vec = format_breaks(&input) .into_iter() - .map(str::to_string) - .collect::>(); - assert_eq!(format_breaks(&input), input); + .map(std::borrow::Cow::into_owned) + .collect(); + assert_eq!(result, input); } #[test] fn test_format_breaks_mixed_chars() { - let input = vec!["-*-*-"] + let input = string_vec!["-*-*-"]; + let result: Vec = format_breaks(&input) .into_iter() - .map(str::to_string) - .collect::>(); - assert_eq!(format_breaks(&input), input); + .map(std::borrow::Cow::into_owned) + .collect(); + assert_eq!(result, input); } #[test] fn test_format_breaks_with_spaces_and_indent() { - let input = vec![" - - - "] - .into_iter() - .map(str::to_string) - .collect::>(); + let input = string_vec![" - - - "]; let expected = vec!["_".repeat(THEMATIC_BREAK_LEN)]; - assert_eq!(format_breaks(&input), expected); + let result: Vec = format_breaks(&input) + .into_iter() + .map(std::borrow::Cow::into_owned) + .collect(); + assert_eq!(result, expected); } #[test] fn test_format_breaks_with_tabs_and_underscores() { - let input = vec!["\t_\t_\t_\t"] - .into_iter() - .map(str::to_string) - .collect::>(); + let input = string_vec!["\t_\t_\t_\t"]; let expected = vec!["_".repeat(THEMATIC_BREAK_LEN)]; - assert_eq!(format_breaks(&input), expected); + let result: Vec = format_breaks(&input) + .into_iter() + .map(std::borrow::Cow::into_owned) + .collect(); + assert_eq!(result, expected); } #[test]