From 98cc209d6bc9dccb33e11bdae2c928ab5fdf3513 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 12:42:34 +1000 Subject: [PATCH 01/20] chore: renamed .rustfmt.toml => rustfmt.toml --- .rustfmt.toml => rustfmt.toml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .rustfmt.toml => rustfmt.toml (100%) diff --git a/.rustfmt.toml b/rustfmt.toml similarity index 100% rename from .rustfmt.toml rename to rustfmt.toml From 9de07f12be5ef34442629cdd85587fe90b3b7f9e Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 12:44:16 +1000 Subject: [PATCH 02/20] chore: .gitattributes --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitattributes b/.gitattributes index e69de29..7beaa80 100644 --- a/.gitattributes +++ b/.gitattributes @@ -0,0 +1,2 @@ + +*.rs linguist-language=Rust From e1efea712403ea424af818aa26175bb900770885 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 12:45:34 +1000 Subject: [PATCH 03/20] chore: shortened `description` in **Cargo.toml** --- Cargo.toml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cb9475b..c9ab016 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,11 +14,7 @@ categories = [ "development-tools", "text-processing", ] -description = """ -**shwild** is a small, standalone library, implemented in C++ with a C and a C++ API, that provides shell-compatible wildcard matching. - -**shwild.Rust** is a **Rust** port, with minimal API differences. The design emphasis is on simplicity-of-use, modularity, and performance. -""" +description = "Shell-compatible wildcard matching library" documentation = "https://docs.rs/shwild" edition = "2021" exclude = [ From 51ba2b64a7ae3c524f9e3a1fedd14ae2885dddb2 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 12:49:42 +1000 Subject: [PATCH 04/20] chore: updated **rustfmt.toml** --- rustfmt.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index 41189d4..a32019c 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -7,16 +7,16 @@ # attr_fn_like_width=70 # deprecated binop_separator="Front" blank_lines_lower_bound=0 -blank_lines_upper_bound=2 +blank_lines_upper_bound=3 brace_style="SameLineWhere" # chain_width=60 # deprecated color="Auto" combine_control_expr=false -comment_width=100 +comment_width=76 condense_wildcard_suffixes=false control_brace_style="AlwaysSameLine" disable_all_formatting=false -edition="2018" +edition="2021" empty_item_single_line=false enum_discrim_align_threshold=0 error_on_line_overflow=false @@ -75,7 +75,7 @@ tab_spaces=4 # Q: do we want to move to 2?? trailing_comma="Vertical" trailing_semicolon=true type_punctuation_density="Wide" -# unstable_features=false +unstable_features=true use_field_init_shorthand=true use_small_heuristics="Default" use_try_shorthand=true From 2d504e06c7694be5126ae345cb9ca8d3493bfcfe Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 12:57:40 +1000 Subject: [PATCH 05/20] chore: **README.md** badges --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 20649fa..887a7db 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,13 @@ -# shwild - -**SH**ell-compatible **WILD**cards, for **Rust**. +# shwild.Rust +![Language](https://img.shields.io/badge/Rust-000000?style=flat&logo=rust&logoColor=white) +[![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) [![Crates.io](https://img.shields.io/crates/v/shwild.svg)](https://crates.io/crates/shwild) +[![GitHub release](https://img.shields.io/github/v/release/synesissoftware/shwild.Rust.svg)](https://github.com/synesissoftware/shwild.Rust/releases/latest) +[![Last Commit](https://img.shields.io/github/last-commit/synesissoftware/shwild.Rust)](https://github.com/synesissoftware/shwild.Rust/commits/master) +[![docs.rs](https://docs.rs/shwild/badge.svg)](https://docs.rs/shwild) + +**SH**ell-compatible **WILD**cards, for **Rust** β€” part of the cross-language **shwild** family. ## Table of Contents @@ -30,9 +35,7 @@ ## Introduction -**shwild** is a small, standalone library, implemented in C++ with a C and a C++ API, that provides shell-compatible wildcard matching. - -**shwild.Rust** is a **Rust** port, with minimal API differences. The design emphasis is on simplicity-of-use, modularity, and performance. +**shwild** is a small, standalone library, implemented in C++ with a C and a C++ API, that provides shell-compatible wildcard matching. **shwild.Rust** is a **Rust** port, with minimal API differences. The design emphasis is on simplicity-of-use, modularity, and performance. ```Rust let pattern = r"Where are the* [🐼🐻]s\?"; From d62d30dccdaa70162fec047f42329083607d9b02 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 13:01:41 +1000 Subject: [PATCH 06/20] chore: added examples material --- EXAMPLES.md | 11 +++ README.md | 2 +- examples/character-play.md | 99 ++++++++++++++++++++ examples/list-matching-files-compiled.md | 113 +++++++++++++++++++++++ examples/list-matching-files.md | 109 ++++++++++++++++++++++ 5 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 EXAMPLES.md create mode 100644 examples/character-play.md create mode 100644 examples/list-matching-files-compiled.md create mode 100644 examples/list-matching-files.md diff --git a/EXAMPLES.md b/EXAMPLES.md new file mode 100644 index 0000000..e1f50c6 --- /dev/null +++ b/EXAMPLES.md @@ -0,0 +1,11 @@ +# shwild.Rust Examples + +|Name|Source & Description|Summary| +|---|---|---| +|**list-matching-files**|[examples/list-matching-files/main.rs](/examples/list-matching-files/main.rs)
[examples/list-matching-files.md](/examples/list-matching-files.md)|Lists files in the current directory whose paths match one or more shell wildcard pattern(s), using `shwild::matches()`.| +|**list-matching-files-compiled**|[examples/list-matching-files-compiled/main.rs](/examples/list-matching-files-compiled/main.rs)
[examples/list-matching-files-compiled.md](/examples/list-matching-files-compiled.md)|Same as **list-matching-files**, but patterns are compiled once into `CompiledMatcher` instances before matching.| +|**character-play**|[test/scratch/character-play/main.rs](/test/scratch/character-play/main.rs)
[examples/character-play.md](/examples/character-play.md)|Scratch program exercising **regex** crate Unicode matching behaviour (requires feature `"test-regex"`).| + + + + diff --git a/README.md b/README.md index 887a7db..5db5a54 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ No public traits are defined at this time. ## Examples -T.B.C. +Examples are provided in the ```examples``` directory, along with a markdown description for each. A detailed list TOC of them is provided in [EXAMPLES.md](./EXAMPLES.md). ## Project Information diff --git a/examples/character-play.md b/examples/character-play.md new file mode 100644 index 0000000..3266700 --- /dev/null +++ b/examples/character-play.md @@ -0,0 +1,99 @@ +# shwild.Rust Example - **character-play** + +## Summary + +A scratch program that exercises **regex** crate matching behaviour for Unicode text, including emoji and combining characters. It is intended to support development of the `"test-regex"` feature and the `regex_comparision_tests` unit tests; it does not call **shwild** APIs directly. + + +## Source + +```Rust +// test/scratch/character-play/main.rs : Unicode matching experiments with **regex** + +use regex::Regex; + +fn main() { + { + let re = Regex::new("abc").unwrap(); + + assert!(re.is_match("abc")); + assert!(re.is_match("abcd")); + } + + { + let re = Regex::new("abc$").unwrap(); + + assert!(re.is_match("abc")); + assert!(!re.is_match("abcd")); + } + + { + let re = Regex::new("a🐻c").unwrap(); + + assert!(re.is_match("a🐻c")); + assert!(!re.is_match("abc")); + assert!(re.is_match("a🐻cd")); + } + + { + let re = Regex::new("aΓ©c").unwrap(); + + assert!(re.is_match("aΓ©c")); + assert!(!re.is_match("abc")); + assert!(re.is_match("aΓ©cd")); + } + + { + let re = Regex::new("aéc").unwrap(); + + assert!(re.is_match("aéc")); + assert!(!re.is_match("abc")); + assert!(re.is_match("aécd")); + } + + { + let re = Regex::new("a[πŸ»πŸ‘€πŸ›‘]c").unwrap(); + + assert!(re.is_match("a🐻c")); + assert!(re.is_match("aπŸ‘€c")); + assert!(re.is_match("aπŸ›‘c")); + assert!(!re.is_match("abc")); + assert!(re.is_match("aπŸ›‘cd")); + } + + { + let re = Regex::new("aπŸ‘οΈc").unwrap(); + + assert!(!re.is_match("a🐻c")); + assert!(!re.is_match("aπŸ‘€c")); + assert!(!re.is_match("aπŸ›‘c")); + assert!(re.is_match("aπŸ‘οΈc")); + assert!(!re.is_match("abc")); + assert!(!re.is_match("aπŸ›‘cd")); + } + + { + let re = Regex::new("a[πŸ»πŸ‘€πŸ›‘]c").unwrap(); + + assert!(re.is_match("a🐻c")); + assert!(re.is_match("aπŸ‘€c")); + assert!(re.is_match("aπŸ›‘c")); + assert!(!re.is_match("abc")); + assert!(re.is_match("aπŸ›‘cd")); + } +} +``` + + +## Running and output + +When executed, as in: + +```bash +$ cargo run --example character-play --features test-regex +``` + +it runs to completion with no output on success (all assertions pass). + + + diff --git a/examples/list-matching-files-compiled.md b/examples/list-matching-files-compiled.md new file mode 100644 index 0000000..e426bff --- /dev/null +++ b/examples/list-matching-files-compiled.md @@ -0,0 +1,113 @@ +# shwild.Rust Example - **list-matching-files-compiled** + +## Summary + +An example using **shwild.Rust**'s `CompiledMatcher` to list files in the current directory whose paths match one or more shell wildcard pattern(s) given on the command-line. Each pattern is parsed once up front; this is preferable when the same pattern(s) will be matched repeatedly. When no patterns are specified, `"*"` is assumed. + + +## Source + +```Rust +// examples/list-matching-files-compiled/main.rs : filter files using `CompiledMatcher` + +use std::{ + env as std_env, + fs as std_fs, + process as std_process, +}; + + +fn main() { + let directory = "."; + + let patterns = { + let r = std_env::args().skip(1).collect::>(); + + if r.is_empty() { + vec!["*".into()] + } else { + r + } + }; + let matchers = patterns + .iter() + .map(|pattern| { + shwild::CompiledMatcher::from_pattern_and_flags(&pattern, 0).unwrap_or_else(|e| { + eprintln!("failed to parse pattern '{pattern}': {e}"); + + std_process::exit(1); + }) + }) + .collect::>(); + + println!("searching in '{directory}' with pattern(s) {:?}", patterns); + + match std_fs::read_dir(directory) { + Ok(entries) => { + // for each file in the directory ... + for entry in entries { + match entry { + Ok(entry) => { + let path = entry.path(); + let path_s = format!("{}", path.display()); + + // ... check against ... + for matcher in &matchers { + // ... each pattern ... + if matcher.matches(&path_s) { + // ... and print when it matches any one. + println!("\t{path_s}"); + + break; + } + } + }, + Err(e) => { + eprintln!("failed to read file in '{directory}': {e}"); + }, + }; + } + }, + Err(e) => { + eprintln!("failed to read files in '{directory}': {e}"); + }, + }; +} +``` + + +## Running and output + +When executed, as in: + +```bash +$ cargo run --example list-matching-files-compiled +``` + +it gives output similar to: + +``` +searching in '.' with pattern(s) ["*"] + ./Cargo.toml + ./LICENSE + ./README.md + ... +``` + +With pattern argument(s), as in: + +```bash +$ cargo run --example list-matching-files-compiled -- '*.md' +``` + +it gives output similar to: + +``` +searching in '.' with pattern(s) ["*.md"] + ./README.md + ./CHANGES.md + ./TODO.md +``` + + + diff --git a/examples/list-matching-files.md b/examples/list-matching-files.md new file mode 100644 index 0000000..93a84ca --- /dev/null +++ b/examples/list-matching-files.md @@ -0,0 +1,109 @@ +# shwild.Rust Example - **list-matching-files** + +## Summary + +An example using **shwild.Rust**'s `matches()` function to list files in the current directory whose paths match one or more shell wildcard pattern(s) given on the command-line. When no patterns are specified, `"*"` is assumed. + + +## Source + +```Rust +// examples/list-matching-files/main.rs : filter files by name using `matches()` + +use std::{ + env as std_env, + fs as std_fs, +}; + + +fn main() { + let directory = "."; + + let patterns = { + let r = std_env::args().skip(1).collect::>(); + + if r.is_empty() { + vec!["*".into()] + } else { + r + } + }; + + println!("searching in '{directory}' with pattern(s) {patterns:?}"); + + match std_fs::read_dir(directory) { + Ok(entries) => { + // for each file in the directory ... + for entry in entries { + match entry { + Ok(entry) => { + let path = entry.path(); + let path_s = format!("{}", path.display()); + + // ... check against ... + for pattern in &patterns { + // ... each pattern ... + match shwild::matches(pattern, &path_s, 0) { + Ok(is_matched) => { + if is_matched { + // ... and print when it matches any one. + println!("\t{path_s}"); + + break; + } + }, + Err(e) => { + eprintln!("failed to match against '{path_s}': {e}"); + }, + }; + } + }, + Err(e) => { + eprintln!("failed to read file in '{directory}': {e}"); + }, + }; + } + }, + Err(e) => { + eprintln!("failed to read files in '{directory}': {e}"); + }, + }; +} +``` + + +## Running and output + +When executed, as in: + +```bash +$ cargo run --example list-matching-files +``` + +it gives output similar to: + +``` +searching in '.' with pattern(s) ["*"] + ./Cargo.toml + ./LICENSE + ./README.md + ... +``` + +With pattern argument(s), as in: + +```bash +$ cargo run --example list-matching-files -- '*.md' +``` + +it gives output similar to: + +``` +searching in '.' with pattern(s) ["*.md"] + ./README.md + ./CHANGES.md + ./TODO.md +``` + + + From 952271c7cd6565e08e6227e7faa57c5b328d1647 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 13:06:35 +1000 Subject: [PATCH 07/20] chore: added **CHANGES.md** --- CHANGES.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 CHANGES.md diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..409ae6c --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,42 @@ +# shwild.Rust - CHANGES + + +## 0.1.3 - 28th March 2025 + +* crates.io packaging metadata β€” `categories`, `keywords`, `documentation`, and expanded `description`; +* added `exclude` for `target` and `.github` in **Cargo.toml**; +* added **TODO.md**; +* **character-play** changed from `[[bin]]` to `[[example]]`; + + +## 0.1.2 - 3rd November 2024 + +* added `test-regex` feature β€” optional **regex** dependency for benchmarks and scratch programs; +* enabled `lookup-ranges` in default features; +* added **cw-regex** benchmark; +* added **character-play** scratch program; +* added `regex_comparision_tests` unit tests (gated on `test-regex`); + + +## 0.1.1 - 3rd November 2024 + +* added `lookup-ranges` feature β€” optional **collect-rs** dependency for `UnicodePointMap`-based range matching; +* added **Cargo.lock**; +* README: added Features section; clarified Wild-1 escape behaviour in pattern elements; + + +## 0.1.0 - 3rd November 2024 + +* first public release; +* added `matches()` and `shwild_matches!()`; +* added `CompiledMatcher`; +* added `Error`, `Result`, and `IGNORE_CASE`; +* added example programs **list-matching-files** and **list-matching-files-compiled**; +* added benchmarks **range_string-creation_functions**, **shwild-compiled_matcher**, and **shwild-matches**; +* added **README.md**; + + +All history before this day is moot! + + + From 8323989d021c62d7b503ab1c48c8303fd040ebe8 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 13:11:37 +1000 Subject: [PATCH 08/20] chore: general boilerplate improvements --- CHANGES.md | 11 +++++++++++ NEWS.md | 11 +++++++++++ README.md | 11 +++++++---- src/lib.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 NEWS.md diff --git a/CHANGES.md b/CHANGES.md index 409ae6c..fa8ad8b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,17 @@ # shwild.Rust - CHANGES +## 0.1.4 - 7th July 2026 + +* added **CHANGES.md** (back-filled) and **NEWS.md**; +* added **EXAMPLES.md** and per-example documentation; +* **README.md** badges, dependency links, related projects, and `null-feature` documentation; +* added crate-level `//!` documentation; +* renamed `.rustfmt.toml` => **rustfmt.toml**; updated formatting settings; +* added `.gitattributes`; +* shortened `description` in **Cargo.toml**; + + ## 0.1.3 - 28th March 2025 * crates.io packaging metadata β€” `categories`, `keywords`, `documentation`, and expanded `description`; diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..ac97273 --- /dev/null +++ b/NEWS.md @@ -0,0 +1,11 @@ +# shwild.Rust - NEWS + +| Date | News Item | +| --------------------- | ----------------------------------------- | +| 28th March 2025 | shwild.Rust 0.1.3 released | +| 3rd November 2024 | shwild.Rust 0.1.2 released | +| 3rd November 2024 | shwild.Rust 0.1.1 released | +| 3rd November 2024 | shwild.Rust 0.1.0 released | + + + diff --git a/README.md b/README.md index 5db5a54..39fd839 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ The library (and other **shwild** variants) support the following pattern elemen Reference in **Cargo.toml** in the usual way: ```toml -shwild = { version = "~0.1" } +shwild = { version = "0.1" } ``` @@ -107,6 +107,7 @@ The following crate features are defined: | Name | Effect | Is `"default"`? | Dependent feature(s) | | --------------------------- | ------------------------------------- | --------------- | ------------------------------------- | | `"lookup-ranges"` | Causes match/non-match ranges to be implemented in terms of `UnicodePointMap` (from **collect-rs** crate), resulting in significant performance improvements in parsing and matching | Yes | | +| `"null-feature"` | A feature that has no effect (and, thus, is useful for simplifying driver scripts) | **No** | | | `"test-regex"` | Introduces a dependency to **regex** crate to support benchmark/example program(s) | **No** | | @@ -189,8 +190,8 @@ Defect reports, feature requests, and pull requests are welcome on https://githu **shwild.Rust** has two dependencies, both optional: -* [**collect-rs**]() - required, for more efficient range matching, if feature `"lookup-ranges"` is specified; -* [**regex**]() - required, by some benchmark/example programs only, if feature `"test-regex"` is specified; +* [**collect-rs**](https://github.com/synesissoftware/collect-rs) - required, for more efficient range matching, if feature `"lookup-ranges"` is specified; +* [**regex**](https://github.com/rust-lang/regex) - required, by some benchmark/example programs only, if feature `"test-regex"` is specified; #### Dev Dependencies @@ -203,7 +204,9 @@ Crates upon which **shwild** has development dependencies: ### Related projects -None at this time. +* [**shwild**](https://github.com/synesissoftware/shwild/); +* [**shwild.Go**](https://github.com/synesissoftware/shwild.Go/); +* [**collect-rs**](https://github.com/synesissoftware/collect-rs/); ### License diff --git a/src/lib.rs b/src/lib.rs index 3812d38..2a99bd7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,51 @@ +//! Shell-compatible wildcard matching for Rust β€” part of the cross-language +//! **shwild** family. +//! +//! **shwild** provides shell-style pattern matching: literals, `?` (any one +//! character), `*` (any number of characters), `[`…`]` ranges, `[^`…`]` +//! not-ranges, escapes, and case flags. **shwild.Rust** is a port with +//! minimal API differences from the original **C/C++** library; the design +//! emphasis is simplicity-of-use, modularity, and performance. +//! +//! (See [**shwild.Go**][sg] for the Go implementation.) +//! +//! # Installation +//! +//! Reference in **Cargo.toml** in the usual way: +//! +//! ```toml +//! shwild = { version = "0.1" } +//! ``` +//! +//! # Components +//! +//! * [`matches()`] β€” parse `pattern` and test `input` in one step; +//! * [`shwild_matches!`] β€” shorthand for [`matches()`] (2- or 3-arg); +//! * [`CompiledMatcher`] β€” parse once, match many times; +//! * [`Error`] and [`Result`] β€” parse/match error reporting; +//! * [`IGNORE_CASE`] β€” flag for case-insensitive matching; +//! +//! # Features +//! +//! * `lookup-ranges` (default) β€” range matching via **collect-rs** +//! `UnicodePointMap`; +//! * `null-feature` β€” no effect; useful for driver scripts; +//! * `test-regex` β€” optional **regex** dependency for benchmarks and +//! scratch programs; +//! +//! # Examples +//! +//! ``` +//! use shwild::shwild_matches; +//! +//! assert_eq!(Ok(true), shwild_matches!("*.rs", "lib.rs")); +//! ``` +//! +//! Further examples are in the repository **examples** directory and in +//! the project [README](https://github.com/synesissoftware/shwild.Rust). +//! +//! [sg]: https://github.com/synesissoftware/shwild.Go + // src/lib.rs : Definition of the shwild Rust package // /////////////////////////////////////////////// From 480dfbd592b7dcd261f573fe032452f9fce72dc1 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 13:22:12 +1000 Subject: [PATCH 09/20] chore: add CI and quality tooling for 0.1.4 Introduce GitHub Actions (test, clippy, fmt, doc, checkers, MSRV 1.74) and supporting scripts to align shwild.Rust with other Synesis Rust projects. Includes Clippy and test-naming fixes required for a clean CI run. --- .cargo/config.toml | 3 + .github/workflows/ci.yml | 70 +++++++ .gitignore | 1 + CHANGES.md | 3 + Cargo.lock | 2 +- Cargo.toml | 3 +- README.md | 4 +- benches/cw-regex.rs | 2 - benches/range_string-creation_functions.rs | 38 +--- benches/shwild-compiled_matcher.rs | 2 - benches/shwild-matches.rs | 2 - examples/list-matching-files-compiled/main.rs | 2 +- rustfmt.toml | 2 + scripts/check_derives.py | 129 ++++++++++++ scripts/check_doc_76.py | 66 ++++++ scripts/check_test_names.py | 192 ++++++++++++++++++ scripts/fmt | 12 ++ src/lib.rs | 23 +-- 18 files changed, 495 insertions(+), 61 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 .github/workflows/ci.yml create mode 100755 scripts/check_derives.py create mode 100644 scripts/check_doc_76.py create mode 100644 scripts/check_test_names.py create mode 100755 scripts/fmt diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..bcd5e07 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ +# .cargo/config.toml +# +# Formatting requires nightly rustfmt β€” use ./scripts/fmt (see rustfmt.toml). diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3979618 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,70 @@ +name: CI + +on: + push: + branches: + - master + - dev + - boilerplate + pull_request: + +env: + CARGO_TERM_COLOR: always + +jobs: + check: + name: Test, Clippy, Fmt, Checkers + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + + - name: Install nightly rustfmt + run: rustup toolchain install nightly --profile minimal --component rustfmt + + - uses: Swatinem/rust-cache@v2 + + - name: cargo test + run: cargo test --locked + + - name: cargo clippy + run: cargo clippy --all-targets --locked -- -D warnings + + - name: cargo build (examples) + run: cargo build --examples --locked + + - name: cargo build (character-play example) + run: cargo build --example character-play --features test-regex --locked + + - name: cargo doc + run: cargo doc --no-deps --locked + + - name: rustfmt + run: ./scripts/fmt --check + + - name: DOC_76 checker + run: python3 scripts/check_doc_76.py + + - name: RUST_TEST_NAMING checker + run: python3 scripts/check_test_names.py + + - name: DERIVE_LAYOUT checker + run: python3 scripts/check_derives.py + + msrv: + name: MSRV (1.74) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@1.74.0 + + - uses: Swatinem/rust-cache@v2 + + - name: cargo check (library) + # Full `cargo test` needs dev-deps (criterion β†’ clap_lex 2024 edition), + # which exceeds MSRV 1.74; the stable job runs the full test suite. + run: cargo check --lib --locked diff --git a/.gitignore b/.gitignore index 3589c67..8ea0d84 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # directories (by name) /_build/ +/scripts/__pycache__/ /scratch/ /target/ diff --git a/CHANGES.md b/CHANGES.md index fa8ad8b..336a116 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,9 +7,12 @@ * added **EXAMPLES.md** and per-example documentation; * **README.md** badges, dependency links, related projects, and `null-feature` documentation; * added crate-level `//!` documentation; +* added CI (`.github/workflows/ci.yml`) and quality scripts (`scripts/fmt`, checkers); +* added `rust-version` (MSRV 1.74); * renamed `.rustfmt.toml` => **rustfmt.toml**; updated formatting settings; * added `.gitattributes`; * shortened `description` in **Cargo.toml**; +* Clippy and test-naming fixes for CI; ## 0.1.3 - 28th March 2025 diff --git a/Cargo.lock b/Cargo.lock index be726ec..5c59781 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -421,7 +421,7 @@ dependencies = [ [[package]] name = "shwild" -version = "0.1.3" +version = "0.1.4" dependencies = [ "collect-rs", "criterion", diff --git a/Cargo.toml b/Cargo.toml index c9ab016..8b9df6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,8 @@ license = "BSD-3-Clause" name = "shwild" readme = "README.md" repository = "https://github.com/synesissoftware/shwild.Rust" -version = "0.1.3" +rust-version = "1.74" +version = "0.1.4" # ########################################################## diff --git a/README.md b/README.md index 39fd839..3151b33 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,10 @@ [![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) [![Crates.io](https://img.shields.io/crates/v/shwild.svg)](https://crates.io/crates/shwild) [![GitHub release](https://img.shields.io/github/v/release/synesissoftware/shwild.Rust.svg)](https://github.com/synesissoftware/shwild.Rust/releases/latest) +![MSRV](https://img.shields.io/badge/MSRV-1.74-lightgrey) +[![CI](https://github.com/synesissoftware/shwild.Rust/actions/workflows/ci.yml/badge.svg)](https://github.com/synesissoftware/shwild.Rust/actions/workflows/ci.yml) [![Last Commit](https://img.shields.io/github/last-commit/synesissoftware/shwild.Rust)](https://github.com/synesissoftware/shwild.Rust/commits/master) -[![docs.rs](https://docs.rs/shwild/badge.svg)](https://docs.rs/shwild) +[![docs.rs](https://img.shields.io/docsrs/shwild/badge.svg)](https://docs.rs/shwild) **SH**ell-compatible **WILD**cards, for **Rust** β€” part of the cross-language **shwild** family. diff --git a/benches/cw-regex.rs b/benches/cw-regex.rs index e890801..10c2f6b 100644 --- a/benches/cw-regex.rs +++ b/benches/cw-regex.rs @@ -2,8 +2,6 @@ #![allow(non_snake_case)] -use shwild; - use regex::Regex; use criterion::{ diff --git a/benches/range_string-creation_functions.rs b/benches/range_string-creation_functions.rs index a366992..211b63d 100644 --- a/benches/range_string-creation_functions.rs +++ b/benches/range_string-creation_functions.rs @@ -1,9 +1,6 @@ // benches/range_string-creation_functions.rs : evaluates performance of different range-string creation-function approaches #![allow(non_snake_case)] -#![feature(ascii_char)] - -use shwild; use criterion::{ black_box, @@ -68,7 +65,7 @@ mod utils { chars.dedup(); chars.into_iter().collect() // appears to be (marginally) faster for `Vec<>`, although it's very close - // chars.as_slice().iter().collect() + // chars.as_slice().iter().collect() } pub(super) fn range_string_from_slice_1( @@ -134,7 +131,7 @@ mod utils { chars.dedup(); chars.into_iter().collect() // appears to be (marginally) faster for `Vec<>`, although it's very close - // chars.as_slice().iter().collect() + // chars.as_slice().iter().collect() } pub(super) fn range_string_from_slice_3( @@ -241,37 +238,6 @@ mod utils { // chars.as_slice().iter().collect() } - #[allow(unexpected_cfgs)] - #[cfg(feature = "auto-buffer")] - pub(super) fn range_string_from_slice_6( - chars : &[char], - flags : i64, - ) -> String { - let mut chars = if 0 != (shwild::IGNORE_CASE & flags) { - let mut ci_chars = auto_buffer::AutoBuffer::::with_capacity(chars.len() * 2); - - for (ix, c) in chars.iter().enumerate() { - if c.is_alphabetic() { - ci_chars.push(c.to_ascii_uppercase()); - - ci_chars.push(c.to_ascii_lowercase()); - } else { - ci_chars.push(*c); - } - } - - ci_chars - } else { - chars.into() - }; - - chars.sort_unstable(); - - chars.dedup(); - - chars.into_iter().collect() - } - pub(super) fn range_string_from_slice_7( chars : &[char], flags : i64, diff --git a/benches/shwild-compiled_matcher.rs b/benches/shwild-compiled_matcher.rs index 8674850..b7f7411 100644 --- a/benches/shwild-compiled_matcher.rs +++ b/benches/shwild-compiled_matcher.rs @@ -2,8 +2,6 @@ #![allow(non_snake_case)] -use shwild; - use criterion::{ black_box, criterion_group, diff --git a/benches/shwild-matches.rs b/benches/shwild-matches.rs index e59e051..b340546 100644 --- a/benches/shwild-matches.rs +++ b/benches/shwild-matches.rs @@ -2,8 +2,6 @@ #![allow(non_snake_case)] -use shwild; - use criterion::{ black_box, criterion_group, diff --git a/examples/list-matching-files-compiled/main.rs b/examples/list-matching-files-compiled/main.rs index 1ea0aa2..edeb487 100644 --- a/examples/list-matching-files-compiled/main.rs +++ b/examples/list-matching-files-compiled/main.rs @@ -23,7 +23,7 @@ fn main() { let matchers = patterns .iter() .map(|pattern| { - shwild::CompiledMatcher::from_pattern_and_flags(&pattern, 0).unwrap_or_else(|e| { + shwild::CompiledMatcher::from_pattern_and_flags(pattern, 0).unwrap_or_else(|e| { eprintln!("failed to parse pattern '{pattern}': {e}"); std_process::exit(1); diff --git a/rustfmt.toml b/rustfmt.toml index a32019c..c08d358 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,6 +1,8 @@ # rustfmt.toml for shwild.Rust # +# Requires nightly rustfmt with unstable features enabled β€” use ./scripts/fmt +# # configured for cargo-fmt 1.84.0 # array_width=60 # deprecated diff --git a/scripts/check_derives.py b/scripts/check_derives.py new file mode 100755 index 0000000..5c595ec --- /dev/null +++ b/scripts/check_derives.py @@ -0,0 +1,129 @@ +#! /usr/bin/env python3 +""" +Verify DERIVE_LAYOUT: multi-trait `#[derive(...)]` macros must be split +into separate single-trait lines, ordered alphabetically by trait name, +except tightly coupled groups (Eq/PartialEq, Ord/PartialOrd). +""" + +from __future__ import annotations + +import re +import sys +from pathlib import Path + + +COUPLED_TRAIT_GROUPS = [ + ["Eq", "PartialEq"], + ["Ord", "PartialOrd"], +] + +PASS = "\N{WHITE HEAVY CHECK MARK}" # βœ… +FAIL = "\N{CROSS MARK}" # ❌ + + +def lint_file(filepath: Path) -> list[str]: + errors: list[str] = [] + + lines = filepath.read_text(encoding="utf-8").splitlines() + i = 0 + + while i < len(lines): + line = lines[i] + + if not re.match(r"^\s*#\[derive\(", line): + i += 1 + continue + + derive_block: list[tuple[int, str]] = [] + start_line_num = i + 1 + + while i < len(lines) and re.match(r"^\s*#\[derive\(", lines[i]): + derive_block.append((i + 1, lines[i])) + i += 1 + + parsed_lines: list[tuple[int, str, str]] = [] + block_has_error = False + + for line_num, line_str in derive_block: + match = re.search(r"#\[derive\((.*?)\)\]", line_str) + + if not match: + continue + + traits = [ + t.strip() + for t in match.group(1).split(",") + if t.strip() + ] + + if len(traits) > 1: + if traits not in COUPLED_TRAIT_GROUPS: + block_has_error = True + allowed = ", ".join( + f"'{', '.join(group)}'" + for group in COUPLED_TRAIT_GROUPS + ) + errors.append( + f"{filepath}:{line_num}: multi-trait derive " + f"'{line_str.strip()}' is not allowed " + f"(except coupled groups: {allowed})", + ) + elif len(traits) == 0: + block_has_error = True + errors.append( + f"{filepath}:{line_num}: empty derive attribute " + f"'{line_str.strip()}'", + ) + + sort_key = traits[0] if traits else "" + parsed_lines.append((line_num, line_str, sort_key)) + + if not block_has_error and len(parsed_lines) > 1: + sort_keys = [item[2] for item in parsed_lines] + + if sort_keys != sorted(sort_keys): + actual = [item[1].strip() for item in parsed_lines] + expected = [ + item[1].strip() + for item in sorted(parsed_lines, key=lambda x: x[2]) + ] + errors.append( + f"{filepath}:{start_line_num}: derive attributes not " + f"sorted alphabetically\n" + f" actual: {actual}\n" + f" expected: {expected}", + ) + + return errors + + +def main() -> int: + root = Path(__file__).resolve().parents[1] + errors: list[str] = [] + + for directory in ("src", "examples", "benches", "test"): + base = root / directory + + if not base.is_dir(): + continue + + for path in sorted(base.rglob("*.rs")): + if "target" in path.parts: + continue + + errors.extend(lint_file(path)) + + if errors: + print( + f"{FAIL} DERIVE_LAYOUT violations:", + file=sys.stderr, + ) + print("\n".join(f" {FAIL} {error}" for error in errors), file=sys.stderr) + return 1 + + print(f"{PASS} DERIVE_LAYOUT: ok") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/check_doc_76.py b/scripts/check_doc_76.py new file mode 100644 index 0000000..2a10562 --- /dev/null +++ b/scripts/check_doc_76.py @@ -0,0 +1,66 @@ +#! /usr/bin/env python3 +""" +Verify DOC_76: public documentation comment lines are at most 76 characters. + +Code blocks inside doc comments (``` ... ```) are exempt, matching Synesis +Information Systems' internal project standards. +""" + +from __future__ import annotations + +import re +import sys +from pathlib import Path + + +DOC_LINE = re.compile(r"^\s*(//!|///)") + +PASS = "\N{WHITE HEAVY CHECK MARK}" # βœ… +FAIL = "\N{CROSS MARK}" # ❌ + + +def iter_doc_violations(path: Path) -> list[str]: + violations: list[str] = [] + in_codeblock = False + + for line_no, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): + stripped = line.rstrip() + + if DOC_LINE.match(stripped) and re.search(r"\s*```\s*$", stripped): + in_codeblock = not in_codeblock + continue + + if in_codeblock or not DOC_LINE.match(stripped): + continue + + if len(stripped) > 76: + violations.append( + f"{path}:{line_no} ({len(stripped)} chars): {stripped}" + ) + + return violations + + +def main() -> int: + root = Path(__file__).resolve().parents[1] + errors: list[str] = [] + + for path in sorted(root.rglob("*.rs")): + if "target" in path.parts: + continue + errors.extend(iter_doc_violations(path)) + + if errors: + print( + f"{FAIL} DOC_76 violations (doc comment lines must be <= 76 characters):", + file=sys.stderr, + ) + print("\n".join(f" {FAIL} {error}" for error in errors), file=sys.stderr) + return 1 + + print(f"{PASS} DOC_76: ok") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/check_test_names.py b/scripts/check_test_names.py new file mode 100644 index 0000000..f91c003 --- /dev/null +++ b/scripts/check_test_names.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +""" +Verify RUST_TEST_NAMING: test functions and test modules use TEST_ prefix +and SHOUTING_SNAKE_CASE, except words that name a specific Rust construct +(type, function, macro, field, etc.) which must preserve exact case. +""" + +from __future__ import annotations + +import re +import sys +from pathlib import Path + + +TEST_ATTR = re.compile(r"^\s*#\[(\w+::)?test(\(\))?\]") +FN_DEF = re.compile(r"^\s*fn\s+(\w+)") +MOD_DEF = re.compile(r"^\s*mod\s+(\w+)") +SNAKE_PART = re.compile(r"^[a-z][a-z0-9]*$") +CONSECUTIVE_UPPER = re.compile(r"[A-Z]{2,}") + +PASS = "\N{WHITE HEAVY CHECK MARK}" # βœ… +FAIL = "\N{CROSS MARK}" # ❌ + + +def is_pascal_case_atom(atom: str) -> bool: + return ( + atom[0].isupper() + and any(c.islower() for c in atom) + and not CONSECUTIVE_UPPER.search(atom) + and atom.isalnum() + ) + + +def atom_violation(atom: str) -> str | None: + if atom.isupper() or atom.isdigit(): + return None + + if atom[0].islower() and all(SNAKE_PART.match(part) for part in atom.split("_")): + return None + + if is_pascal_case_atom(atom): + return None + + return ( + f"segment '{atom}' must be SHOUTING_SNAKE_CASE, a PascalCase construct name, " + "or a Rust snake_case identifier" + ) + + +def parse_name_atoms(rest: str) -> tuple[list[str], list[str]]: + """Split a test name body into atoms; return (atoms, violations).""" + atoms: list[str] = [] + violations: list[str] = [] + segments = rest.split("_") + i = 0 + + while i < len(segments): + seg = segments[i] + if not seg: + violations.append(f"empty segment in '{rest}'") + i += 1 + continue + + if seg.isupper() or seg.isdigit(): + reason = atom_violation(seg) + if reason: + violations.append(reason) + else: + atoms.append(seg) + i += 1 + continue + + if seg[0].isupper(): + reason = atom_violation(seg) + if reason: + violations.append(reason) + else: + atoms.append(seg) + i += 1 + continue + + if SNAKE_PART.match(seg): + parts = [seg] + i += 1 + while i < len(segments) and SNAKE_PART.match(segments[i]): + parts.append(segments[i]) + i += 1 + atom = "_".join(parts) + reason = atom_violation(atom) + if reason: + violations.append(reason) + else: + atoms.append(atom) + continue + + violations.append( + f"segment '{seg}' must be SHOUTING_SNAKE_CASE, a PascalCase construct name, " + "or a Rust snake_case identifier" + ) + i += 1 + + return atoms, violations + + +def iter_name_violations(name: str) -> list[str]: + if not name.startswith("TEST_"): + return ["must start with 'TEST_'"] + + rest = name[len("TEST_") :] + if not rest: + return ["must have a name after 'TEST_'"] + + _, violations = parse_name_atoms(rest) + return violations + + +def iter_test_results(path: Path, root: Path) -> list[tuple[bool, str]]: + results: list[tuple[bool, str]] = [] + pending_test = False + display = path.relative_to(root) + + for line_no, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): + stripped = line.rstrip() + + if TEST_ATTR.match(stripped): + pending_test = True + continue + + if pending_test and stripped.startswith("#["): + continue + + fn_match = FN_DEF.match(stripped) + if fn_match: + name = fn_match.group(1) + if pending_test: + pending_test = False + reasons = iter_name_violations(name) + label = f"{display}:{line_no}: test function '{name}'" + if reasons: + for reason in reasons: + results.append((False, f"{label}: {reason}")) + else: + results.append((True, label)) + continue + + mod_match = MOD_DEF.match(stripped) + if mod_match: + pending_test = False + name = mod_match.group(1) + if name.startswith("TEST_"): + reasons = iter_name_violations(name) + label = f"{display}:{line_no}: test module '{name}'" + if reasons: + for reason in reasons: + results.append((False, f"{label}: {reason}")) + else: + results.append((True, label)) + continue + + if stripped and not stripped.startswith("#") and stripped.endswith("{"): + pending_test = False + + return results + + +def main() -> int: + root = Path(__file__).resolve().parents[1] + results: list[tuple[bool, str]] = [] + + for path in sorted(root.rglob("*.rs")): + if "target" in path.parts: + continue + results.extend(iter_test_results(path, root)) + + failures = [line for ok, line in results if not ok] + if failures: + print( + f"{FAIL} RUST_TEST_NAMING violations " + "(test functions and modules must use TEST_ + SHOUTING_SNAKE_CASE):", + file=sys.stderr, + ) + for ok, line in results: + mark = PASS if ok else FAIL + print(f" {mark} {line}", file=sys.stderr) + return 1 + + print(f"{PASS} RUST_TEST_NAMING: ok") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/fmt b/scripts/fmt new file mode 100755 index 0000000..13731a1 --- /dev/null +++ b/scripts/fmt @@ -0,0 +1,12 @@ +#! /usr/bin/env bash +set -euo pipefail + +RUSTFMT="$(rustup which --toolchain nightly rustfmt 2>/dev/null || true)" +if [[ -z "${RUSTFMT}" ]]; then + echo "error: nightly rustfmt is required (see rustfmt.toml)" >&2 + echo " rustup component add rustfmt --toolchain nightly" >&2 + exit 1 +fi + +export RUSTFMT +exec cargo fmt -- --unstable-features "$@" diff --git a/src/lib.rs b/src/lib.rs index 2a99bd7..752ab1e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -149,7 +149,6 @@ mod traits { /// # Returns: /// - `true` - indicates a full match; or /// - `false` - if not a full match. - fn matches( &self, slice : &str, @@ -348,11 +347,7 @@ mod match_structures { let slice_starts_with_literal = slice.starts_with(&self.literal) || match &self.literal_uppercase { Some(literal_uppercase) => { - if slice.len() >= literal_uppercase.len() { - slice.to_uppercase().starts_with(literal_uppercase) - } else { - false - } + slice.len() >= literal_uppercase.len() && slice.to_uppercase().starts_with(literal_uppercase) }, None => false, }; @@ -474,13 +469,12 @@ mod match_structures { mod tests { #![allow(non_snake_case)] + #[cfg(not(feature = "lookup-ranges"))] + use super::super::utils::prepare_range_string; #[cfg(feature = "lookup-ranges")] use super::super::utils::prepare_range_upm_from_slice; use super::{ - super::{ - traits::Match, - utils::prepare_range_string, - }, + super::traits::Match, MatchEnd, MatchLiteral, MatchNotRange, @@ -1937,7 +1931,6 @@ mod tests { #![allow(non_snake_case)] use crate as shwild; - use crate::shwild_matches; mod TEST_CompiledMatcher_PARSING { @@ -3154,10 +3147,10 @@ mod tests { assert!(!matcher.matches("")); assert!(!matcher.matches("Where are the bears?")); - assert_eq!(true, matcher.matches("Where are the 🐻s?")); - assert_eq!(true, matcher.matches("Where are the 🐼s?")); - assert_eq!(true, matcher.matches("Where are their 🐻s?")); - assert_eq!(true, matcher.matches("Where are the big brown 🐻s?")); + assert!(matcher.matches("Where are the 🐻s?")); + assert!(matcher.matches("Where are the 🐼s?")); + assert!(matcher.matches("Where are their 🐻s?")); + assert!(matcher.matches("Where are the big brown 🐻s?")); assert!(!matcher.matches("Where are the teddy-🐻s?")); } } From 32ba106009ecf3d23fd52ed2647743e69cbc6935 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 13:23:27 +1000 Subject: [PATCH 10/20] fix --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 5c59781..9e85b4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "aho-corasick" From a99ae835b662882b4f20eba71aac12224c4ae66f Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 17:51:05 +1000 Subject: [PATCH 11/20] chore: added **.cursor/rules/rust-standards.mdc** --- .cursor/rules/rust-standards.mdc | 47 ++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .cursor/rules/rust-standards.mdc diff --git a/.cursor/rules/rust-standards.mdc b/.cursor/rules/rust-standards.mdc new file mode 100644 index 0000000..077ad04 --- /dev/null +++ b/.cursor/rules/rust-standards.mdc @@ -0,0 +1,47 @@ +--- +description: Rust coding standards for shwild.Rust (aligned with Synesis Information Systems' internal project standards) +globs: **/*.rs +alwaysApply: false +--- + +# Rust Software Quality Rules + + +## DOC_76 (The 76-Rule for Documentation Comments) + +- Documentation comments (`///`, `//!`) on **public** constructs must be at + most **76 characters** per line, including leading indentation; +- Wrap text **greedily** (pack to maximum width) β€” do not wrap early if the + next word fits on the current line; +- Code blocks inside doc comments (triple backticks) are exempt; +- List items in doc comments should end with a semicolon; +- Inline comments (`//`) inside functions are limited to **100 characters** + per line, wrapped greedily; +- Private/non-`pub` constructs are exempt from DOC_76 for their doc + comments; + +Run `./scripts/check_doc_76.py` to verify compliance. + + +## DERIVE_LAYOUT (Derive Layout & Formatting) + +Multi-trait `#[derive(...)]` macros must be split into separate, +single-trait lines, ordered alphabetically by trait name. Tightly coupled +traits may remain on one line: `#[derive(Eq, PartialEq)]`, +`#[derive(Ord, PartialOrd)]`. + +`rustfmt.toml` sets `merge_derives = false` to preserve this layout. + +Run `./scripts/check_derives.py` to verify compliance. + + +## RUST_TEST_NAMING + +All test methods must use `SHOUTING_SNAKE_CASE` - e.g. `TEST_PARSING()` - +except where a word in the snake case represents a specific construct (such +as a struct, enum, type, function, macro, or field name), in which case that +word must preserve its exact correct case - e.g. +`TEST_AutoBuffer_WITH_INTERNAL_SIZE()`, `TEST_doom_scope_1()`, +`TEST_type_name_only_WITH_SomeCustomType()`. + +Run `./scripts/check_test_names.py` to verify compliance. From 1f257f276a2e883e9dc5697b1bbb25b6084c49d9 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 17:56:59 +1000 Subject: [PATCH 12/20] chore: updated criterion from 0.5 => 0.6 --- Cargo.lock | 48 +++++----------------- Cargo.toml | 2 +- benches/cw-regex.rs | 5 +-- benches/range_string-creation_functions.rs | 3 +- benches/shwild-compiled_matcher.rs | 3 +- benches/shwild-matches.rs | 3 +- 6 files changed, 20 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e85b4f..9ba32c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,25 +116,22 @@ dependencies = [ [[package]] name = "criterion" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", - "is-terminal", - "itertools", + "itertools 0.13.0", "num-traits", - "once_cell", "oorandom", "plotters", "rayon", "regex", "serde", - "serde_derive", "serde_json", "tinytemplate", "walkdir", @@ -147,7 +144,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -198,27 +195,19 @@ dependencies = [ ] [[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - -[[package]] -name = "is-terminal" -version = "0.4.13" +name = "itertools" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.52.0", + "either", ] [[package]] name = "itertools" -version = "0.10.5" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -238,12 +227,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "libc" -version = "0.2.161" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" - [[package]] name = "log" version = "0.4.22" @@ -546,16 +529,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", + "windows-sys", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8b9df6b..bdb5d01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,7 +118,7 @@ regex = { version = "1.11", optional = true } [dev-dependencies] -criterion = { version = "0.5" } +criterion = { version = "0.6" } test_help-rs = { version = "0.1" } diff --git a/benches/cw-regex.rs b/benches/cw-regex.rs index 10c2f6b..f4011af 100644 --- a/benches/cw-regex.rs +++ b/benches/cw-regex.rs @@ -2,15 +2,14 @@ #![allow(non_snake_case)] -use regex::Regex; - use criterion::{ - black_box, criterion_group, criterion_main, Criterion, }; +use regex::Regex; +use std::hint::black_box; mod constants { pub(crate) const S_TQBFJOTLD : &str = "The quick brown fox jumps over the lazy dog"; diff --git a/benches/range_string-creation_functions.rs b/benches/range_string-creation_functions.rs index 211b63d..a6e5fc0 100644 --- a/benches/range_string-creation_functions.rs +++ b/benches/range_string-creation_functions.rs @@ -3,13 +3,14 @@ #![allow(non_snake_case)] use criterion::{ - black_box, criterion_group, criterion_main, // BenchmarkId, Criterion, }; +use std::hint::black_box; + mod constants { #![allow(non_upper_case_globals)] diff --git a/benches/shwild-compiled_matcher.rs b/benches/shwild-compiled_matcher.rs index b7f7411..bc7da1c 100644 --- a/benches/shwild-compiled_matcher.rs +++ b/benches/shwild-compiled_matcher.rs @@ -3,12 +3,13 @@ #![allow(non_snake_case)] use criterion::{ - black_box, criterion_group, criterion_main, Criterion, }; +use std::hint::black_box; + mod constants { #![allow(non_upper_case_globals)] diff --git a/benches/shwild-matches.rs b/benches/shwild-matches.rs index b340546..a2dec17 100644 --- a/benches/shwild-matches.rs +++ b/benches/shwild-matches.rs @@ -3,12 +3,13 @@ #![allow(non_snake_case)] use criterion::{ - black_box, criterion_group, criterion_main, Criterion, }; +use std::hint::black_box; + mod constants { #![allow(non_upper_case_globals)] From 0d74fdcf50f1b7e2fb935a61e600817a844605f1 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 18:01:43 +1000 Subject: [PATCH 13/20] chore: upgraded **criterion** from 0.5 => 0.7 --- CHANGES.md | 3 ++- Cargo.lock | 21 ++++++--------------- Cargo.toml | 2 +- NEWS.md | 1 + 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 336a116..1642f4b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,7 @@ # shwild.Rust - CHANGES -## 0.1.4 - 7th July 2026 +## 0.1.4 - 8th July 2026 * added **CHANGES.md** (back-filled) and **NEWS.md**; * added **EXAMPLES.md** and per-example documentation; @@ -13,6 +13,7 @@ * added `.gitattributes`; * shortened `description` in **Cargo.toml**; * Clippy and test-naming fixes for CI; +* upgraded **criterion** from 0.5 => 0.7; ## 0.1.3 - 28th March 2025 diff --git a/Cargo.lock b/Cargo.lock index 9ba32c4..93d3067 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,16 +116,16 @@ dependencies = [ [[package]] name = "criterion" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679" +checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", - "itertools 0.13.0", + "itertools", "num-traits", "oorandom", "plotters", @@ -139,12 +139,12 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" dependencies = [ "cast", - "itertools 0.10.5", + "itertools", ] [[package]] @@ -194,15 +194,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" diff --git a/Cargo.toml b/Cargo.toml index bdb5d01..0cc9efc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,7 +118,7 @@ regex = { version = "1.11", optional = true } [dev-dependencies] -criterion = { version = "0.6" } +criterion = { version = "0.7" } test_help-rs = { version = "0.1" } diff --git a/NEWS.md b/NEWS.md index ac97273..6d66629 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,7 @@ | Date | News Item | | --------------------- | ----------------------------------------- | +| 8th July 2026 | shwild.Rust 0.1.4 released | | 28th March 2025 | shwild.Rust 0.1.3 released | | 3rd November 2024 | shwild.Rust 0.1.2 released | | 3rd November 2024 | shwild.Rust 0.1.1 released | From 34bcaf36142c097c116cc7b75964eb3cd03f6a47 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 18:03:11 +1000 Subject: [PATCH 14/20] fix --- src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 752ab1e..51167f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1426,7 +1426,10 @@ pub type Result = std_result::Result; /// # Examples: /// /// ``` -/// let matcher = shwild::CompiledMatcher::from_pattern_and_flags("a[bc]c?", shwild::IGNORE_CASE).unwrap(); +/// let matcher = shwild::CompiledMatcher::from_pattern_and_flags( +/// "a[bc]c?", +/// shwild::IGNORE_CASE, +/// ).unwrap(); /// /// assert!(matcher.matches("abcd")); /// assert!(matcher.matches("accd")); From 8b17a17742b9e2b9ed48463e6df02c5565cf4d66 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 18:07:43 +1000 Subject: [PATCH 15/20] chore: applying `default-features = false` to all dependencies --- Cargo.lock | 167 ----------------------------------------------------- Cargo.toml | 12 ++-- 2 files changed, 8 insertions(+), 171 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 93d3067..e919a3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,12 +35,6 @@ version = "0.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "556031355dea4fc81a56aa4850032c9b433c926e7296c30780f63ece19ecf082" -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - [[package]] name = "cast" version = "0.3.0" @@ -128,8 +122,6 @@ dependencies = [ "itertools", "num-traits", "oorandom", - "plotters", - "rayon", "regex", "serde", "serde_json", @@ -147,31 +139,6 @@ dependencies = [ "itertools", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - [[package]] name = "crunchy" version = "0.2.2" @@ -209,21 +176,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" -[[package]] -name = "js-sys" -version = "0.3.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - [[package]] name = "memchr" version = "2.7.4" @@ -239,46 +191,12 @@ dependencies = [ "autocfg", ] -[[package]] -name = "once_cell" -version = "1.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" - [[package]] name = "oorandom" version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" -[[package]] -name = "plotters" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" - -[[package]] -name = "plotters-svg" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" -dependencies = [ - "plotters-backend", -] - [[package]] name = "proc-macro2" version = "1.0.89" @@ -297,26 +215,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - [[package]] name = "regex" version = "1.11.1" @@ -449,71 +347,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "wasm-bindgen" -version = "0.2.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" -dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" - -[[package]] -name = "web-sys" -version = "0.3.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "winapi-util" version = "0.1.9" diff --git a/Cargo.toml b/Cargo.toml index 0cc9efc..56e5817 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,14 +112,18 @@ test-regex = [ [dependencies] -collect-rs = { version = "0.2", optional = true } -regex = { version = "1.11", optional = true } +collect-rs = { version = "0.2", optional = true, default-features = false, features = [ +]} +regex = { version = "1.11", optional = true, default-features = false, features = [ +]} [dev-dependencies] -criterion = { version = "0.7" } -test_help-rs = { version = "0.1" } +criterion = { version = "0.7", default-features = false, features = [ +]} +test_help-rs = { version = "0.1", default-features = false, features = [ +]} # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 5b1749e38c889d5b01c10549981bd5dda145eb43 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 18:10:37 +1000 Subject: [PATCH 16/20] fix --- .github/workflows/ci.yml | 8 +++++--- CHANGES.md | 2 +- Cargo.toml | 2 +- README.md | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3979618..5084a8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,16 +55,18 @@ jobs: run: python3 scripts/check_derives.py msrv: - name: MSRV (1.74) + name: MSRV (1.79) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@1.74.0 + - uses: dtolnay/rust-toolchain@1.79.0 - uses: Swatinem/rust-cache@v2 - name: cargo check (library) # Full `cargo test` needs dev-deps (criterion β†’ clap_lex 2024 edition), - # which exceeds MSRV 1.74; the stable job runs the full test suite. + # which exceeds MSRV; the stable job runs the full test suite. + # MSRV 1.79: default `lookup-ranges` β†’ collect-rs β†’ base-traits uses + # `CStr::count_bytes()` (stabilized in Rust 1.79). run: cargo check --lib --locked diff --git a/CHANGES.md b/CHANGES.md index 1642f4b..f854b7e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,7 @@ * **README.md** badges, dependency links, related projects, and `null-feature` documentation; * added crate-level `//!` documentation; * added CI (`.github/workflows/ci.yml`) and quality scripts (`scripts/fmt`, checkers); -* added `rust-version` (MSRV 1.74); +* added `rust-version` (MSRV 1.79); * renamed `.rustfmt.toml` => **rustfmt.toml**; updated formatting settings; * added `.gitattributes`; * shortened `description` in **Cargo.toml**; diff --git a/Cargo.toml b/Cargo.toml index 56e5817..cf0056e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ license = "BSD-3-Clause" name = "shwild" readme = "README.md" repository = "https://github.com/synesissoftware/shwild.Rust" -rust-version = "1.74" +rust-version = "1.79" version = "0.1.4" diff --git a/README.md b/README.md index 3151b33..cdd359e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) [![Crates.io](https://img.shields.io/crates/v/shwild.svg)](https://crates.io/crates/shwild) [![GitHub release](https://img.shields.io/github/v/release/synesissoftware/shwild.Rust.svg)](https://github.com/synesissoftware/shwild.Rust/releases/latest) -![MSRV](https://img.shields.io/badge/MSRV-1.74-lightgrey) +![MSRV](https://img.shields.io/badge/MSRV-1.79-lightgrey) [![CI](https://github.com/synesissoftware/shwild.Rust/actions/workflows/ci.yml/badge.svg)](https://github.com/synesissoftware/shwild.Rust/actions/workflows/ci.yml) [![Last Commit](https://img.shields.io/github/last-commit/synesissoftware/shwild.Rust)](https://github.com/synesissoftware/shwild.Rust/commits/master) [![docs.rs](https://img.shields.io/docsrs/shwild/badge.svg)](https://docs.rs/shwild) From d870a84e4075ff6cbbaf625ee09b7cc592b89948 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 18:20:13 +1000 Subject: [PATCH 17/20] fix --- .cursor/rules/rust-standards.mdc | 4 +++ CHANGES.md | 2 +- scripts/check_test_names.py | 51 ++++++++++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/.cursor/rules/rust-standards.mdc b/.cursor/rules/rust-standards.mdc index 077ad04..5c0edd5 100644 --- a/.cursor/rules/rust-standards.mdc +++ b/.cursor/rules/rust-standards.mdc @@ -44,4 +44,8 @@ word must preserve its exact correct case - e.g. `TEST_AutoBuffer_WITH_INTERNAL_SIZE()`, `TEST_doom_scope_1()`, `TEST_type_name_only_WITH_SomeCustomType()`. +When a construct is embedded as a SHOUTING_SNAKE_CASE constant (or other +construct name), use an extra underscore on each side as a delimiter - e.g. +`TEST_MatcherSequence_WITH_Range_HAVING__IGNORE_CASE__1()`. + Run `./scripts/check_test_names.py` to verify compliance. diff --git a/CHANGES.md b/CHANGES.md index f854b7e..c74781f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,7 +12,7 @@ * renamed `.rustfmt.toml` => **rustfmt.toml**; updated formatting settings; * added `.gitattributes`; * shortened `description` in **Cargo.toml**; -* Clippy and test-naming fixes for CI; +* Clippy and test-naming fixes for CI; `check_test_names.py` allows `__CONSTRUCT__` padding; * upgraded **criterion** from 0.5 => 0.7; diff --git a/scripts/check_test_names.py b/scripts/check_test_names.py index f91c003..f2084a9 100644 --- a/scripts/check_test_names.py +++ b/scripts/check_test_names.py @@ -3,6 +3,10 @@ Verify RUST_TEST_NAMING: test functions and test modules use TEST_ prefix and SHOUTING_SNAKE_CASE, except words that name a specific Rust construct (type, function, macro, field, etc.) which must preserve exact case. + +When a construct name is embedded as a SHOUTING_SNAKE_CASE constant (or +PascalCase construct), it may be delimited with an extra underscore on +each side β€” e.g. HAVING__IGNORE_CASE__1. """ from __future__ import annotations @@ -47,6 +51,45 @@ def atom_violation(atom: str) -> str | None: ) +def parse_padded_construct( + segments: list[str], start: int +) -> tuple[str | None, int, list[str]]: + """Parse __CONSTRUCT__ padding around a shouting or PascalCase atom.""" + violations: list[str] = [] + i = start + while i < len(segments) and not segments[i]: + i += 1 + if i >= len(segments): + return None, i, [f"empty segment padding without construct"] + + seg = segments[i] + atom: str | None = None + + if seg.isupper(): + parts = [seg] + i += 1 + while i < len(segments) and segments[i] and segments[i].isupper(): + parts.append(segments[i]) + i += 1 + atom = "_".join(parts) + reason = atom_violation(atom) + if reason: + violations.append(reason) + elif seg[0].isupper() and is_pascal_case_atom(seg): + atom = seg + reason = atom_violation(atom) + if reason: + violations.append(reason) + i += 1 + else: + return None, start, [f"empty segment padding without construct"] + + while i < len(segments) and not segments[i]: + i += 1 + + return atom, i, violations + + def parse_name_atoms(rest: str) -> tuple[list[str], list[str]]: """Split a test name body into atoms; return (atoms, violations).""" atoms: list[str] = [] @@ -57,8 +100,12 @@ def parse_name_atoms(rest: str) -> tuple[list[str], list[str]]: while i < len(segments): seg = segments[i] if not seg: - violations.append(f"empty segment in '{rest}'") - i += 1 + atom, i, viols = parse_padded_construct(segments, i) + violations.extend(viols) + if atom: + atoms.append(atom) + elif not viols: + violations.append(f"empty segment in '{rest}'") continue if seg.isupper() or seg.isdigit(): From 3cabe6311b5d62b5b413761674f563ae88045de5 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 18:27:39 +1000 Subject: [PATCH 18/20] chore: upgraded **criterion** from 0.7 => 0.8 --- CHANGES.md | 2 +- Cargo.lock | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 5 ++-- 3 files changed, 79 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c74781f..d4484d1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,7 +13,7 @@ * added `.gitattributes`; * shortened `description` in **Cargo.toml**; * Clippy and test-naming fixes for CI; `check_test_names.py` allows `__CONSTRUCT__` padding; -* upgraded **criterion** from 0.5 => 0.7; +* upgraded **criterion** from 0.5 => 0.8; ## 0.1.3 - 28th March 2025 diff --git a/Cargo.lock b/Cargo.lock index e919a3f..7962111 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "anes" version = "0.1.6" @@ -41,6 +50,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cc" +version = "1.2.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -110,10 +129,11 @@ dependencies = [ [[package]] name = "criterion" -version = "0.7.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" dependencies = [ + "alloca", "anes", "cast", "ciborium", @@ -122,6 +142,7 @@ dependencies = [ "itertools", "num-traits", "oorandom", + "page_size", "regex", "serde", "serde_json", @@ -131,9 +152,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.6.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" dependencies = [ "cast", "itertools", @@ -151,6 +172,12 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "half" version = "2.4.1" @@ -176,6 +203,12 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + [[package]] name = "memchr" version = "2.7.4" @@ -197,6 +230,16 @@ version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "proc-macro2" version = "1.0.89" @@ -291,6 +334,12 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "shwild" version = "0.1.4" @@ -347,6 +396,22 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.9" @@ -356,6 +421,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.59.0" diff --git a/Cargo.toml b/Cargo.toml index cf0056e..f4884d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ license = "BSD-3-Clause" name = "shwild" readme = "README.md" repository = "https://github.com/synesissoftware/shwild.Rust" -rust-version = "1.79" +rust-version = "1.74" version = "0.1.4" @@ -120,7 +120,8 @@ regex = { version = "1.11", optional = true, default-features = false, features [dev-dependencies] -criterion = { version = "0.7", default-features = false, features = [ +criterion = { version = "0.8", default-features = false, features = [ + "html_reports", ]} test_help-rs = { version = "0.1", default-features = false, features = [ ]} From 8689ca288f68b3f99d329918bad973e86e89cebb Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 18:42:16 +1000 Subject: [PATCH 19/20] chore: Cargo.toml MSRV --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f4884d2..019941d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ license = "BSD-3-Clause" name = "shwild" readme = "README.md" repository = "https://github.com/synesissoftware/shwild.Rust" -rust-version = "1.74" +rust-version = "1.79" version = "0.1.4" From 1b507c7d5efbd79f64fdcefddee0962454a1d01d Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 7 Jul 2026 18:43:55 +1000 Subject: [PATCH 20/20] chore: **TODO.md** --- TODO.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/TODO.md b/TODO.md index 57628ec..90c33f3 100644 --- a/TODO.md +++ b/TODO.md @@ -3,21 +3,101 @@ ## Table of Contents +- [0.1.4 boilerplate checklist](#014-boilerplate-checklist) +- [Source layout (deferred)](#source-layout-deferred) - [Functional improvements](#functional-improvements) - [Performance improvements](#performance-improvements) +## 0.1.4 boilerplate checklist + +### Cargo packaging + +* [x] `default-features = false` on optional and dev dependencies; +* [x] `rust-version` (MSRV 1.79); +* [x] `version = "0.1.4"`; +* [x] upgraded **criterion** from 0.5 => 0.8; +* [x] ~~narrow `exclude` (drop `.github`)~~ β€” **won't fix** (keep `exclude = ["target", ".github"]` since 0.1.3); + +### CI + +* [x] `.github/workflows/ci.yml` (test, clippy, examples, doc, fmt, checkers); +* [x] Clippy, DOC_76, and test-naming fixes; +* [x] MSRV job on 1.79 (`cargo check --lib --locked`); +* [x] `check_test_names.py` allows `__CONSTRUCT__` padding around construct names; + +### Documentation + +* [x] back-filled **CHANGES.md** (0.1.0–0.1.3) and **0.1.4** entry; +* [x] crate-level `//!` documentation; +* [x] **EXAMPLES.md** and per-example `.md` files; +* [x] **NEWS.md** release table (through 0.1.4); +* [x] **README.md** badges, dependency links, related projects, `null-feature`; +* [x] shortened `description` in **Cargo.toml**; + +### Formatting and quality tooling + +* [x] `.cargo/config.toml`; +* [x] `.cursor/rules/rust-standards.mdc`; +* [x] `.gitattributes`; +* [x] `.gitignore` (`/scripts/__pycache__/`); +* [x] `scripts/check_derives.py`, `check_doc_76.py`, `check_test_names.py`, `fmt`; +* [x] renamed `.rustfmt.toml` => **rustfmt.toml**; + +### Layout and structure + +* [x] ~~flatten examples to `examples/*.rs`~~ β€” **won't fix** (keep `examples/*/main.rs`); +* [x] ~~move **character-play** from `[[example]]` to `[[bin]]`~~ β€” **won't fix** (since 0.1.3); +* [x] ~~normalise bench filenames to snake_case~~ β€” **won't fix** (keep current names); + +### Release + +* [ ] merge `boilerplate` branch and tag **0.1.4** on GitHub; +* [ ] publish **0.1.4** to crates.io; + + +## Source layout (deferred) + +Split `src/lib.rs` (~3,450 lines) into multiple source files. **Defer past 0.1.4** +β€” behaviour-neutral refactor; ship as a dedicated follow-up change. + +**API unit tests remain in `src/lib.rs`** (`#[cfg(test)] mod tests { ... }` and +`regex_comparision_tests`). + +Proposed module layout: + +``` +src/ + lib.rs # crate docs, re-exports, matches(), shwild_matches! macro; + # #[cfg(test)] API unit tests (stay here) + constants.rs # IGNORE_CASE + error.rs # Error + Display/Error trait impls + traits.rs # Match trait + types.rs # CharacterRangeType + match_structures.rs # MatchLiteral, MatchRange, MatchWildN, ... + Match impls + utils.rs # MatcherSequence, parsing helpers, prepare_range_string + compiled_matcher.rs # CompiledMatcher, ParseState, parse logic +``` + +Suggested order of work: + +1. Extract internal modules (`match_structures`, `utils`, `compiled_matcher`, …) + β€” largest line-count reduction with lowest risk; +2. Extract `error`, `constants`, `traits`, `types` β€” thin `lib.rs` faΓ§ade + (Diagnosticism-style); +3. Leave API tests in `lib.rs` (no `tests/mod.rs`). + + ## Functional improvements * [ ] **no-std**; -* [ ] ...; +* [ ] unit-test macros; ## Performance improvements * [ ] special cases (for compiled only) such as `"*brown*"` could just be `strstr()`; -* [ ] ...; +* [ ] thorough optimisation review (including optional "unsafe"); -