Files
claudemesh/packages/shared/src/utils/wildcard-match.ts
Alejandro Gutiérrez d3163a5bff feat(db): mesh data model — meshes, members, invites, audit log
- pgSchema "mesh" with 4 tables isolating the peer mesh domain
- Enums: visibility, transport, tier, role
- audit_log is metadata-only (E2E encryption enforced at broker/client)
- Cascade on mesh delete, soft-delete via archivedAt/revokedAt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 21:19:32 +01:00

247 lines
6.2 KiB
TypeScript

/* https://github.com/axtgr/wildcard-match */
/**
* Escapes a character if it has a special meaning in regular expressions
* and returns the character as is if it doesn't
*/
function escapeRegExpChar(char: string) {
if (
char === "-" ||
char === "^" ||
char === "$" ||
char === "+" ||
char === "." ||
char === "(" ||
char === ")" ||
char === "|" ||
char === "[" ||
char === "]" ||
char === "{" ||
char === "}" ||
char === "*" ||
char === "?" ||
char === "\\"
) {
return `\\${char}`;
} else {
return char;
}
}
/**
* Escapes all characters in a given string that have a special meaning in regular expressions
*/
function escapeRegExpString(str: string) {
let result = "";
for (const char of str) {
result += escapeRegExpChar(char);
}
return result;
}
/**
* Transforms one or more glob patterns into a RegExp pattern
*/
function transform(
pattern: string | string[],
separator: string | boolean = true,
): string {
if (Array.isArray(pattern)) {
const regExpPatterns = pattern.map((p) => `^${transform(p, separator)}$`);
return `(?:${regExpPatterns.join("|")})`;
}
let separatorSplitter = "";
let separatorMatcher = "";
let wildcard = ".";
if (separator === true) {
// In this case forward slashes in patterns match both forward and backslashes in samples:
//
// `foo/bar` will match `foo/bar`
// will match `foo\bar`
//
separatorSplitter = "/";
separatorMatcher = "[/\\\\]";
wildcard = "[^/\\\\]";
} else if (separator) {
separatorSplitter = separator;
separatorMatcher = escapeRegExpString(separatorSplitter);
if (separatorMatcher.length > 1) {
separatorMatcher = `(?:${separatorMatcher})`;
wildcard = `((?!${separatorMatcher}).)`;
} else {
wildcard = `[^${separatorMatcher}]`;
}
}
// When a separator is explicitly specified in a pattern,
// it MUST match ONE OR MORE separators in a sample:
//
// `foo/bar/` will match `foo//bar///`
// won't match `foo/bar`
//
// When a pattern doesn't have a trailing separator,
// a sample can still optionally have them:
//
// `foo/bar` will match `foo/bar//`
//
// So we use different quantifiers depending on the index of a segment.
const requiredSeparator = separator ? `${separatorMatcher}+?` : "";
const optionalSeparator = separator ? `${separatorMatcher}*?` : "";
const segments = separator ? pattern.split(separatorSplitter) : [pattern];
let result = "";
for (let s = 0; s < segments.length; s++) {
const segment = segments[s]!;
const nextSegment = segments[s + 1]!;
let currentSeparator = "";
if (!segment && s > 0) {
continue;
}
if (separator) {
if (s === segments.length - 1) {
currentSeparator = optionalSeparator;
} else if (nextSegment !== "**") {
currentSeparator = requiredSeparator;
} else {
currentSeparator = "";
}
}
if (separator && segment === "**") {
if (currentSeparator) {
result += s === 0 ? "" : currentSeparator;
result += `(?:${wildcard}*?${currentSeparator})*?`;
}
continue;
}
for (let c = 0; c < segment.length; c++) {
const char = segment[c]!;
if (char === "\\") {
if (c < segment.length - 1) {
result += escapeRegExpChar(segment[c + 1]!);
c++;
}
} else if (char === "?") {
result += wildcard;
} else if (char === "*") {
result += `${wildcard}*?`;
} else {
result += escapeRegExpChar(char);
}
}
result += currentSeparator;
}
return result;
}
export default transform;
interface WildcardMatchOptions {
/** Separator to be used to split patterns and samples into segments */
separator?: string | boolean;
/** Flags to pass to the RegExp */
flags?: string;
}
// This overrides the function's signature because for the end user
// the function is always bound to a RegExp
interface isMatch {
/**
* Tests if a sample string matches the pattern(s)
*
* ```js
* isMatch('foo') //=> true
* ```
*/
(sample: string): boolean;
/** Compiled regular expression */
regexp: RegExp;
/** Original pattern or array of patterns that was used to compile the RegExp */
pattern: string | string[];
/** Options that were used to compile the RegExp */
options: WildcardMatchOptions;
}
function isMatch(regexp: RegExp, sample: string) {
if (typeof sample !== "string") {
throw new TypeError(`Sample must be a string, but ${typeof sample} given`);
}
return regexp.test(sample);
}
/**
* Compiles one or more glob patterns into a RegExp and returns an isMatch function.
* The isMatch function takes a sample string as its only argument and returns `true`
* if the string matches the pattern(s).
*
* ```js
* wildcardMatch('src/*.js')('src/index.js') //=> true
* ```
*
* ```js
* const isMatch = wildcardMatch('*.example.com', '.')
* isMatch('foo.example.com') //=> true
* isMatch('foo.bar.com') //=> false
* ```
*/
function wildcardMatch(
pattern: string | string[],
options?: string | boolean | WildcardMatchOptions,
) {
if (typeof pattern !== "string" && !Array.isArray(pattern)) {
throw new TypeError(
`The first argument must be a single pattern string or an array of patterns, but ${typeof pattern} given`,
);
}
if (typeof options === "string" || typeof options === "boolean") {
options = { separator: options };
}
if (
arguments.length === 2 &&
!(
typeof options === "undefined" ||
(typeof options === "object" && !Array.isArray(options))
)
) {
throw new TypeError(
`The second argument must be an options object or a string/boolean separator, but ${typeof options} given`,
);
}
options = options ?? {};
if (options.separator === "\\") {
throw new Error(
"\\ is not a valid separator because it is used for escaping. Try setting the separator to `true` instead",
);
}
const regexpPattern = transform(pattern, options.separator);
const regexp = new RegExp(`^${regexpPattern}$`, options.flags);
const fn = isMatch.bind(null, regexp) as isMatch;
fn.options = options;
fn.pattern = pattern;
fn.regexp = regexp;
return fn;
}
export { wildcardMatch, isMatch };