blob: 3fa8a1a942bbd6f9a75c1d4f3d0a1afcacdf3488 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// A pattern that matches against filesystem path-like strings with wildcards.
///
/// The pattern matches strings as follows:
/// * The pattern must use `/` as the path separator.
/// * The whole string must match, not a substring.
/// * Any non wildcard is matched as a literal.
/// * '*' matches one or more characters except '/'.
/// * '?' matches exactly one character except '/'.
/// * '**' matches one or more characters including '/'.
class Glob {
/// The special characters are: \ ^ $ . | + [ ] ( ) { }
/// as defined here: http://ecma-international.org/ecma-262/5.1/#sec-15.10
static final RegExp _specialChars = RegExp(r'([\\\^\$\.\|\+\[\]\(\)\{\}])');
/// The path separator used to separate components in file paths.
final String _separator;
/// The pattern string.
final String _pattern;
String _suffix;
RegExp _regex;
Glob(this._separator, this._pattern) {
if (_hasJustPrefix(_pattern, '**/*')) {
_suffix = _pattern.substring(4).toLowerCase();
} else if (_hasJustPrefix(_pattern, '**')) {
_suffix = _pattern.substring(2).toLowerCase();
} else {
_regex = _regexpFromGlobPattern(_pattern);
}
}
@override
int get hashCode => _pattern.hashCode;
@override
bool operator ==(Object other) => other is Glob && _pattern == other._pattern;
/// Return `true` if the given [path] matches this glob.
/// The given [path] must use the same [_separator] as the glob.
bool matches(String path) {
String posixPath = _toPosixPath(path);
if (_suffix != null) {
return posixPath.toLowerCase().endsWith(_suffix);
}
return _regex.matchAsPrefix(posixPath) != null;
}
@override
String toString() => _pattern;
/// Return the Posix version of the given [path].
String _toPosixPath(String path) {
if (_separator == '/') {
return path;
}
return path.replaceAll(_separator, '/');
}
/// Return `true` if the [pattern] start with the given [prefix] and does
/// not have `*` or `?` characters after the [prefix].
static bool _hasJustPrefix(String pattern, String prefix) {
if (pattern.startsWith(prefix)) {
int prefixLength = prefix.length;
return !pattern.contains('*', prefixLength) &&
!pattern.contains('?', prefixLength);
}
return false;
}
static RegExp _regexpFromGlobPattern(String pattern) {
StringBuffer sb = StringBuffer();
sb.write('^');
List<String> chars = pattern.split('');
for (int i = 0; i < chars.length; i++) {
String c = chars[i];
if (_specialChars.hasMatch(c)) {
sb.write(r'\');
sb.write(c);
} else if (c == '*') {
if (i + 1 < chars.length && chars[i + 1] == '*') {
sb.write('.*');
i++;
} else {
sb.write('[^/]*');
}
} else if (c == '?') {
sb.write('[^/]');
} else {
sb.write(c);
}
}
sb.write(r'$');
return RegExp(sb.toString(), caseSensitive: false);
}
}