From 629c8bc8d67dba72f9173979a6a7f460246840da Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 9 Jul 2026 17:20:44 -0400 Subject: [PATCH 1/4] claude: default fromRegex to fullmatch, add fullmatch(boolean) opt-out --- RELEASE.md | 6 ++++++ src/main/java/dev/hegel/Generators.java | 8 ++++--- .../dev/hegel/generators/RegexGenerator.java | 21 +++++++++++++++---- src/test/java/dev/hegel/ConformanceTest.java | 15 ++++++++++++- 4 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 RELEASE.md diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..6299ee7 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,6 @@ +RELEASE_TYPE: major + +`Generators.fromRegex` now generates strings where the *entire* string matches +the pattern, instead of strings that merely *contain* a match somewhere within +them. To restore the old behaviour, opt out with +`fromRegex(pattern).fullmatch(false)`. diff --git a/src/main/java/dev/hegel/Generators.java b/src/main/java/dev/hegel/Generators.java index d476cc6..e5d4c13 100644 --- a/src/main/java/dev/hegel/Generators.java +++ b/src/main/java/dev/hegel/Generators.java @@ -590,12 +590,14 @@ public static DurationGenerator durations() { } /** - * Generates strings matching a (Python-compatible) regular expression. + * Generates strings matching a (Python-compatible) regular expression. By default the entire + * string matches the pattern; use {@link RegexGenerator#fullmatch(boolean) fullmatch(false)} + * to generate strings that merely contain a match. * * @param pattern the regex pattern * @return a regex generator */ - public static Generator fromRegex(String pattern) { - return new RegexGenerator(pattern); + public static RegexGenerator fromRegex(String pattern) { + return new RegexGenerator(pattern, true); } } diff --git a/src/main/java/dev/hegel/generators/RegexGenerator.java b/src/main/java/dev/hegel/generators/RegexGenerator.java index f0c2aa3..21eb868 100644 --- a/src/main/java/dev/hegel/generators/RegexGenerator.java +++ b/src/main/java/dev/hegel/generators/RegexGenerator.java @@ -5,20 +5,33 @@ import dev.hegel.Generator; /** - * Generates strings matching a (Python-compatible) regular expression. Always basic (one engine - * call). + * Generates strings matching a (Python-compatible) regular expression. By default the entire + * string matches the pattern; use {@link #fullmatch(boolean) fullmatch(false)} to generate strings + * that merely contain a match. Always basic (one engine call). */ public final class RegexGenerator implements Generator { private final String pattern; + private final boolean fullmatch; - public RegexGenerator(String pattern) { + public RegexGenerator(String pattern, boolean fullmatch) { this.pattern = pattern; + this.fullmatch = fullmatch; + } + + /** + * @param fullmatch whether the entire string must match the pattern (the default), or merely + * contain a match somewhere within it + * @return a copy with the fullmatch behaviour set + */ + public RegexGenerator fullmatch(boolean fullmatch) { + return new RegexGenerator(pattern, fullmatch); } /** @hidden */ @Override public BasicGenerator asBasic() { - CBORObject schema = CBORObject.NewMap().Add("type", "regex").Add("pattern", pattern); + CBORObject schema = + CBORObject.NewMap().Add("type", "regex").Add("pattern", pattern).Add("fullmatch", fullmatch); return new BasicGenerator<>(schema, Cbor::asString); } } diff --git a/src/test/java/dev/hegel/ConformanceTest.java b/src/test/java/dev/hegel/ConformanceTest.java index 8289867..36eb818 100644 --- a/src/test/java/dev/hegel/ConformanceTest.java +++ b/src/test/java/dev/hegel/ConformanceTest.java @@ -43,6 +43,7 @@ import java.time.ZoneOffset; import java.util.List; import java.util.Objects; +import java.util.regex.Pattern; import org.junit.jupiter.api.Test; /** Behaviour/conformance suite exercising every generator against the real engine. */ @@ -194,7 +195,19 @@ void formatGenerators() { assertAllExamples(ipAddresses().v6(), s -> s.contains(":")); assertAllExamples(ipAddresses(), s -> s.contains(".") || s.contains(":")); assertAllExamples(uuids(), Objects::nonNull); - assertAllExamples(fromRegex("[0-9]{3}"), s -> s.matches(".*[0-9]{3}.*")); + } + + @Test + void regexDefaultsToFullmatch() { + // By default the entire string matches the pattern (String.matches is a full match). + assertAllExamples(fromRegex("[0-9]{3}"), s -> s.matches("[0-9]{3}")); + // fullmatch(false) relaxes to contains-a-match: every example contains a match... + assertAllExamples( + fromRegex("[0-9]{3}").fullmatch(false), + s -> Pattern.compile("[0-9]{3}").matcher(s).find()); + // ...and surrounding characters genuinely occur. + assertTrue(!findAny(fromRegex("[0-9]{3}").fullmatch(false), s -> !s.matches("[0-9]{3}")) + .matches("[0-9]{3}")); } @Test From a592ef233579bd76be56e7488d231c51e9e7d0b7 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 9 Jul 2026 17:25:27 -0400 Subject: [PATCH 2/4] claude: address review: drop tautological assert, add RELEASE.md example --- RELEASE.md | 11 +++++++++-- src/test/java/dev/hegel/ConformanceTest.java | 5 ++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 6299ee7..8531d0c 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -2,5 +2,12 @@ RELEASE_TYPE: major `Generators.fromRegex` now generates strings where the *entire* string matches the pattern, instead of strings that merely *contain* a match somewhere within -them. To restore the old behaviour, opt out with -`fromRegex(pattern).fullmatch(false)`. +them. + +```java +// before: could generate "xy123z" — the pattern only had to appear somewhere +// after: generates only exact matches like "123" +fromRegex("[0-9]{3}") +// restore the old behaviour: +fromRegex("[0-9]{3}").fullmatch(false) +``` diff --git a/src/test/java/dev/hegel/ConformanceTest.java b/src/test/java/dev/hegel/ConformanceTest.java index 36eb818..556e0f1 100644 --- a/src/test/java/dev/hegel/ConformanceTest.java +++ b/src/test/java/dev/hegel/ConformanceTest.java @@ -205,9 +205,8 @@ void regexDefaultsToFullmatch() { assertAllExamples( fromRegex("[0-9]{3}").fullmatch(false), s -> Pattern.compile("[0-9]{3}").matcher(s).find()); - // ...and surrounding characters genuinely occur. - assertTrue(!findAny(fromRegex("[0-9]{3}").fullmatch(false), s -> !s.matches("[0-9]{3}")) - .matches("[0-9]{3}")); + // ...and surrounding characters genuinely occur (findAny throws if no such example exists). + findAny(fromRegex("[0-9]{3}").fullmatch(false), s -> !s.matches("[0-9]{3}")); } @Test From 5da2a847861b8588ec77f663ea6c9037616019ed Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 9 Jul 2026 17:36:13 -0400 Subject: [PATCH 3/4] claude: trim RELEASE.md to the single-sentence changelog entry --- RELEASE.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 8531d0c..e24bafd 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,13 +1,3 @@ RELEASE_TYPE: major -`Generators.fromRegex` now generates strings where the *entire* string matches -the pattern, instead of strings that merely *contain* a match somewhere within -them. - -```java -// before: could generate "xy123z" — the pattern only had to appear somewhere -// after: generates only exact matches like "123" -fromRegex("[0-9]{3}") -// restore the old behaviour: -fromRegex("[0-9]{3}").fullmatch(false) -``` +This release changes the default value of `fullmatch` in `fromRegex` from `false` to `true`. From d51b212f99cb76055c47bd5260e37bd1cd90bf21 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 9 Jul 2026 18:02:39 -0400 Subject: [PATCH 4/4] claude: RELEASE_TYPE minor, not major (zerover: breaking changes are minor pre-1.0) --- RELEASE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE.md b/RELEASE.md index e24bafd..4924614 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,3 +1,3 @@ -RELEASE_TYPE: major +RELEASE_TYPE: minor This release changes the default value of `fullmatch` in `fromRegex` from `false` to `true`.