| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- // Reference: https://github.com/gobwas/glob/blob/master/glob.go
- //
- // Compile creates Glob for given pattern and strings (if any present after pattern) as separators.
- // The pattern syntax is:
- //
- // pattern:
- // { term }
- //
- // term:
- // `*` matches any sequence of non-separator characters
- // `**` matches any sequence of characters
- // `?` matches any single non-separator character
- // `[` [ `!` ] { character-range } `]`
- // character class (must be non-empty)
- // `{` pattern-list `}`
- // pattern alternatives
- // c matches character c (c != `*`, `**`, `?`, `\`, `[`, `{`, `}`)
- // `\` c matches character c
- //
- // character-range:
- // c matches character c (c != `\\`, `-`, `]`)
- // `\` c matches character c
- // lo `-` hi matches character c for lo <= c <= hi
- //
- // pattern-list:
- // pattern { `,` pattern }
- // comma-separated (without spaces) patterns
- //
-
- class GlobCompiler {
- nonSeparatorChars: string;
- globPattern: string;
- regexpPattern: string;
- regexp: RegExp;
- pos: number = 0;
-
- #compileChars(): string {
- let result = '';
- if (this.globPattern[this.pos] === '!') {
- this.pos++;
- result += '^';
- }
- while (this.pos < this.globPattern.length) {
- const c = this.globPattern[this.pos];
- this.pos++;
- if (c === ']') {
- return `[${result}]`;
- }
- if (c === '\\') {
- if (this.pos >= this.globPattern.length) {
- throw new Error('Unterminated character class escape');
- }
- this.pos++;
- result += `\\${this.globPattern[this.pos]}`;
- } else {
- result += c;
- }
- }
- throw new Error('Unterminated character class');
- }
-
- #compile(subPattern: boolean = false): string {
- let result = '';
- while (this.pos < this.globPattern.length) {
- const c = this.globPattern[this.pos];
- this.pos++;
- if (subPattern && c === '}') {
- return `(${result})`;
- }
- switch (c) {
- case '*':
- if (this.globPattern[this.pos] !== '*') {
- result += `${this.nonSeparatorChars}*`; // match any sequence of non-separator characters
- } else {
- this.pos++;
- result += '.*'; // match any sequence of characters
- }
- break;
- case '?':
- result += this.nonSeparatorChars; // match any single non-separator character
- break;
- case '[':
- result += this.#compileChars();
- break;
- case '{':
- result += this.#compile(true);
- break;
- case ',':
- result += subPattern ? '|' : ',';
- break;
- case '\\':
- if (this.pos >= this.globPattern.length) {
- throw new Error('No character to escape');
- }
- result += `\\${this.globPattern[this.pos]}`;
- this.pos++;
- break;
- case '.': case '+': case '^': case '$': case '(': case ')': case '|':
- result += `\\${c}`; // escape regexp special characters
- break;
- default:
- result += c;
- }
- }
- return result;
- }
-
- constructor(pattern: string, separators: string = '') {
- const escapedSeparators = separators.replaceAll(/[\^\]\-\\]/g, '\\$&');
- this.nonSeparatorChars = escapedSeparators ? `[^${escapedSeparators}]` : '.';
- this.globPattern = pattern;
- this.regexpPattern = `^${this.#compile()}$`;
- this.regexp = new RegExp(`^${this.regexpPattern}$`);
- }
- }
-
- export function globCompile(pattern: string, separators: string = ''): GlobCompiler {
- return new GlobCompiler(pattern, separators);
- }
-
- export function globMatch(str: string, pattern: string, separators: string = ''): boolean {
- try {
- return globCompile(pattern, separators).regexp.test(str);
- } catch {
- return false;
- }
- }
|