This repository was archived by the owner on Feb 24, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathglob.dart
203 lines (169 loc) · 7.57 KB
/
glob.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
// Copyright (c) 2014, 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.
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:path/path.dart' as p;
import 'src/ast.dart';
import 'src/list_tree.dart';
import 'src/parser.dart';
import 'src/utils.dart';
/// Regular expression used to quote globs.
final _quoteRegExp = RegExp(r'[*{[?\\}\],\-()]');
/// A glob for matching and listing files and directories.
///
/// A glob matches an entire string as a path. Although the glob pattern uses
/// POSIX syntax, it can match against POSIX, Windows, or URL paths. The format
/// it expects paths to use is based on the `context` parameter to [Glob.new];
/// it defaults to the current system's syntax.
///
/// Paths are normalized before being matched against a glob, so for example the
/// glob `foo/bar` matches the path `foo/./bar`. A relative glob can match an
/// absolute path and vice versa; globs and paths are both interpreted as
/// relative to `context.current`, which defaults to the current working
/// directory.
///
/// When used as a [Pattern], a glob will return either one or zero matches for
/// a string depending on whether the entire string matches the glob. These
/// matches don't currently have capture groups, although this may change in the
/// future.
class Glob implements Pattern {
/// The pattern used to create this glob.
final String pattern;
/// The context in which paths matched against this glob are interpreted.
final p.Context context;
/// If true, a path matches if it matches the glob itself or is recursively
/// contained within a directory that matches.
final bool recursive;
/// Whether the glob matches paths case-sensitively.
bool get caseSensitive => _ast.caseSensitive;
/// The parsed AST of the glob.
final AstNode _ast;
/// The underlying object used to implement [listFileSystem] and
/// [listFileSystemSync].
///
/// This should not be read directly outside of [_listTreeForFileSystem].
ListTree? _listTree;
/// Keeps track of the previous file system used. If this changes then the
/// [_listTree] must be invalidated.
///
/// This is handled inside of [_listTreeForFileSystem].
FileSystem? _previousFileSystem;
/// Whether [context]'s current directory is absolute.
bool get _contextIsAbsolute =>
_contextIsAbsoluteCache ??= context.isAbsolute(context.current);
bool? _contextIsAbsoluteCache;
/// Whether [pattern] could match absolute paths.
bool get _patternCanMatchAbsolute =>
_patternCanMatchAbsoluteCache ??= _ast.canMatchAbsolute;
bool? _patternCanMatchAbsoluteCache;
/// Whether [pattern] could match relative paths.
bool get _patternCanMatchRelative =>
_patternCanMatchRelativeCache ??= _ast.canMatchRelative;
bool? _patternCanMatchRelativeCache;
/// Returns [contents] with characters that are meaningful in globs
/// backslash-escaped.
static String quote(String contents) =>
contents.replaceAllMapped(_quoteRegExp, (match) => '\\${match[0]}');
/// Creates a new glob with [pattern].
///
/// Paths matched against the glob are interpreted according to [context]. It
/// defaults to the system context.
///
/// If [recursive] is true, this glob matches and lists not only the files and
/// directories it explicitly matches, but anything beneath those as well.
///
/// If [caseSensitive] is true, this glob matches and lists only files whose
/// case matches that of the characters in the glob. Otherwise, it matches
/// regardless of case. This defaults to `false` when [context] is Windows and
/// `true` otherwise.
factory Glob(String pattern,
{p.Context? context, bool recursive = false, bool? caseSensitive}) {
context ??= p.context;
caseSensitive ??= context.style == p.Style.windows ? false : true;
if (recursive) pattern += '{,/**}';
var parser = Parser(pattern, context, caseSensitive: caseSensitive);
return Glob._(pattern, context, parser.parse(), recursive);
}
Glob._(this.pattern, this.context, this._ast, this.recursive);
/// Lists all [FileSystemEntity]s beneath [root] that match the glob in the
/// provided [fileSystem].
///
/// This works much like [Directory.list], but it only lists directories that
/// could contain entities that match the glob. It provides no guarantees
/// about the order of the returned entities, although it does guarantee that
/// only one entity with a given path will be returned.
///
/// [root] defaults to the current working directory.
///
/// [followLinks] works the same as for [Directory.list].
Stream<FileSystemEntity> listFileSystem(FileSystem fileSystem,
{String? root, bool followLinks = true}) {
if (context.style != p.style) {
throw StateError("Can't list glob \"$this\"; it matches "
'${context.style} paths, but this platform uses ${p.style} paths.');
}
return _listTreeForFileSystem(fileSystem)
.list(root: root, followLinks: followLinks);
}
/// Synchronously lists all [FileSystemEntity]s beneath [root] that match the
/// glob in the provided [fileSystem].
///
/// This works much like [Directory.listSync], but it only lists directories
/// that could contain entities that match the glob. It provides no guarantees
/// about the order of the returned entities, although it does guarantee that
/// only one entity with a given path will be returned.
///
/// [root] defaults to the current working directory.
///
/// [followLinks] works the same as for [Directory.list].
List<FileSystemEntity> listFileSystemSync(FileSystem fileSystem,
{String? root, bool followLinks = true}) {
if (context.style != p.style) {
throw StateError("Can't list glob \"$this\"; it matches "
'${context.style} paths, but this platform uses ${p.style} paths.');
}
return _listTreeForFileSystem(fileSystem)
.listSync(root: root, followLinks: followLinks);
}
/// Returns whether this glob matches [path].
bool matches(String path) => matchAsPrefix(path) != null;
@override
Match? matchAsPrefix(String path, [int start = 0]) {
// Globs are like anchored RegExps in that they only match entire paths, so
// if the match starts anywhere after the first character it can't succeed.
if (start != 0) return null;
if (_patternCanMatchAbsolute &&
(_contextIsAbsolute || context.isAbsolute(path))) {
var absolutePath = context.normalize(context.absolute(path));
if (_ast.matches(toPosixPath(context, absolutePath))) {
return GlobMatch(path, this);
}
}
if (_patternCanMatchRelative) {
var relativePath = context.relative(path);
if (_ast.matches(toPosixPath(context, relativePath))) {
return GlobMatch(path, this);
}
}
return null;
}
@override
Iterable<Match> allMatches(String path, [int start = 0]) {
var match = matchAsPrefix(path, start);
return match == null ? [] : [match];
}
@override
String toString() => pattern;
/// Handles getting a possibly cached [ListTree] for a [fileSystem].
ListTree _listTreeForFileSystem(FileSystem fileSystem) {
// Don't use cached trees for in memory file systems to avoid memory leaks.
if (fileSystem is MemoryFileSystem) return ListTree(_ast, fileSystem);
// Throw away our cached `_listTree` if the file system is different.
if (fileSystem != _previousFileSystem) {
_listTree = null;
_previousFileSystem = fileSystem;
}
return _listTree ??= ListTree(_ast, fileSystem);
}
}