75 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			75 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import * as XRegExp from 'xregexp';
 | 
						|
 | 
						|
const dot = /\./g;
 | 
						|
const star = /\*/g;
 | 
						|
const doubleStar = /(^|\/)\*\*($|\/)/g;           // e.g. a/**/b or **/b or a/** but not a**b
 | 
						|
const modifiedPatterns = /(.)\(([^)]+)\)/g;       // e.g. `@(a|b)
 | 
						|
const restParam = /\/:([A-Za-z]+)\*/g;            // e.g. `:rest*`
 | 
						|
const namedParam = /\/:([A-Za-z]+)/g;             // e.g. `:api`
 | 
						|
const possiblyEmptyInitialSegments = /^\.🐷\//g;  // e.g. `**/a` can also match `a`
 | 
						|
const possiblyEmptySegments = /\/\.🐷\//g;        // e.g. `a/**/b` can also match `a/b`
 | 
						|
const willBeStar = /🐷/g;                         // e.g. `a**b` not matched by previous rule
 | 
						|
 | 
						|
export class FirebaseGlob {
 | 
						|
  pattern: string;
 | 
						|
  regex: XRegExp;
 | 
						|
  namedParams: { [key: string]: boolean } = {};
 | 
						|
  restParams: { [key: string]: boolean } = {};
 | 
						|
  constructor(glob: string) {
 | 
						|
    try {
 | 
						|
      const pattern = glob
 | 
						|
          .replace(dot, '\\.')
 | 
						|
          .replace(modifiedPatterns, replaceModifiedPattern)
 | 
						|
          .replace(restParam, (_, param) => {
 | 
						|
            // capture the rest of the string
 | 
						|
            this.restParams[param] = true;
 | 
						|
            return `(?:/(?<${param}>.🐷))?`;
 | 
						|
          })
 | 
						|
          .replace(namedParam, (_, param) => {
 | 
						|
            // capture the named parameter
 | 
						|
            this.namedParams[param] = true;
 | 
						|
            return `/(?<${param}>[^/]+)`;
 | 
						|
          })
 | 
						|
          .replace(doubleStar, '$1.🐷$2')                 // use the pig to avoid replacing ** in next rule
 | 
						|
          .replace(star, '[^/]*')                         // match a single segment
 | 
						|
          .replace(possiblyEmptyInitialSegments, '(?:.*)')// deal with **/ special cases
 | 
						|
          .replace(possiblyEmptySegments, '(?:/|/.*/)')   // deal with /**/ special cases
 | 
						|
          .replace(willBeStar, '*');                      // other ** matches
 | 
						|
      this.pattern = `^${pattern}$`;
 | 
						|
      this.regex = XRegExp(this.pattern);
 | 
						|
    } catch (e) {
 | 
						|
      throw new Error(`Error in FirebaseGlob: "${glob}" - ${e.message}`);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  test(url: string) {
 | 
						|
    return XRegExp.test(url, this.regex);
 | 
						|
  }
 | 
						|
 | 
						|
  match(url: string) {
 | 
						|
    const match = XRegExp.exec(url, this.regex);
 | 
						|
    if (match) {
 | 
						|
      const result = {};
 | 
						|
      const names = (this.regex as any).xregexp.captureNames || [];
 | 
						|
      names.forEach(name => result[name] = match[name]);
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function replaceModifiedPattern(_, modifier, pattern) {
 | 
						|
  switch (modifier) {
 | 
						|
    case '!':
 | 
						|
      throw new Error(`"not" expansions are not supported: "${_}"`);
 | 
						|
    case '?':
 | 
						|
    case '+':
 | 
						|
      return `(${pattern})${modifier}`;
 | 
						|
    case '*':
 | 
						|
      return `(${pattern})🐷`;  // it will become a star
 | 
						|
    case '@':
 | 
						|
      return `(${pattern})`;
 | 
						|
    default:
 | 
						|
      throw new Error(`unknown expansion type: "${modifier}" in "${_}"`);
 | 
						|
  }
 | 
						|
}
 |