UNPKG

@softvisio/core

Version:
269 lines (220 loc) • 6.42 kB
import path from "node:path"; import { Minimatch } from "minimatch"; const DEFAULT_OPTIONS = { "dot": true, "nonegate": true, "nocomment": true, "magicalBraces": true, "platform": "linux", }, MATCH_ALL_PATTERNS = new Set( [ // "**", "/**", "!**", "!/**", ] ); export default class GlobPattern { #id; #pattern; #caseSensitive; #allowNegated; #allowBraces; #allowGlobstar; #allowExtglob; #matchBasename; #isNegated = false; #isStatic; #isMatchAll; #minimatch; #regExp; constructor ( pattern, { prefix, caseSensitive = true, allowNegated = true, allowBraces = true, allowGlobstar = true, allowExtglob = true, matchBasename } = {} ) { if ( pattern instanceof GlobPattern ) { this.#pattern = pattern.pattern; } else { this.#pattern = pattern; } this.#caseSensitive = Boolean( caseSensitive ); this.#allowNegated = Boolean( allowNegated ); this.#allowBraces = Boolean( allowBraces ); this.#allowGlobstar = Boolean( allowGlobstar ); this.#allowExtglob = Boolean( allowExtglob ); this.#matchBasename = Boolean( matchBasename ); // negated pattern if ( this.#allowNegated && this.#pattern.startsWith( "!" ) ) { if ( !this.#allowExtglob || !this.#pattern.startsWith( "!(" ) ) { this.#isNegated = true; this.#pattern = this.#pattern.slice( 1 ); } } const options = { ...DEFAULT_OPTIONS, "nocase": !this.#caseSensitive, "nobrace": !this.#allowBraces, "noglobstar": !this.#allowGlobstar, "noext": !this.#allowExtglob, "matchBase": this.#matchBasename, }; // add prefix if ( prefix ) { prefix = prefix.replaceAll( "\\", "/" ); prefix = path.posix.normalize( prefix ); // XXX // prefix = Minimatch.escape( prefix, options ); prefix = prefix // .replaceAll( "*", "\\*" ) .replaceAll( "?", "\\?" ) .replaceAll( "{", "\\{" ) .replaceAll( "}", "\\}" ) .replaceAll( "[", "\\[" ) .replaceAll( "]", "\\]" ) .replaceAll( "(", "\\(" ) .replaceAll( "|", "\\|" ) .replaceAll( ")", "\\)" ); // quote start "!" if ( prefix.startsWith( "!" ) ) { prefix = "\\" + prefix; } if ( prefix.endsWith( "/" ) && this.#pattern.startsWith( "/" ) ) { prefix = prefix.slice( 0, -1 ); } this.#pattern = prefix + this.#pattern; } this.#minimatch = new Minimatch( this.#pattern, options ); // build regular expression if ( !this.#minimatch.makeRe() ) { throw new Error( `Glob pattern "${ this.#pattern }" is not valid` ); } // add negated sign if ( this.#isNegated ) { this.#pattern = "!" + this.#pattern; } // create id if ( this.#caseSensitive ) { this.#id = this.regExp.source; } else { this.#id = this.regExp.source.toLowerCase(); } } // static static new ( pattern, options ) { if ( pattern instanceof this ) { return pattern; } else { return new this( pattern, options ); } } static isValid ( pattern ) { if ( pattern instanceof GlobPattern ) { return true; } try { pattern = new Minimatch( pattern, { ...DEFAULT_OPTIONS, } ); if ( !pattern.makeRe() ) { return false; } return true; } catch { return false; } } static normalizePath ( string, { prefix, normalize } = {} ) { if ( normalize ) { return path.posix.normalize( ( ( prefix || "" ) + string ).replaceAll( "\\", "/" ) ); } else if ( prefix ) { return prefix + string; } else { return string; } } // properties get id () { return this.#id; } get pattern () { return this.#pattern; } get regExp () { if ( this.#regExp == null ) { // match any not-empty string if ( this.isMatchAll ) { this.#regExp = new RegExp( ".+" ); } // match pattern else { this.#regExp = this.#minimatch.regexp; } } return this.#regExp; } get isCaseSensitive () { return this.#caseSensitive; } get allowNegated () { return this.#allowNegated; } get allowBraces () { return this.#allowBraces; } get allowGlobstar () { return this.#allowGlobstar; } get allowExtglob () { return this.#allowExtglob; } get matchBasename () { return this.#matchBasename; } get isNegated () { return this.#isNegated; } get isStatic () { this.#isStatic ??= this.#minimatch.hasMagic(); return this.#isStatic; } get isMatchAll () { if ( this.#isMatchAll == null ) { if ( this.#allowGlobstar ) { this.#isMatchAll = MATCH_ALL_PATTERNS.has( this.#pattern ); } else { this.#isMatchAll = false; } } return this.#isMatchAll; } // public test ( string, { prefix, normalize, ignoreNegated } = {} ) { var res; if ( !string ) { return false; } else if ( this.isMatchAll ) { res = true; } else { string = this.constructor.normalizePath( string, { prefix, normalize } ); res = this.regExp.test( string ); } if ( !ignoreNegated && this.#isNegated ) { return !res; } else { return res; } } toString () { return this.pattern; } toJSON () { return this.toString(); } }