Rejigs: Making Regular Expressions Human-Readable

4 months ago 1

Omar

Photo by Aleksei Ieshkin on Unsplash

Regular expressions are powerful, but let’s be honest — they’re also notoriously difficult to read and maintain. What does ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ mean at first glance? If you're like most developers, you'll need a moment (or several) to decode it.

Enter Rejigs — a fluent C# library that transforms the cryptic world of regex into readable, maintainable code. Instead of wrestling with arcane symbols, you can now build regular expressions using intuitive, English-like methods.

Traditional regular expressions suffer from several issues:

  1. Readability: Complex patterns are nearly impossible to understand without deep regex knowledge
  2. Maintainability: Modifying existing patterns is error-prone and time-consuming
  3. Learning Curve: New team members struggle with regex syntax
  4. Documentation: Patterns require extensive comments to explain their purpose

Consider this email validation regex:

^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$

What does it do? How would you modify it to allow underscores in the domain? These simple questions become complex puzzles.

Rejigs transforms the same email validation into this:

var emailRegex =
Rejigs.Create()
.AtStart()
.OneOrMore(r => r.AnyLetterOrDigit().Or().AnyOf("._%+-"))
.Text("@")
.OneOrMore(r => r.AnyLetterOrDigit().Or().AnyOf(".-"))
.Text(".")
.AnyLetterOrDigit().AtLeast(2)
.AtEnd()
.Build();

Now the intent is crystal clear: start at the beginning, match one or more valid email characters, then an @ symbol, then domain characters, then a period, then at least two letters for the top-level domain, and end there.

1. Text Matching and Anchoring

// Match exact text
Rejigs.Create().Text("hello").Build(); // Matches "hello"
// Anchor to start/end
Rejigs.Create()
.AtStart()
.Text("Hello")
.AtEnd()
.Build(); // Matches exactly "Hello", nothing more or less

// Word boundaries
Rejigs.Create()
.AtWordBoundary()
.Text("cat")
.AtWordBoundary()
.Build(); // Matches "cat" as a whole word, not as part of "category"

2. Character Classes Made Simple

Instead of memorizing \d, \w, \s and their counterparts:

var regex =
Rejigs.Create()
.AnyDigit() // \d
.AnyLetterOrDigit() // \w
.AnySpace() // \s
.AnyCharacter() // .
.AnyNonDigit() // \D
.Build();
// Character sets and ranges
var phoneRegex =
Rejigs.Create()
.AnyOf("0123456789-() ") // Custom character set
.AnyInRange('A', 'Z') // Character range
.AnyExcept("@#$") // Everything except these
.Build();

3. Quantifiers That Make Sense

var passwordRegex =
Rejigs.Create()
.AtStart()
.AnyLetterOrDigit().AtLeast(8) // At least 8 characters
.AnyDigit().Exactly(2) // Exactly 2 digits
.AnyOf("!@#$").Between(1, 3) // 1-3 special characters
.AtEnd()
.Build();
// Optional elements
var urlRegex =
Rejigs.Create()
.AtStart()
.Optional("http://") // Optional protocol
.Optional(r => r.Text("www.")) // Optional www
.OneOrMore(r => r.AnyLetterOrDigit()) // Domain name
.Text(".")
.AnyLetterOrDigit()
.AtLeast(2)
.AtEnd()
.Build();

4. Grouping and Alternation

// Either/or patterns
var fileExtension =
Rejigs.Create()
.Text(".")
.Either(
r => r.Text("jpg"),
r => r.Text("png"),
r => r.Text("gif")
)
.Build();
// Complex grouping
var phoneNumber =
Rejigs.Create()
.Optional(r => r.Text("+").AnyDigit().Between(1, 3)) // Country code
.Grouping(r => r.AnyDigit().Exactly(3)) // Area code
.Optional("-")
.Grouping(r => r.AnyDigit().Exactly(3)) // Exchange
.Optional("-")
.Grouping(r => r.AnyDigit().Exactly(4)) // Number
.Build();

Parsing Log Files

var logEntryRegex =
Rejigs.Create()
.AtStart()
.Grouping(r => r.AnyDigit().Exactly(4)) // Year
.Text("-")
.Grouping(r => r.AnyDigit().Exactly(2)) // Month
.Text("-")
.Grouping(r => r.AnyDigit().Exactly(2)) // Day
.AnySpace().OneOrMore() // Whitespace
.Grouping(r => r.AnyExcept(" ").OneOrMore()) // Log level
.AnySpace().OneOrMore()
.Grouping(r => r.AnyCharacter().ZeroOrMore()) // Message
.Build();

URL Validation

var urlRegex =
Rejigs.Create()
.AtStart()
.Either(
r => r.Text("http://"),
r => r.Text("https://")
)
.Optional(r => r.Text("www."))
.OneOrMore(r => r.AnyLetterOrDigit().Or().AnyOf(".-"))
.Optional(r => r.Text(":").AnyDigit().OneOrMore()) // Port
.Optional(r => r.Text("/").AnyCharacter().ZeroOrMore()) // Path
.AtEnd()
.Build();

Raw Patterns When Needed

Sometimes you need the full power of regex. Rejigs doesn’t restrict you:

var advancedRegex =
Rejigs.Create()
.Text("start")
.Pattern(@"(?=.*\d)(?=.*[a-z])(?=.*[A-Z])") // Lookaheads
.Text("end")
.Build();

Regex Options Support

var caseInsensitiveRegex =
Rejigs.Create()
.Text("Hello")
.Build(RegexOptions.IgnoreCase | RegexOptions.Multiline);

1. Self-Documenting Code

Your regex patterns become their own documentation. New team members can understand what a pattern does just by reading the method calls.

2. Easier Maintenance

Need to modify a pattern? Change the fluent method calls instead of deciphering regex syntax. Want to make email domains case-insensitive? Add .Build(RegexOptions.IgnoreCase).

3. Reduced Errors

The fluent API guides you toward correct patterns. No more wondering if you need to escape that dot or whether you have the right number of backslashes.

4. Better Testing

You can build patterns incrementally and test each part:

var basePattern = Rejigs.Create().AtStart().Text("user");
var withDomain = basePattern.Text("@").OneOrMore(r => r.AnyLetterOrDigit());
var complete = withDomain.Text(".com").AtEnd();
// Test each stage
Assert.IsTrue(basePattern.Build().IsMatch("[email protected]"));
Assert.IsTrue(withDomain.Build().IsMatch("[email protected]"));
Assert.IsTrue(complete.Build().IsMatch("[email protected]"));

5. Learning Tool

Rejigs helps developers learn regex concepts without getting lost in syntax. You can always call .Expression to see the generated regex pattern and learn how traditional regex works.

Installation

dotnet add package Rejigs

Basic Usage

using Rejigs;// Create a simple pattern
var pattern =
Rejigs.Create()
.Text("Hello")
.AnySpace()
.Text("World")
.Build();

// Use it like any Regex
bool matches = pattern.IsMatch("Hello World");
string result = pattern.Replace("Hello World", "Hi Earth");

Rejigs generates standard .NET Regex objects, so runtime performance is identical to hand-written regex patterns. The only overhead is during pattern construction, which typically happens once during application startup.

For frequently used patterns, consider caching the built Regex object:

public static class CommonPatterns
{
public static readonly Regex Email =
Rejigs.Create()
.AtStart()
.OneOrMore(r => r.AnyLetterOrDigit().Or().AnyOf("._%+-"))
.Text("@")
.OneOrMore(r => r.AnyLetterOrDigit().Or().AnyOf(".-"))
.Text(".")
.AnyLetterOrDigit().AtLeast(2)
.AtEnd()
.Build();
}

Perfect for:

  • Complex validation patterns
  • Data parsing and extraction
  • Pattern matching in business logic
  • Teams with mixed regex experience levels
  • Applications requiring maintainable text processing

Consider alternatives when:

  • You need maximum performance for simple patterns
  • Working with regex experts who prefer traditional syntax
  • Building very simple patterns (like single-character matches)

Regular expressions don’t have to be cryptic. Rejigs brings clarity to pattern matching, making your code more readable, maintainable, and accessible to your entire team.

Whether you’re validating user input, parsing log files, or extracting data from text, Rejigs helps you express your intent clearly while leveraging the full power of .NET’s regex engine.

Try Rejigs today and transform your regex patterns from mysterious hieroglyphs into readable, maintainable code that your future self (and your teammates) will thank you for.

Read Entire Article