Public beta — not for production use. Data may be wiped at any time. Questions? Contact us.
A
Axiom
/Marketplace/christiangeorgelucas/regex-tools
Sign in

christiangeorgelucas/regex-tools

v0.1.1Go

The regex-tools package provides a set of composable regular-expression nodes that leverage Go's standard library `regexp` package, which is based on Google's RE2 engine. It allows users to efficiently test patterns, find matches, extract named groups, replace matches, split text, and validate regex patterns while ensuring linear-time performance and avoiding catastrophic backtracking.

Published by Christian George Lucas· MIT

regexgopattern-matchingstring-manipulationdata-validationcapture-groupsre2

Use cases

  • Validate user input formats like email addresses
  • Extract structured data from log files
  • Perform bulk text replacements in documents
  • Split CSV data into manageable pieces
  • Lint and validate regex patterns before deployment

Nodes (8)

FindAllunary
RegexFindAllRequest·RegexFindAllResult

The FindAll node identifies every non-overlapping match of a specified RE2 pattern within the input text, returning details such as the full text, byte offsets, and numbered capture groups for each match. It supports a limit on the number of matches returned and indicates if there are additional matches beyond the specified limit.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/regex-tools/axiom"
	gen "christiangeorgelucas/regex-tools/gen"
)

// FindAll returns every non-overlapping match of an RE2 pattern in input,
// leftmost-first, each with its full text, byte offsets, and numbered
// capture groups. limit caps how many matches come back (default 1000,
// hard-capped at 10000); truncated reports whether the cap was hit.
func FindAll(ctx context.Context, ax axiom.Context, input *gen.RegexFindAllRequest) (*gen.RegexFindAllResult, error) {
	re, code := compilePattern(input.GetPattern(), input.GetFlags())
	if code != "" {
		return &gen.RegexFindAllResult{Error: code}, nil
	}

	limit := clampFindLimit(input.GetLimit())
	// Ask for one more than the cap so we can tell whether there were more
	// matches beyond it (truncated) without a second, unbounded pass.
	all := re.FindAllStringSubmatchIndex(input.GetInput(), limit+1)

	truncated := len(all) > limit
	if truncated {
		all = all[:limit]
	}

	matches := make([]*gen.RegexMatch, 0, len(all))
	for _, idx := range all {
		matches = append(matches, matchFromIndex(input.GetInput(), idx))
	}

	return &gen.RegexFindAllResult{
		Matches:   matches,
		Count:     int32(len(matches)),
		Truncated: truncated,
	}, nil
}
FindFirstunary
RegexFindRequest·RegexFindResult

The FindFirst node locates the leftmost match of a specified RE2 pattern within the input text, returning details about the match including the matched substring, its byte offsets, and any capture groups. If no match is found, it returns a structured result indicating the absence of a match.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/regex-tools/axiom"
	gen "christiangeorgelucas/regex-tools/gen"
)

// FindFirst returns the leftmost match of an RE2 pattern in input, with the
// full matched text, its byte offsets, and every numbered capture group's
// text and offsets. found is false when the pattern does not match at all.
func FindFirst(ctx context.Context, ax axiom.Context, input *gen.RegexFindRequest) (*gen.RegexFindResult, error) {
	re, code := compilePattern(input.GetPattern(), input.GetFlags())
	if code != "" {
		return &gen.RegexFindResult{Error: code}, nil
	}
	idx := re.FindStringSubmatchIndex(input.GetInput())
	if idx == nil {
		return &gen.RegexFindResult{Found: false}, nil
	}
	return &gen.RegexFindResult{
		Found: true,
		Match: matchFromIndex(input.GetInput(), idx),
	}, nil
}
FindNamedGroupsunary
RegexNamedGroupsRequest·RegexNamedGroupsResult

The FindNamedGroups node extracts named capture groups from regex matches, returning them as a name-value map for each match. It omits any named groups that did not participate in a match, allowing for clear distinction between matched and unmatched groups.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/regex-tools/axiom"
	gen "christiangeorgelucas/regex-tools/gen"
)

// FindNamedGroups extracts every match's named capture groups as a direct
// name→value map, e.g. pattern `(?P<year>\d{4})-(?P<month>\d{2})` on
// "2026-07" yields {"year":"2026","month":"07"}. A named group that did not
// participate in a given match is omitted from that match's map. limit and
// truncation behave exactly as FindAll's.
func FindNamedGroups(ctx context.Context, ax axiom.Context, input *gen.RegexNamedGroupsRequest) (*gen.RegexNamedGroupsResult, error) {
	re, code := compilePattern(input.GetPattern(), input.GetFlags())
	if code != "" {
		return &gen.RegexNamedGroupsResult{Error: code}, nil
	}

	names := re.SubexpNames() // names[0] is always "" (whole match)
	namesPresent := false
	for _, n := range names {
		if n != "" {
			namesPresent = true
			break
		}
	}

	limit := clampFindLimit(input.GetLimit())
	all := re.FindAllStringSubmatchIndex(input.GetInput(), limit+1)

	truncated := len(all) > limit
	if truncated {
		all = all[:limit]
	}

	text := input.GetInput()
	results := make([]*gen.RegexNamedGroupMatch, 0, len(all))
	for _, idx := range all {
		groups := make(map[string]string)
		for i := 1; i < len(names); i++ {
			if names[i] == "" {
				continue
			}
			gs, ge := idx[2*i], idx[2*i+1]
			if gs < 0 {
				continue // group did not participate in this match
			}
			groups[names[i]] = text[gs:ge]
		}
		results = append(results, &gen.RegexNamedGroupMatch{Groups: groups})
	}

	return &gen.RegexNamedGroupsResult{
		Matches:           results,
		Count:             int32(len(results)),
		Truncated:         truncated,
		GroupNamesPresent: namesPresent,
	}, nil
}
Replaceunary
RegexReplaceRequest·RegexReplaceResult

The Replace node substitutes the first match of a specified RE2 pattern in the input text with a replacement string, allowing for the use of capture groups. If no match is found, the output remains unchanged and an error is returned for malformed input.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/regex-tools/axiom"
	gen "christiangeorgelucas/regex-tools/gen"
)

// Replace substitutes only the first (leftmost) match of an RE2 pattern in
// input with replacement, which may reference capture groups via Go's
// expand syntax ($1, ${1}, ${name}; $$ for a literal dollar). replaced is
// false (output equals input unchanged) when the pattern does not match.
func Replace(ctx context.Context, ax axiom.Context, input *gen.RegexReplaceRequest) (*gen.RegexReplaceResult, error) {
	re, code := compilePattern(input.GetPattern(), input.GetFlags())
	if code != "" {
		return &gen.RegexReplaceResult{Error: code}, nil
	}

	text := input.GetInput()
	idx := re.FindStringSubmatchIndex(text)
	if idx == nil {
		return &gen.RegexReplaceResult{Output: text, Replaced: false}, nil
	}

	expanded := re.ExpandString(nil, input.GetReplacement(), text, idx)
	output := text[:idx[0]] + string(expanded) + text[idx[1]:]
	return &gen.RegexReplaceResult{Output: output, Replaced: true}, nil
}
ReplaceAllunary
RegexReplaceAllRequest·RegexReplaceAllResult

The ReplaceAll node replaces every non-overlapping match of an RE2 pattern in the input text with a specified replacement, returning the modified text and the count of substitutions made. It utilizes Go's regexp.ReplaceAllString functionality, ensuring efficient performance and proper backreference handling.

View source
package nodes

import (
	"context"
	"strings"

	"christiangeorgelucas/regex-tools/axiom"
	gen "christiangeorgelucas/regex-tools/gen"
)

// ReplaceAll substitutes every non-overlapping match of an RE2 pattern in
// input with replacement (same $1/${name} backreference syntax as Replace),
// returning the resulting text and how many substitutions were made. RE2's
// linear-time guarantee bounds the match search itself; a large result (a
// zero-width match repeated many times with a long replacement) is the
// platform's payload/memory concern, not this node's, so it's returned in
// full rather than truncated.
func ReplaceAll(ctx context.Context, ax axiom.Context, input *gen.RegexReplaceAllRequest) (*gen.RegexReplaceAllResult, error) {
	re, code := compilePattern(input.GetPattern(), input.GetFlags())
	if code != "" {
		return &gen.RegexReplaceAllResult{Error: code}, nil
	}

	text := input.GetInput()
	allIdx := re.FindAllStringSubmatchIndex(text, -1)
	if len(allIdx) == 0 {
		return &gen.RegexReplaceAllResult{Output: text, Replacements: 0}, nil
	}

	var b strings.Builder
	b.Grow(len(text))
	last := 0
	for _, idx := range allIdx {
		b.WriteString(text[last:idx[0]])
		b.Write(re.ExpandString(nil, input.GetReplacement(), text, idx))
		last = idx[1]
	}
	b.WriteString(text[last:])

	return &gen.RegexReplaceAllResult{
		Output:       b.String(),
		Replacements: int32(len(allIdx)),
	}, nil
}
Splitunary
RegexSplitRequest·RegexSplitResult

The Split node processes input text by dividing it into segments based on matches of a specified RE2 regular expression pattern, returning the resulting pieces in order. It allows for a limit on the number of pieces returned, with any excess being combined into the last segment.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/regex-tools/axiom"
	gen "christiangeorgelucas/regex-tools/gen"
)

// Split slices input on every match of an RE2 pattern, returning the pieces
// strictly between (and around) matches, in order. A positive limit caps
// the result at that many pieces, Go regexp.Split-style, with the
// remainder folded into the last piece; limit <= 0 (including an omitted
// field, since proto3 has no way to distinguish "not set" from "explicitly
// zero") means "every piece" rather than Go's own literal n==0 "no pieces"
// behavior — a default of "return nothing" would be a footgun for any
// caller that simply omits limit. A large piece count for an unbounded
// request is the platform's response-size concern, not this node's.
func Split(ctx context.Context, ax axiom.Context, input *gen.RegexSplitRequest) (*gen.RegexSplitResult, error) {
	re, code := compilePattern(input.GetPattern(), input.GetFlags())
	if code != "" {
		return &gen.RegexSplitResult{Error: code}, nil
	}

	text := input.GetInput()
	requested := int(input.GetLimit())

	n := -1 // "every piece" (Go's regexp.Split unbounded sentinel)
	if requested > 0 {
		n = requested
	}

	parts := re.Split(text, n)
	return &gen.RegexSplitResult{
		Parts: parts,
		Count: int32(len(parts)),
	}, nil
}
Testunary
RegexTestRequest·RegexTestResult

The Test node evaluates whether a given RE2 regular expression pattern matches any part of the input text, providing a simple yes/no answer. It utilizes Go's efficient linear-time RE2 engine to prevent performance issues from adversarial patterns and returns structured error messages for invalid patterns.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/regex-tools/axiom"
	gen "christiangeorgelucas/regex-tools/gen"
)

// Test reports whether an RE2 pattern matches anywhere in input, using Go's
// linear-time regexp engine — so an adversarial pattern cannot cause
// catastrophic-backtracking ReDoS. Pure function; safe to call repeatedly.
func Test(ctx context.Context, ax axiom.Context, input *gen.RegexTestRequest) (*gen.RegexTestResult, error) {
	re, code := compilePattern(input.GetPattern(), input.GetFlags())
	if code != "" {
		return &gen.RegexTestResult{Error: code}, nil
	}
	return &gen.RegexTestResult{Matched: re.MatchString(input.GetInput())}, nil
}
ValidatePatternunary
RegexValidateRequest·RegexValidateResult

The ValidatePattern node checks the validity of an RE2 regular expression pattern by compiling it without matching against any input. It reports any syntax errors and, if valid, provides the count of capture groups and their names.

View source
package nodes

import (
	"context"
	"fmt"
	"regexp"
	"strings"

	"christiangeorgelucas/regex-tools/axiom"
	gen "christiangeorgelucas/regex-tools/gen"
)

// ValidatePattern compile-checks an RE2 pattern without matching it against
// any input. When invalid, error_message holds Go regexp/syntax's own
// human-readable compile error (naming the offending construct — e.g. an
// unsupported PCRE-only backreference or lookaround RE2 deliberately does
// not support). When valid, group_count and group_names report the
// pattern's numbered capture groups in source order.
func ValidatePattern(ctx context.Context, ax axiom.Context, input *gen.RegexValidateRequest) (*gen.RegexValidateResult, error) {
	pattern := input.GetPattern()
	flags := input.GetFlags()

	if pattern == "" {
		return &gen.RegexValidateResult{Valid: false, ErrorMessage: "pattern must not be empty"}, nil
	}
	for _, c := range flags {
		if !strings.ContainsRune(validFlagChars, c) {
			return &gen.RegexValidateResult{
				Valid:        false,
				ErrorMessage: fmt.Sprintf("invalid flag %q: only i, m, s, U are supported", string(c)),
			}, nil
		}
	}

	full := pattern
	if flags != "" {
		full = "(?" + flags + ")" + pattern
	}
	re, err := regexp.Compile(full)
	if err != nil {
		return &gen.RegexValidateResult{Valid: false, ErrorMessage: err.Error()}, nil
	}

	names := re.SubexpNames() // names[0] is always "" (whole match)
	groupNames := make([]string, 0, len(names)-1)
	if len(names) > 1 {
		groupNames = append(groupNames, names[1:]...)
	}

	return &gen.RegexValidateResult{
		Valid:      true,
		GroupCount: int32(len(groupNames)),
		GroupNames: groupNames,
	}, nil
}

Messages (19)

Download .proto
RegexFindAllRequest
pattern:string

The regular expression pattern to search for in the input text.

input:string

The text in which to search for matches of the specified pattern.

flags:string

Flags that modify the behavior of the regex engine, such as case sensitivity.

limit:int32

The maximum number of matches to return; defaults to 1000 if omitted or less than or equal to 0.

RegexFindAllResult
matches:[]RegexMatch

An array of found matches, each containing the full text and byte offsets.

count:int32

The total number of matches returned in the response.

truncated:bool

A boolean indicating whether the number of matches exceeded the specified limit.

error:string
RegexFindRequest
pattern:string

The regular expression pattern to search for in the input text.

input:string

The text in which to search for the specified pattern.

flags:string

Flags that modify the behavior of the regex engine, such as case sensitivity.

RegexFindResult
found:bool

A boolean indicating whether a match was found.

match:RegexMatch

An object containing details about the matched substring and capture groups, if a match was found.

error:string

A string containing any error message if the pattern compilation fails.

RegexGroup
text:string
start:int32
end:int32
matched:bool
RegexMatch
full_match:string
start:int32
end:int32
groups:[]RegexGroup
RegexNamedGroupMatch

No fields

RegexNamedGroupsRequest
pattern:string

The regex pattern containing named capture groups to be matched against the input string.

input:string

The input string on which the regex pattern will be applied.

flags:string

Optional flags that modify the behavior of the regex matching process.

limit:int32

The maximum number of matches to return; if exceeded, results will be truncated.

RegexNamedGroupsResult
matches:[]RegexNamedGroupMatch

An array of maps where each map contains the named groups and their corresponding values for each match.

count:int32

The total number of matches found, which may be less than the limit if truncation occurred.

truncated:bool

A boolean indicating whether the results were truncated due to exceeding the specified limit.

group_names_present:bool

A boolean indicating whether any named groups were declared in the regex pattern.

error:string
RegexReplaceAllRequest
pattern:string

The regular expression pattern to match against the input text.

input:string

The original text in which the replacements will be made.

replacement:string

The string to replace each match with, supporting backreference syntax.

flags:string

Flags that modify the behavior of the regex matching, such as case sensitivity.

RegexReplaceAllResult
output:string

The resulting text after all replacements have been made.

replacements:int32

The total number of substitutions that were performed.

truncated:bool
error:string
RegexReplaceRequest
pattern:string

The regular expression pattern to search for in the input text.

input:string

The text in which the regex pattern will be searched and replaced.

replacement:string

The string that will replace the matched pattern, which can reference capture groups.

flags:string

Flags that modify the behavior of the regex matching, such as case sensitivity.

RegexReplaceResult
output:string

The resulting text after the replacement has been applied.

replaced:bool

A boolean indicating whether a replacement was made (true) or if the input text remains unchanged (false).

error:string

A structured error message if the input is malformed or if there is an issue with the regex pattern.

RegexSplitRequest
pattern:string

The regular expression pattern used to determine where to split the input text.

input:string

The text string that will be split according to the specified pattern.

flags:string

Flags that modify the behavior of the regular expression matching.

limit:int32

An integer that specifies the maximum number of pieces to return; a value of 0 or less indicates that all pieces should be returned.

RegexSplitResult
parts:[]string
count:int32
truncated:bool
error:string
RegexTestRequest
pattern:string

The regular expression pattern to be tested against the input text.

input:string

The text input in which the regular expression pattern is searched for a match.

flags:string

Optional flags that modify the behavior of the regular expression matching.

RegexTestResult
matched:bool

A boolean indicating whether the pattern matched the input text.

error:string

A structured error message indicating an invalid pattern if applicable.

RegexValidateRequest
pattern:string

The regular expression pattern to be validated.

flags:string

Optional flags that modify the behavior of the regex, such as case sensitivity.

RegexValidateResult
valid:bool

Indicates whether the provided pattern is valid.

error_message:string

Contains a human-readable error message if the pattern is invalid.

group_count:int32

The number of capture groups in the valid pattern.

group_names:[]string

An array of names for each capture group in the pattern, with empty strings for unnamed groups.

Use as a tool in any AI agent

christiangeorgelucas/regex-tools is callable as a tool from any MCP client (Claude, Cursor, or your own agent) via Axiom's hosted MCP server — no install, no download. Add the server, then your agent can search and invoke it, or pin it as a typed tool.

claude mcp add --transport http axiom https://api.axiomide.com/mcp --header "Authorization: Bearer YOUR_AXIOM_API_KEY"

Replace YOUR_AXIOM_API_KEY with a key you create in Console → API Keys.

MCP server setup & other clients →

Use this package

Sign in to import christiangeorgelucas/regex-tools into the graph editor — drop it into a flow and hit run. Or call it directly via the API Reference with your API key.

Sign in to useOpen editor

Stats

Error rate (1h)0.0%
Back to marketplace