The Problem Every Developer Knows
We've all been there. You're reviewing code and encounter something like this:
var regex = new Regex(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=]).{8,}$");
What does it do? π€
You squint at it for 5 minutes, open regex101.com, and finally realize it's password validation. Now imagine coming back to this code 6 months later...
There's a Better Way
What if you could write regex like this instead?
Pattern password = Pattern.With
.StartOfLine
.PositiveLookahead(Pattern.With.Anything.Repeat.ZeroOrMore.Set(Pattern.With.LowercaseLetter))
.PositiveLookahead(Pattern.With.Anything.Repeat.ZeroOrMore.Set(Pattern.With.UppercaseLetter))
.PositiveLookahead(Pattern.With.Anything.Repeat.ZeroOrMore.Digit)
.PositiveLookahead(Pattern.With.Anything.Repeat.ZeroOrMore.Set(Pattern.With.Literal("!@#$%^&*()_+-=")))
.Anything.Repeat.AtLeast(8)
.EndOfLine;
Self-documenting. Readable. Maintainable.
This is Masterly.FluentRegex - and v2.0 just dropped with some killer features! π
What's New in v2.0
1. Named Groups & Backreferences
Extract data with meaningful names:
Pattern phone = Pattern.With
.NamedGroup("area", Pattern.With.Digit.Repeat.Times(3))
.Literal("-")
.NamedGroup("exchange", Pattern.With.Digit.Repeat.Times(3))
.Literal("-")
.NamedGroup("number", Pattern.With.Digit.Repeat.Times(4));
Match match = phone.Match("555-123-4567");
Console.WriteLine(match.Groups["area"].Value); // "555"
Console.WriteLine(match.Groups["exchange"].Value); // "123"
Console.WriteLine(match.Groups["number"].Value); // "4567"
Find duplicate words with backreferences:
Pattern duplicates = Pattern.With
.WordBoundary
.NamedGroup("word", Pattern.With.Word.Repeat.OneOrMore)
.Whitespace.Repeat.OneOrMore
.Backreference("word")
.WordBoundary;
duplicates.IsMatch("the the"); // true
duplicates.IsMatch("hello world"); // false
2. Lookahead & Lookbehind
Zero-width assertions made simple:
// Match digits only if preceded by '$'
Pattern price = Pattern.With
.PositiveLookbehind(Pattern.With.Literal("$"))
.Digit.Repeat.OneOrMore;
var match = price.Match("Price: $100");
Console.WriteLine(match.Value); // "100" (not "$100"!)
// Match 'q' only if NOT followed by 'u'
Pattern qNotU = Pattern.With
.Literal("q")
.NegativeLookahead(Pattern.With.Literal("u"));
qNotU.IsMatch("iraq"); // true
qNotU.IsMatch("queen"); // false
3. Full Matching API
No need to call ToRegex() anymore:
Pattern digits = Pattern.With.Digit.Repeat.OneOrMore;
// Direct matching
digits.IsMatch("abc123"); // true
digits.Match("abc123def456"); // First match: "123"
digits.Matches("abc123def456"); // All matches: ["123", "456"]
// Replace & Split
digits.Replace("a1b2c3", "X"); // "aXbXcX"
digits.Replace("a1b2", m => (int.Parse(m.Value) * 2).ToString()); // "a2b4"
Pattern comma = Pattern.With.Literal(",");
comma.Split("a,b,c"); // ["a", "b", "c"]
4. 20+ Pre-built Common Patterns
Stop reinventing the wheel:
// Email
CommonPatterns.Email().IsMatch("user@example.com"); // true
// URLs
CommonPatterns.Url().IsMatch("https://dev.to"); // true
// IP Addresses
CommonPatterns.IPv4().IsMatch("192.168.1.1"); // true
CommonPatterns.IPv4().IsMatch("256.1.1.1"); // false (validates 0-255!)
// And many more...
CommonPatterns.Guid();
CommonPatterns.HexColor();
CommonPatterns.CreditCard();
CommonPatterns.PhoneNumber();
CommonPatterns.StrongPassword();
CommonPatterns.DateIso();
CommonPatterns.Time24();
// ... and more!
5. Debugging Tools
Understand any pattern instantly:
Pattern pattern = Pattern.With
.StartOfLine
.NamedGroup("digits", Pattern.With.Digit.Repeat.OneOrMore)
.EndOfLine;
Console.WriteLine(pattern.Explain());
Output:
Pattern: ^(?<digits>\d+)$
Explanation:
- Start of line/string
- Named capturing group 'digits'
- Digit [0-9]
- One or more
- End of group
- End of line/string
Validate before runtime errors:
Pattern invalid = Pattern.With.RegEx("[unclosed");
if (!invalid.TryValidate(out string error))
{
Console.WriteLine($"Invalid: {error}");
}
Real-World Example: Log Parser
Pattern logEntry = Pattern.With
.StartOfLine
.Literal("[")
.NamedGroup("date", Pattern.With.Digit.Repeat.Times(4)
.Literal("-").Digit.Repeat.Times(2)
.Literal("-").Digit.Repeat.Times(2))
.Whitespace
.NamedGroup("time", Pattern.With.Digit.Repeat.Times(2)
.Literal(":").Digit.Repeat.Times(2)
.Literal(":").Digit.Repeat.Times(2))
.Literal("]")
.Whitespace
.Literal("[")
.NamedGroup("level", Pattern.With.Word.Repeat.OneOrMore)
.Literal("]")
.Whitespace
.NamedGroup("message", Pattern.With.Anything.Repeat.OneOrMore)
.EndOfLine;
string log = "[2024-01-15 10:30:45] [ERROR] Connection timeout";
Match m = logEntry.Match(log);
Console.WriteLine($"Level: {m.Groups["level"].Value}"); // "ERROR"
Console.WriteLine($"Message: {m.Groups["message"].Value}"); // "Connection timeout"
Get Started
dotnet add package Masterly.FluentRegex
using Masterly.FluentRegex;
// Start building!
Pattern myPattern = Pattern.With
.StartOfLine
.Digit.Repeat.OneOrMore
.EndOfLine;
Links
- π¦ NuGet Package
- π GitHub Repository
- π Full Documentation
What regex patterns do you find yourself writing repeatedly? Drop a comment below - maybe we can add them to the CommonPatterns library! π
If this helps you write better regex, consider giving it a β on GitHub!
Top comments (2)
I used your post and library as inspiration for a post of mine; dev.to/xwero/php-fun-regex-builder.... I hope you don't mind.
No problem.