Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(35)

Side by Side Diff: pkg/path/lib/path.dart

Issue 62753005: Refactor pkg/path. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: remove dummy file Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 /// A comprehensive, cross-platform path manipulation library. 5 /// A comprehensive, cross-platform path manipulation library.
6 /// 6 ///
7 /// ## Installing ## 7 /// ## Installing ##
8 /// 8 ///
9 /// Use [pub][] to install this package. Add the following to your 9 /// Use [pub][] to install this package. Add the following to your
10 /// `pubspec.yaml` file. 10 /// `pubspec.yaml` file.
(...skipping 19 matching lines...) Expand all
30 /// These manipulate path strings based on your current working directory and 30 /// These manipulate path strings based on your current working directory and
31 /// the path style (POSIX, Windows, or URLs) of the host platform. For example: 31 /// the path style (POSIX, Windows, or URLs) of the host platform. For example:
32 /// 32 ///
33 /// path.join("directory", "file.txt"); 33 /// path.join("directory", "file.txt");
34 /// 34 ///
35 /// This calls the top-level [join] function to join "directory" and "file.txt" 35 /// This calls the top-level [join] function to join "directory" and "file.txt"
36 /// using the current platform's directory separator. 36 /// using the current platform's directory separator.
37 /// 37 ///
38 /// If you want to work with paths for a specific platform regardless of the 38 /// If you want to work with paths for a specific platform regardless of the
39 /// underlying platform that the program is running on, you can create a 39 /// underlying platform that the program is running on, you can create a
40 /// [Builder] and give it an explicit [Style]: 40 /// [Context] and give it an explicit [Style]:
41 /// 41 ///
42 /// var builder = new path.Builder(style: Style.windows); 42 /// var context = new path.Context(style: Style.windows);
43 /// builder.join("directory", "file.txt"); 43 /// context.join("directory", "file.txt");
44 /// 44 ///
45 /// This will join "directory" and "file.txt" using the Windows path separator, 45 /// This will join "directory" and "file.txt" using the Windows path separator,
46 /// even when the program is run on a POSIX machine. 46 /// even when the program is run on a POSIX machine.
47 library path; 47 library path;
48 48
49 /// A default builder for manipulating POSIX paths. 49 import 'src/context.dart';
50 final posix = new Builder(style: Style.posix); 50 import 'src/style.dart';
51 51
52 /// A default builder for manipulating Windows paths. 52 export 'src/context.dart';
53 final windows = new Builder(style: Style.windows); 53 export 'src/path_exception.dart';
54 export 'src/style.dart';
54 55
55 /// A default builder for manipulating URLs. 56 /// A default context for manipulating POSIX paths.
56 final url = new Builder(style: Style.url); 57 final posix = new Context(style: Style.posix);
57 58
58 /// Inserts [length] elements in front of the [list] and fills them with the 59 /// A default context for manipulating Windows paths.
59 /// [fillValue]. 60 final windows = new Context(style: Style.windows);
60 void _growListFront(List list, int length, fillValue) => 61
61 list.insertAll(0, new List.filled(length, fillValue)); 62 /// A default context for manipulating URLs.
63 final url = new Context(style: Style.url);
62 64
63 /// The result of [Uri.base] last time the current working directory was 65 /// The result of [Uri.base] last time the current working directory was
64 /// calculated. 66 /// calculated.
65 /// 67 ///
66 /// This is used to invalidate [_cachedBuilder] when the working directory has 68 /// This is used to invalidate [_cachedContext] when the working directory has
67 /// changed since the last time a function was called. 69 /// changed since the last time a function was called.
68 Uri _lastBaseUri; 70 Uri _lastBaseUri;
69 71
70 /// An internal builder for the current OS so we can provide a straight 72 /// An internal context for the current OS so we can provide a straight
71 /// functional interface and not require users to create one. 73 /// functional interface and not require users to create one.
72 Builder get _builder { 74 Context get _context {
73 if (_cachedBuilder != null && Uri.base == _lastBaseUri) return _cachedBuilder; 75 if (_cachedContext != null && Uri.base == _lastBaseUri) return _cachedContext;
74 _lastBaseUri = Uri.base; 76 _lastBaseUri = Uri.base;
75 _cachedBuilder = new Builder(); 77 _cachedContext = new Context();
76 return _cachedBuilder; 78 return _cachedContext;
77 } 79 }
78 Builder _cachedBuilder; 80 Context _cachedContext;
79 81
80 /// Gets the path to the current working directory. 82 /// Gets the path to the current working directory.
81 /// 83 ///
82 /// In the browser, this means the current URL, without the last file segment. 84 /// In the browser, this means the current URL, without the last file segment.
83 String get current { 85 String get current {
84 var uri = Uri.base; 86 var uri = Uri.base;
85 if (Style.platform == Style.url) { 87 if (Style.platform == Style.url) {
86 return uri.resolve('.').toString(); 88 return uri.resolve('.').toString();
87 } else { 89 } else {
88 var path = uri.toFilePath(); 90 var path = uri.toFilePath();
89 // Remove trailing '/' or '\'. 91 // Remove trailing '/' or '\'.
90 int lastIndex = path.length - 1; 92 int lastIndex = path.length - 1;
91 assert(path[lastIndex] == '/' || path[lastIndex] == '\\'); 93 assert(path[lastIndex] == '/' || path[lastIndex] == '\\');
92 return path.substring(0, lastIndex); 94 return path.substring(0, lastIndex);
93 } 95 }
94 } 96 }
95 97
96 /// Gets the path separator for the current platform. This is `\` on Windows 98 /// Gets the path separator for the current platform. This is `\` on Windows
97 /// and `/` on other platforms (including the browser). 99 /// and `/` on other platforms (including the browser).
98 String get separator => _builder.separator; 100 String get separator => _context.separator;
99 101
100 /// Converts [path] to an absolute path by resolving it relative to the current 102 /// Creates a new path by appending the given path parts to [current].
101 /// working directory. If [path] is already an absolute path, just returns it. 103 /// Equivalent to [join()] with [current] as the first argument. Example:
102 /// 104 ///
103 /// path.absolute('foo/bar.txt'); // -> /your/current/dir/foo/bar.txt 105 /// path.absolute('path', 'to/foo'); // -> '/your/current/dir/path/to/foo'
104 String absolute(String path) => join(current, path); 106 String absolute(String part1, [String part2, String part3, String part4,
107 String part5, String part6, String part7]) =>
Bob Nystrom 2013/11/18 20:43:21 This is nice.
108 _context.absolute(part1, part2, part3, part4, part5, part6, part7);
105 109
106 /// Gets the part of [path] after the last separator. 110 /// Gets the part of [path] after the last separator.
107 /// 111 ///
108 /// path.basename('path/to/foo.dart'); // -> 'foo.dart' 112 /// path.basename('path/to/foo.dart'); // -> 'foo.dart'
109 /// path.basename('path/to'); // -> 'to' 113 /// path.basename('path/to'); // -> 'to'
110 /// 114 ///
111 /// Trailing separators are ignored. 115 /// Trailing separators are ignored.
112 /// 116 ///
113 /// builder.basename('path/to/'); // -> 'to' 117 /// context.basename('path/to/'); // -> 'to'
Bob Nystrom 2013/11/18 20:43:21 Use "path" here and below.
nweiz 2013/11/19 20:44:53 Done.
114 String basename(String path) => _builder.basename(path); 118 String basename(String path) => _context.basename(path);
115 119
116 /// Gets the part of [path] after the last separator, and without any trailing 120 /// Gets the part of [path] after the last separator, and without any trailing
117 /// file extension. 121 /// file extension.
118 /// 122 ///
119 /// path.basenameWithoutExtension('path/to/foo.dart'); // -> 'foo' 123 /// path.basenameWithoutExtension('path/to/foo.dart'); // -> 'foo'
120 /// 124 ///
121 /// Trailing separators are ignored. 125 /// Trailing separators are ignored.
122 /// 126 ///
123 /// builder.basenameWithoutExtension('path/to/foo.dart/'); // -> 'foo' 127 /// context.basenameWithoutExtension('path/to/foo.dart/'); // -> 'foo'
124 String basenameWithoutExtension(String path) => 128 String basenameWithoutExtension(String path) =>
125 _builder.basenameWithoutExtension(path); 129 _context.basenameWithoutExtension(path);
126 130
127 /// Gets the part of [path] before the last separator. 131 /// Gets the part of [path] before the last separator.
128 /// 132 ///
129 /// path.dirname('path/to/foo.dart'); // -> 'path/to' 133 /// path.dirname('path/to/foo.dart'); // -> 'path/to'
130 /// path.dirname('path/to'); // -> 'path' 134 /// path.dirname('path/to'); // -> 'path'
131 /// 135 ///
132 /// Trailing separators are ignored. 136 /// Trailing separators are ignored.
133 /// 137 ///
134 /// builder.dirname('path/to/'); // -> 'path' 138 /// context.dirname('path/to/'); // -> 'path'
135 /// 139 ///
136 /// If an absolute path contains no directories, only a root, then the root 140 /// If an absolute path contains no directories, only a root, then the root
137 /// is returned. 141 /// is returned.
138 /// 142 ///
139 /// path.dirname('/'); // -> '/' (posix) 143 /// path.dirname('/'); // -> '/' (posix)
140 /// path.dirname('c:\'); // -> 'c:\' (windows) 144 /// path.dirname('c:\'); // -> 'c:\' (windows)
141 /// 145 ///
142 /// If a relative path has no directories, then '.' is returned. 146 /// If a relative path has no directories, then '.' is returned.
143 /// 147 ///
144 /// path.dirname('foo'); // -> '.' 148 /// path.dirname('foo'); // -> '.'
145 /// path.dirname(''); // -> '.' 149 /// path.dirname(''); // -> '.'
146 String dirname(String path) => _builder.dirname(path); 150 String dirname(String path) => _context.dirname(path);
147 151
148 /// Gets the file extension of [path]: the portion of [basename] from the last 152 /// Gets the file extension of [path]: the portion of [basename] from the last
149 /// `.` to the end (including the `.` itself). 153 /// `.` to the end (including the `.` itself).
150 /// 154 ///
151 /// path.extension('path/to/foo.dart'); // -> '.dart' 155 /// path.extension('path/to/foo.dart'); // -> '.dart'
152 /// path.extension('path/to/foo'); // -> '' 156 /// path.extension('path/to/foo'); // -> ''
153 /// path.extension('path.to/foo'); // -> '' 157 /// path.extension('path.to/foo'); // -> ''
154 /// path.extension('path/to/foo.dart.js'); // -> '.js' 158 /// path.extension('path/to/foo.dart.js'); // -> '.js'
155 /// 159 ///
156 /// If the file name starts with a `.`, then that is not considered the 160 /// If the file name starts with a `.`, then that is not considered the
157 /// extension: 161 /// extension:
158 /// 162 ///
159 /// path.extension('~/.bashrc'); // -> '' 163 /// path.extension('~/.bashrc'); // -> ''
160 /// path.extension('~/.notes.txt'); // -> '.txt' 164 /// path.extension('~/.notes.txt'); // -> '.txt'
161 String extension(String path) => _builder.extension(path); 165 String extension(String path) => _context.extension(path);
162 166
163 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed. 167 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed.
164 /// Returns the root of [path], if it's absolute, or the empty string if it's 168 /// Returns the root of [path], if it's absolute, or the empty string if it's
165 /// relative. 169 /// relative.
166 /// 170 ///
167 /// // Unix 171 /// // Unix
168 /// path.rootPrefix('path/to/foo'); // -> '' 172 /// path.rootPrefix('path/to/foo'); // -> ''
169 /// path.rootPrefix('/path/to/foo'); // -> '/' 173 /// path.rootPrefix('/path/to/foo'); // -> '/'
170 /// 174 ///
171 /// // Windows 175 /// // Windows
172 /// path.rootPrefix(r'path\to\foo'); // -> '' 176 /// path.rootPrefix(r'path\to\foo'); // -> ''
173 /// path.rootPrefix(r'C:\path\to\foo'); // -> r'C:\' 177 /// path.rootPrefix(r'C:\path\to\foo'); // -> r'C:\'
174 /// 178 ///
175 /// // URL 179 /// // URL
176 /// path.rootPrefix('path/to/foo'); // -> '' 180 /// path.rootPrefix('path/to/foo'); // -> ''
177 /// path.rootPrefix('http://dartlang.org/path/to/foo'); 181 /// path.rootPrefix('http://dartlang.org/path/to/foo');
178 /// // -> 'http://dartlang.org' 182 /// // -> 'http://dartlang.org'
179 String rootPrefix(String path) => _builder.rootPrefix(path); 183 String rootPrefix(String path) => _context.rootPrefix(path);
180 184
181 /// Returns `true` if [path] is an absolute path and `false` if it is a 185 /// Returns `true` if [path] is an absolute path and `false` if it is a
182 /// relative path. 186 /// relative path.
183 /// 187 ///
184 /// On POSIX systems, absolute paths start with a `/` (forward slash). On 188 /// On POSIX systems, absolute paths start with a `/` (forward slash). On
185 /// Windows, an absolute path starts with `\\`, or a drive letter followed by 189 /// Windows, an absolute path starts with `\\`, or a drive letter followed by
186 /// `:/` or `:\`. For URLs, absolute paths either start with a protocol and 190 /// `:/` or `:\`. For URLs, absolute paths either start with a protocol and
187 /// optional hostname (e.g. `http://dartlang.org`, `file://`) or with a `/`. 191 /// optional hostname (e.g. `http://dartlang.org`, `file://`) or with a `/`.
188 /// 192 ///
189 /// URLs that start with `/` are known as "root-relative", since they're 193 /// URLs that start with `/` are known as "root-relative", since they're
190 /// relative to the root of the current URL. Since root-relative paths are still 194 /// relative to the root of the current URL. Since root-relative paths are still
191 /// absolute in every other sense, [isAbsolute] will return true for them. They 195 /// absolute in every other sense, [isAbsolute] will return true for them. They
192 /// can be detected using [isRootRelative]. 196 /// can be detected using [isRootRelative].
193 bool isAbsolute(String path) => _builder.isAbsolute(path); 197 bool isAbsolute(String path) => _context.isAbsolute(path);
194 198
195 /// Returns `true` if [path] is a relative path and `false` if it is absolute. 199 /// Returns `true` if [path] is a relative path and `false` if it is absolute.
196 /// On POSIX systems, absolute paths start with a `/` (forward slash). On 200 /// On POSIX systems, absolute paths start with a `/` (forward slash). On
197 /// Windows, an absolute path starts with `\\`, or a drive letter followed by 201 /// Windows, an absolute path starts with `\\`, or a drive letter followed by
198 /// `:/` or `:\`. 202 /// `:/` or `:\`.
199 bool isRelative(String path) => _builder.isRelative(path); 203 bool isRelative(String path) => _context.isRelative(path);
200 204
201 /// Returns `true` if [path] is a root-relative path and `false` if it's not. 205 /// Returns `true` if [path] is a root-relative path and `false` if it's not.
202 /// 206 ///
203 /// URLs that start with `/` are known as "root-relative", since they're 207 /// URLs that start with `/` are known as "root-relative", since they're
204 /// relative to the root of the current URL. Since root-relative paths are still 208 /// relative to the root of the current URL. Since root-relative paths are still
205 /// absolute in every other sense, [isAbsolute] will return true for them. They 209 /// absolute in every other sense, [isAbsolute] will return true for them. They
206 /// can be detected using [isRootRelative]. 210 /// can be detected using [isRootRelative].
207 /// 211 ///
208 /// No POSIX and Windows paths are root-relative. 212 /// No POSIX and Windows paths are root-relative.
209 bool isRootRelative(String path) => _builder.isRootRelative(path); 213 bool isRootRelative(String path) => _context.isRootRelative(path);
210 214
211 /// Joins the given path parts into a single path using the current platform's 215 /// Joins the given path parts into a single path using the current platform's
212 /// [separator]. Example: 216 /// [separator]. Example:
213 /// 217 ///
214 /// path.join('path', 'to', 'foo'); // -> 'path/to/foo' 218 /// path.join('path', 'to', 'foo'); // -> 'path/to/foo'
215 /// 219 ///
216 /// If any part ends in a path separator, then a redundant separator will not 220 /// If any part ends in a path separator, then a redundant separator will not
217 /// be added: 221 /// be added:
218 /// 222 ///
219 /// path.join('path/', 'to', 'foo'); // -> 'path/to/foo 223 /// path.join('path/', 'to', 'foo'); // -> 'path/to/foo
220 /// 224 ///
221 /// If a part is an absolute path, then anything before that will be ignored: 225 /// If a part is an absolute path, then anything before that will be ignored:
222 /// 226 ///
223 /// path.join('path', '/to', 'foo'); // -> '/to/foo' 227 /// path.join('path', '/to', 'foo'); // -> '/to/foo'
224 String join(String part1, [String part2, String part3, String part4, 228 String join(String part1, [String part2, String part3, String part4,
225 String part5, String part6, String part7, String part8]) => 229 String part5, String part6, String part7, String part8]) =>
226 _builder.join(part1, part2, part3, part4, part5, part6, part7, part8); 230 _context.join(part1, part2, part3, part4, part5, part6, part7, part8);
227 231
228 /// Joins the given path parts into a single path using the current platform's 232 /// Joins the given path parts into a single path using the current platform's
229 /// [separator]. Example: 233 /// [separator]. Example:
230 /// 234 ///
231 /// path.joinAll(['path', 'to', 'foo']); // -> 'path/to/foo' 235 /// path.joinAll(['path', 'to', 'foo']); // -> 'path/to/foo'
232 /// 236 ///
233 /// If any part ends in a path separator, then a redundant separator will not 237 /// If any part ends in a path separator, then a redundant separator will not
234 /// be added: 238 /// be added:
235 /// 239 ///
236 /// path.joinAll(['path/', 'to', 'foo']); // -> 'path/to/foo 240 /// path.joinAll(['path/', 'to', 'foo']); // -> 'path/to/foo
237 /// 241 ///
238 /// If a part is an absolute path, then anything before that will be ignored: 242 /// If a part is an absolute path, then anything before that will be ignored:
239 /// 243 ///
240 /// path.joinAll(['path', '/to', 'foo']); // -> '/to/foo' 244 /// path.joinAll(['path', '/to', 'foo']); // -> '/to/foo'
241 /// 245 ///
242 /// For a fixed number of parts, [join] is usually terser. 246 /// For a fixed number of parts, [join] is usually terser.
243 String joinAll(Iterable<String> parts) => _builder.joinAll(parts); 247 String joinAll(Iterable<String> parts) => _context.joinAll(parts);
244 248
245 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed. 249 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed.
246 /// Splits [path] into its components using the current platform's [separator]. 250 /// Splits [path] into its components using the current platform's [separator].
247 /// 251 ///
248 /// path.split('path/to/foo'); // -> ['path', 'to', 'foo'] 252 /// path.split('path/to/foo'); // -> ['path', 'to', 'foo']
249 /// 253 ///
250 /// The path will *not* be normalized before splitting. 254 /// The path will *not* be normalized before splitting.
251 /// 255 ///
252 /// path.split('path/../foo'); // -> ['path', '..', 'foo'] 256 /// path.split('path/../foo'); // -> ['path', '..', 'foo']
253 /// 257 ///
254 /// If [path] is absolute, the root directory will be the first element in the 258 /// If [path] is absolute, the root directory will be the first element in the
255 /// array. Example: 259 /// array. Example:
256 /// 260 ///
257 /// // Unix 261 /// // Unix
258 /// path.split('/path/to/foo'); // -> ['/', 'path', 'to', 'foo'] 262 /// path.split('/path/to/foo'); // -> ['/', 'path', 'to', 'foo']
259 /// 263 ///
260 /// // Windows 264 /// // Windows
261 /// path.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo'] 265 /// path.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo']
262 /// 266 ///
263 /// // Browser 267 /// // Browser
264 /// path.split('http://dartlang.org/path/to/foo'); 268 /// path.split('http://dartlang.org/path/to/foo');
265 /// // -> ['http://dartlang.org', 'path', 'to', 'foo'] 269 /// // -> ['http://dartlang.org', 'path', 'to', 'foo']
266 List<String> split(String path) => _builder.split(path); 270 List<String> split(String path) => _context.split(path);
267 271
268 /// Normalizes [path], simplifying it by handling `..`, and `.`, and 272 /// Normalizes [path], simplifying it by handling `..`, and `.`, and
269 /// removing redundant path separators whenever possible. 273 /// removing redundant path separators whenever possible.
270 /// 274 ///
271 /// path.normalize('path/./to/..//file.text'); // -> 'path/file.txt' 275 /// path.normalize('path/./to/..//file.text'); // -> 'path/file.txt'
272 String normalize(String path) => _builder.normalize(path); 276 String normalize(String path) => _context.normalize(path);
273 277
274 /// Attempts to convert [path] to an equivalent relative path from the current 278 /// Attempts to convert [path] to an equivalent relative path from the current
275 /// directory. 279 /// directory.
276 /// 280 ///
277 /// // Given current directory is /root/path: 281 /// // Given current directory is /root/path:
278 /// path.relative('/root/path/a/b.dart'); // -> 'a/b.dart' 282 /// path.relative('/root/path/a/b.dart'); // -> 'a/b.dart'
279 /// path.relative('/root/other.dart'); // -> '../other.dart' 283 /// path.relative('/root/other.dart'); // -> '../other.dart'
280 /// 284 ///
281 /// If the [from] argument is passed, [path] is made relative to that instead. 285 /// If the [from] argument is passed, [path] is made relative to that instead.
282 /// 286 ///
283 /// path.relative('/root/path/a/b.dart', 287 /// path.relative('/root/path/a/b.dart',
284 /// from: '/root/path'); // -> 'a/b.dart' 288 /// from: '/root/path'); // -> 'a/b.dart'
285 /// path.relative('/root/other.dart', 289 /// path.relative('/root/other.dart',
286 /// from: '/root/path'); // -> '../other.dart' 290 /// from: '/root/path'); // -> '../other.dart'
287 /// 291 ///
288 /// If [path] and/or [from] are relative paths, they are assumed to be relative 292 /// If [path] and/or [from] are relative paths, they are assumed to be relative
289 /// to the current directory. 293 /// to the current directory.
290 /// 294 ///
291 /// Since there is no relative path from one drive letter to another on Windows, 295 /// Since there is no relative path from one drive letter to another on Windows,
292 /// or from one hostname to another for URLs, this will return an absolute path 296 /// or from one hostname to another for URLs, this will return an absolute path
293 /// in those cases. 297 /// in those cases.
294 /// 298 ///
295 /// // Windows 299 /// // Windows
296 /// path.relative(r'D:\other', from: r'C:\home'); // -> 'D:\other' 300 /// path.relative(r'D:\other', from: r'C:\home'); // -> 'D:\other'
297 /// 301 ///
298 /// // URL 302 /// // URL
299 /// path.relative('http://dartlang.org', from: 'http://pub.dartlang.org'); 303 /// path.relative('http://dartlang.org', from: 'http://pub.dartlang.org');
300 /// // -> 'http://dartlang.org' 304 /// // -> 'http://dartlang.org'
301 String relative(String path, {String from}) => 305 String relative(String path, {String from}) =>
302 _builder.relative(path, from: from); 306 _context.relative(path, from: from);
303 307
304 /// Returns `true` if [child] is a path beneath `parent`, and `false` otherwise. 308 /// Returns `true` if [child] is a path beneath `parent`, and `false` otherwise.
305 /// 309 ///
306 /// path.isWithin('/root/path', '/root/path/a'); // -> true 310 /// path.isWithin('/root/path', '/root/path/a'); // -> true
307 /// path.isWithin('/root/path', '/root/other'); // -> false 311 /// path.isWithin('/root/path', '/root/other'); // -> false
308 /// path.isWithin('/root/path', '/root/path') // -> false 312 /// path.isWithin('/root/path', '/root/path') // -> false
309 bool isWithin(String parent, String child) => _builder.isWithin(parent, child); 313 bool isWithin(String parent, String child) => _context.isWithin(parent, child);
310 314
311 /// Removes a trailing extension from the last part of [path]. 315 /// Removes a trailing extension from the last part of [path].
312 /// 316 ///
313 /// withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' 317 /// withoutExtension('path/to/foo.dart'); // -> 'path/to/foo'
314 String withoutExtension(String path) => _builder.withoutExtension(path); 318 String withoutExtension(String path) => _context.withoutExtension(path);
315 319
316 /// Returns the path represented by [uri]. 320 /// Returns the path represented by [uri].
317 /// 321 ///
318 /// For POSIX and Windows styles, [uri] must be a `file:` URI. For the URL 322 /// For POSIX and Windows styles, [uri] must be a `file:` URI. For the URL
319 /// style, this will just convert [uri] to a string. 323 /// style, this will just convert [uri] to a string.
320 /// 324 ///
321 /// // POSIX 325 /// // POSIX
322 /// path.fromUri(Uri.parse('file:///path/to/foo')) 326 /// path.fromUri(Uri.parse('file:///path/to/foo'))
323 /// // -> '/path/to/foo' 327 /// // -> '/path/to/foo'
324 /// 328 ///
325 /// // Windows 329 /// // Windows
326 /// path.fromUri(Uri.parse('file:///C:/path/to/foo')) 330 /// path.fromUri(Uri.parse('file:///C:/path/to/foo'))
327 /// // -> r'C:\path\to\foo' 331 /// // -> r'C:\path\to\foo'
328 /// 332 ///
329 /// // URL 333 /// // URL
330 /// path.fromUri(Uri.parse('http://dartlang.org/path/to/foo')) 334 /// path.fromUri(Uri.parse('http://dartlang.org/path/to/foo'))
331 /// // -> 'http://dartlang.org/path/to/foo' 335 /// // -> 'http://dartlang.org/path/to/foo'
332 String fromUri(Uri uri) => _builder.fromUri(uri); 336 String fromUri(Uri uri) => _context.fromUri(uri);
333 337
334 /// Returns the URI that represents [path]. 338 /// Returns the URI that represents [path].
335 /// 339 ///
336 /// For POSIX and Windows styles, this will return a `file:` URI. For the URL 340 /// For POSIX and Windows styles, this will return a `file:` URI. For the URL
337 /// style, this will just convert [path] to a [Uri]. 341 /// style, this will just convert [path] to a [Uri].
338 /// 342 ///
339 /// // POSIX 343 /// // POSIX
340 /// path.toUri('/path/to/foo') 344 /// path.toUri('/path/to/foo')
341 /// // -> Uri.parse('file:///path/to/foo') 345 /// // -> Uri.parse('file:///path/to/foo')
342 /// 346 ///
343 /// // Windows 347 /// // Windows
344 /// path.toUri(r'C:\path\to\foo') 348 /// path.toUri(r'C:\path\to\foo')
345 /// // -> Uri.parse('file:///C:/path/to/foo') 349 /// // -> Uri.parse('file:///C:/path/to/foo')
346 /// 350 ///
347 /// // URL 351 /// // URL
348 /// path.toUri('http://dartlang.org/path/to/foo') 352 /// path.toUri('http://dartlang.org/path/to/foo')
349 /// // -> Uri.parse('http://dartlang.org/path/to/foo') 353 /// // -> Uri.parse('http://dartlang.org/path/to/foo')
350 /// 354 ///
351 /// If [path] is relative, a relative URI will be returned. 355 /// If [path] is relative, a relative URI will be returned.
352 /// 356 ///
353 /// path.toUri('path/to/foo') 357 /// path.toUri('path/to/foo')
354 /// // -> Uri.parse('path/to/foo') 358 /// // -> Uri.parse('path/to/foo')
355 Uri toUri(String path) => _builder.toUri(path); 359 Uri toUri(String path) => _context.toUri(path);
356
357 /// Validates that there are no non-null arguments following a null one and
358 /// throws an appropriate [ArgumentError] on failure.
359 _validateArgList(String method, List<String> args) {
360 for (var i = 1; i < args.length; i++) {
361 // Ignore nulls hanging off the end.
362 if (args[i] == null || args[i - 1] != null) continue;
363
364 var numArgs;
365 for (numArgs = args.length; numArgs >= 1; numArgs--) {
366 if (args[numArgs - 1] != null) break;
367 }
368
369 // Show the arguments.
370 var message = new StringBuffer();
371 message.write("$method(");
372 message.write(args.take(numArgs)
373 .map((arg) => arg == null ? "null" : '"$arg"')
374 .join(", "));
375 message.write("): part ${i - 1} was null, but part $i was not.");
376 throw new ArgumentError(message.toString());
377 }
378 }
379
380 /// An instantiable class for manipulating paths. Unlike the top-level
381 /// functions, this lets you explicitly select what platform the paths will use.
382 class Builder {
383 /// Creates a new path builder for the given style and root directory.
384 ///
385 /// If [style] is omitted, it uses the host operating system's path style. If
386 /// only [root] is omitted, it defaults ".". If *both* [style] and [root] are
387 /// omitted, [root] defaults to the current working directory.
388 ///
389 /// On the browser, the path style is [Style.url]. In Dartium, [root] defaults
390 /// to the current URL. When using dart2js, it currently defaults to `.` due
391 /// to technical constraints.
392 factory Builder({Style style, String root}) {
393 if (root == null) {
394 if (style == null) {
395 root = current;
396 } else {
397 root = ".";
398 }
399 }
400
401 if (style == null) style = Style.platform;
402
403 return new Builder._(style, root);
404 }
405
406 Builder._(this.style, this.root);
407
408 /// The style of path that this builder works with.
409 final Style style;
410
411 /// The root directory that relative paths will be relative to.
412 final String root;
413
414 /// Gets the path separator for the builder's [style]. On Mac and Linux,
415 /// this is `/`. On Windows, it's `\`.
416 String get separator => style.separator;
417
418 /// Gets the part of [path] after the last separator on the builder's
419 /// platform.
420 ///
421 /// builder.basename('path/to/foo.dart'); // -> 'foo.dart'
422 /// builder.basename('path/to'); // -> 'to'
423 ///
424 /// Trailing separators are ignored.
425 ///
426 /// builder.basename('path/to/'); // -> 'to'
427 String basename(String path) => _parse(path).basename;
428
429 /// Gets the part of [path] after the last separator on the builder's
430 /// platform, and without any trailing file extension.
431 ///
432 /// builder.basenameWithoutExtension('path/to/foo.dart'); // -> 'foo'
433 ///
434 /// Trailing separators are ignored.
435 ///
436 /// builder.basenameWithoutExtension('path/to/foo.dart/'); // -> 'foo'
437 String basenameWithoutExtension(String path) =>
438 _parse(path).basenameWithoutExtension;
439
440 /// Gets the part of [path] before the last separator.
441 ///
442 /// builder.dirname('path/to/foo.dart'); // -> 'path/to'
443 /// builder.dirname('path/to'); // -> 'path'
444 ///
445 /// Trailing separators are ignored.
446 ///
447 /// builder.dirname('path/to/'); // -> 'path'
448 String dirname(String path) {
449 var parsed = _parse(path);
450 parsed.removeTrailingSeparators();
451 if (parsed.parts.isEmpty) return parsed.root == null ? '.' : parsed.root;
452 if (parsed.parts.length == 1) {
453 return parsed.root == null ? '.' : parsed.root;
454 }
455 parsed.parts.removeLast();
456 parsed.separators.removeLast();
457 parsed.removeTrailingSeparators();
458 return parsed.toString();
459 }
460
461 /// Gets the file extension of [path]: the portion of [basename] from the last
462 /// `.` to the end (including the `.` itself).
463 ///
464 /// builder.extension('path/to/foo.dart'); // -> '.dart'
465 /// builder.extension('path/to/foo'); // -> ''
466 /// builder.extension('path.to/foo'); // -> ''
467 /// builder.extension('path/to/foo.dart.js'); // -> '.js'
468 ///
469 /// If the file name starts with a `.`, then it is not considered an
470 /// extension:
471 ///
472 /// builder.extension('~/.bashrc'); // -> ''
473 /// builder.extension('~/.notes.txt'); // -> '.txt'
474 String extension(String path) => _parse(path).extension;
475
476 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed.
477 /// Returns the root of [path], if it's absolute, or an empty string if it's
478 /// relative.
479 ///
480 /// // Unix
481 /// builder.rootPrefix('path/to/foo'); // -> ''
482 /// builder.rootPrefix('/path/to/foo'); // -> '/'
483 ///
484 /// // Windows
485 /// builder.rootPrefix(r'path\to\foo'); // -> ''
486 /// builder.rootPrefix(r'C:\path\to\foo'); // -> r'C:\'
487 ///
488 /// // URL
489 /// builder.rootPrefix('path/to/foo'); // -> ''
490 /// builder.rootPrefix('http://dartlang.org/path/to/foo');
491 /// // -> 'http://dartlang.org'
492 String rootPrefix(String path) {
493 var root = _parse(path).root;
494 return root == null ? '' : root;
495 }
496
497 /// Returns `true` if [path] is an absolute path and `false` if it is a
498 /// relative path.
499 ///
500 /// On POSIX systems, absolute paths start with a `/` (forward slash). On
501 /// Windows, an absolute path starts with `\\`, or a drive letter followed by
502 /// `:/` or `:\`. For URLs, absolute paths either start with a protocol and
503 /// optional hostname (e.g. `http://dartlang.org`, `file://`) or with a `/`.
504 ///
505 /// URLs that start with `/` are known as "root-relative", since they're
506 /// relative to the root of the current URL. Since root-relative paths are
507 /// still absolute in every other sense, [isAbsolute] will return true for
508 /// them. They can be detected using [isRootRelative].
509 bool isAbsolute(String path) => _parse(path).isAbsolute;
510
511 /// Returns `true` if [path] is a relative path and `false` if it is absolute.
512 /// On POSIX systems, absolute paths start with a `/` (forward slash). On
513 /// Windows, an absolute path starts with `\\`, or a drive letter followed by
514 /// `:/` or `:\`.
515 bool isRelative(String path) => !this.isAbsolute(path);
516
517 /// Returns `true` if [path] is a root-relative path and `false` if it's not.
518 ///
519 /// URLs that start with `/` are known as "root-relative", since they're
520 /// relative to the root of the current URL. Since root-relative paths are
521 /// still absolute in every other sense, [isAbsolute] will return true for
522 /// them. They can be detected using [isRootRelative].
523 ///
524 /// No POSIX and Windows paths are root-relative.
525 bool isRootRelative(String path) => _parse(path).isRootRelative;
526
527 /// Joins the given path parts into a single path. Example:
528 ///
529 /// builder.join('path', 'to', 'foo'); // -> 'path/to/foo'
530 ///
531 /// If any part ends in a path separator, then a redundant separator will not
532 /// be added:
533 ///
534 /// builder.join('path/', 'to', 'foo'); // -> 'path/to/foo
535 ///
536 /// If a part is an absolute path, then anything before that will be ignored:
537 ///
538 /// builder.join('path', '/to', 'foo'); // -> '/to/foo'
539 ///
540 String join(String part1, [String part2, String part3, String part4,
541 String part5, String part6, String part7, String part8]) {
542 var parts = [part1, part2, part3, part4, part5, part6, part7, part8];
543 _validateArgList("join", parts);
544 return joinAll(parts.where((part) => part != null));
545 }
546
547 /// Joins the given path parts into a single path. Example:
548 ///
549 /// builder.joinAll(['path', 'to', 'foo']); // -> 'path/to/foo'
550 ///
551 /// If any part ends in a path separator, then a redundant separator will not
552 /// be added:
553 ///
554 /// builder.joinAll(['path/', 'to', 'foo']); // -> 'path/to/foo
555 ///
556 /// If a part is an absolute path, then anything before that will be ignored:
557 ///
558 /// builder.joinAll(['path', '/to', 'foo']); // -> '/to/foo'
559 ///
560 /// For a fixed number of parts, [join] is usually terser.
561 String joinAll(Iterable<String> parts) {
562 var buffer = new StringBuffer();
563 var needsSeparator = false;
564 var isAbsoluteAndNotRootRelative = false;
565
566 for (var part in parts.where((part) => part != '')) {
567 if (this.isRootRelative(part) && isAbsoluteAndNotRootRelative) {
568 // If the new part is root-relative, it preserves the previous root but
569 // replaces the path after it.
570 var parsed = _parse(part);
571 parsed.root = this.rootPrefix(buffer.toString());
572 if (parsed.root.contains(style.needsSeparatorPattern)) {
573 parsed.separators[0] = style.separator;
574 }
575 buffer.clear();
576 buffer.write(parsed);
577 } else if (this.isAbsolute(part)) {
578 isAbsoluteAndNotRootRelative = !this.isRootRelative(part);
579 // An absolute path discards everything before it.
580 buffer.clear();
581 buffer.write(part);
582 } else {
583 if (part.length > 0 && part[0].contains(style.separatorPattern)) {
584 // The part starts with a separator, so we don't need to add one.
585 } else if (needsSeparator) {
586 buffer.write(separator);
587 }
588
589 buffer.write(part);
590 }
591
592 // Unless this part ends with a separator, we'll need to add one before
593 // the next part.
594 needsSeparator = part.contains(style.needsSeparatorPattern);
595 }
596
597 return buffer.toString();
598 }
599
600 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed.
601 /// Splits [path] into its components using the current platform's
602 /// [separator]. Example:
603 ///
604 /// builder.split('path/to/foo'); // -> ['path', 'to', 'foo']
605 ///
606 /// The path will *not* be normalized before splitting.
607 ///
608 /// builder.split('path/../foo'); // -> ['path', '..', 'foo']
609 ///
610 /// If [path] is absolute, the root directory will be the first element in the
611 /// array. Example:
612 ///
613 /// // Unix
614 /// builder.split('/path/to/foo'); // -> ['/', 'path', 'to', 'foo']
615 ///
616 /// // Windows
617 /// builder.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo']
618 List<String> split(String path) {
619 var parsed = _parse(path);
620 // Filter out empty parts that exist due to multiple separators in a row.
621 parsed.parts = parsed.parts.where((part) => !part.isEmpty)
622 .toList();
623 if (parsed.root != null) parsed.parts.insert(0, parsed.root);
624 return parsed.parts;
625 }
626
627 /// Normalizes [path], simplifying it by handling `..`, and `.`, and
628 /// removing redundant path separators whenever possible.
629 ///
630 /// builder.normalize('path/./to/..//file.text'); // -> 'path/file.txt'
631 String normalize(String path) {
632 var parsed = _parse(path);
633 parsed.normalize();
634 return parsed.toString();
635 }
636
637 /// Creates a new path by appending the given path parts to the [root].
638 /// Equivalent to [join()] with [root] as the first argument. Example:
639 ///
640 /// var builder = new Builder(root: 'root');
641 /// builder.resolve('path', 'to', 'foo'); // -> 'root/path/to/foo'
642 String resolve(String part1, [String part2, String part3, String part4,
643 String part5, String part6, String part7]) {
644 return join(root, part1, part2, part3, part4, part5, part6, part7);
645 }
646
647 /// Attempts to convert [path] to an equivalent relative path relative to
648 /// [root].
649 ///
650 /// var builder = new Builder(root: '/root/path');
651 /// builder.relative('/root/path/a/b.dart'); // -> 'a/b.dart'
652 /// builder.relative('/root/other.dart'); // -> '../other.dart'
653 ///
654 /// If the [from] argument is passed, [path] is made relative to that instead.
655 ///
656 /// builder.relative('/root/path/a/b.dart',
657 /// from: '/root/path'); // -> 'a/b.dart'
658 /// builder.relative('/root/other.dart',
659 /// from: '/root/path'); // -> '../other.dart'
660 ///
661 /// If [path] and/or [from] are relative paths, they are assumed to be
662 /// relative to [root].
663 ///
664 /// Since there is no relative path from one drive letter to another on
665 /// Windows, this will return an absolute path in that case.
666 ///
667 /// builder.relative(r'D:\other', from: r'C:\other'); // -> 'D:\other'
668 ///
669 /// This will also return an absolute path if an absolute [path] is passed to
670 /// a builder with a relative [root].
671 ///
672 /// var builder = new Builder(r'some/relative/path');
673 /// builder.relative(r'/absolute/path'); // -> '/absolute/path'
674 ///
675 /// If [root] is relative, it may be impossible to determine a path from
676 /// [from] to [path]. For example, if [root] and [path] are "." and [from] is
677 /// "/", no path can be determined. In this case, a [PathException] will be
678 /// thrown.
679 String relative(String path, {String from}) {
680 from = from == null ? root : this.join(root, from);
681
682 // We can't determine the path from a relative path to an absolute path.
683 if (this.isRelative(from) && this.isAbsolute(path)) {
684 return this.normalize(path);
685 }
686
687 // If the given path is relative, resolve it relative to the root of the
688 // builder.
689 if (this.isRelative(path) || this.isRootRelative(path)) {
690 path = this.resolve(path);
691 }
692
693 // If the path is still relative and `from` is absolute, we're unable to
694 // find a path from `from` to `path`.
695 if (this.isRelative(path) && this.isAbsolute(from)) {
696 throw new PathException('Unable to find a path to "$path" from "$from".');
697 }
698
699 var fromParsed = _parse(from)..normalize();
700 var pathParsed = _parse(path)..normalize();
701
702 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '.') {
703 return pathParsed.toString();
704 }
705
706 // If the root prefixes don't match (for example, different drive letters
707 // on Windows), then there is no relative path, so just return the absolute
708 // one. In Windows, drive letters are case-insenstive and we allow
709 // calculation of relative paths, even if a path has not been normalized.
710 if (fromParsed.root != pathParsed.root &&
711 ((fromParsed.root == null || pathParsed.root == null) ||
712 fromParsed.root.toLowerCase().replaceAll('/', '\\') !=
713 pathParsed.root.toLowerCase().replaceAll('/', '\\'))) {
714 return pathParsed.toString();
715 }
716
717 // Strip off their common prefix.
718 while (fromParsed.parts.length > 0 && pathParsed.parts.length > 0 &&
719 fromParsed.parts[0] == pathParsed.parts[0]) {
720 fromParsed.parts.removeAt(0);
721 fromParsed.separators.removeAt(1);
722 pathParsed.parts.removeAt(0);
723 pathParsed.separators.removeAt(1);
724 }
725
726 // If there are any directories left in the from path, we need to walk up
727 // out of them. If a directory left in the from path is '..', it cannot
728 // be cancelled by adding a '..'.
729 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '..') {
730 throw new PathException('Unable to find a path to "$path" from "$from".');
731 }
732 _growListFront(pathParsed.parts, fromParsed.parts.length, '..');
733 pathParsed.separators[0] = '';
734 pathParsed.separators.insertAll(1,
735 new List.filled(fromParsed.parts.length, style.separator));
736
737 // Corner case: the paths completely collapsed.
738 if (pathParsed.parts.length == 0) return '.';
739
740 // Corner case: path was '.' and some '..' directories were added in front.
741 // Don't add a final '/.' in that case.
742 if (pathParsed.parts.length > 1 && pathParsed.parts.last == '.') {
743 pathParsed.parts.removeLast();
744 pathParsed.separators..removeLast()..removeLast()..add('');
745 }
746
747 // Make it relative.
748 pathParsed.root = '';
749 pathParsed.removeTrailingSeparators();
750
751 return pathParsed.toString();
752 }
753
754 /// Returns `true` if [child] is a path beneath `parent`, and `false`
755 /// otherwise.
756 ///
757 /// path.isWithin('/root/path', '/root/path/a'); // -> true
758 /// path.isWithin('/root/path', '/root/other'); // -> false
759 /// path.isWithin('/root/path', '/root/path') // -> false
760 bool isWithin(String parent, String child) {
761 var relative;
762 try {
763 relative = this.relative(child, from: parent);
764 } on PathException catch (_) {
765 // If no relative path from [parent] to [child] is found, [child]
766 // definitely isn't a child of [parent].
767 return false;
768 }
769
770 var parts = this.split(relative);
771 return this.isRelative(relative) && parts.first != '..' &&
772 parts.first != '.';
773 }
774
775 /// Removes a trailing extension from the last part of [path].
776 ///
777 /// builder.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo'
778 String withoutExtension(String path) {
779 var parsed = _parse(path);
780
781 for (var i = parsed.parts.length - 1; i >= 0; i--) {
782 if (!parsed.parts[i].isEmpty) {
783 parsed.parts[i] = parsed.basenameWithoutExtension;
784 break;
785 }
786 }
787
788 return parsed.toString();
789 }
790
791 /// Returns the path represented by [uri].
792 ///
793 /// For POSIX and Windows styles, [uri] must be a `file:` URI. For the URL
794 /// style, this will just convert [uri] to a string.
795 ///
796 /// // POSIX
797 /// builder.fromUri(Uri.parse('file:///path/to/foo'))
798 /// // -> '/path/to/foo'
799 ///
800 /// // Windows
801 /// builder.fromUri(Uri.parse('file:///C:/path/to/foo'))
802 /// // -> r'C:\path\to\foo'
803 ///
804 /// // URL
805 /// builder.fromUri(Uri.parse('http://dartlang.org/path/to/foo'))
806 /// // -> 'http://dartlang.org/path/to/foo'
807 String fromUri(Uri uri) => style.pathFromUri(uri);
808
809 /// Returns the URI that represents [path].
810 ///
811 /// For POSIX and Windows styles, this will return a `file:` URI. For the URL
812 /// style, this will just convert [path] to a [Uri].
813 ///
814 /// // POSIX
815 /// builder.toUri('/path/to/foo')
816 /// // -> Uri.parse('file:///path/to/foo')
817 ///
818 /// // Windows
819 /// builder.toUri(r'C:\path\to\foo')
820 /// // -> Uri.parse('file:///C:/path/to/foo')
821 ///
822 /// // URL
823 /// builder.toUri('http://dartlang.org/path/to/foo')
824 /// // -> Uri.parse('http://dartlang.org/path/to/foo')
825 Uri toUri(String path) {
826 if (isRelative(path)) {
827 return style.relativePathToUri(path);
828 } else {
829 return style.absolutePathToUri(join(root, path));
830 }
831 }
832
833 _ParsedPath _parse(String path) {
834 var before = path;
835
836 // Remove the root prefix, if any.
837 var root = style.getRoot(path);
838 var isRootRelative = style.getRelativeRoot(path) != null;
839 if (root != null) path = path.substring(root.length);
840
841 // Split the parts on path separators.
842 var parts = [];
843 var separators = [];
844
845 var firstSeparator = style.separatorPattern.matchAsPrefix(path);
846 if (firstSeparator != null) {
847 separators.add(firstSeparator[0]);
848 path = path.substring(firstSeparator[0].length);
849 } else {
850 separators.add('');
851 }
852
853 var start = 0;
854 for (var match in style.separatorPattern.allMatches(path)) {
855 parts.add(path.substring(start, match.start));
856 separators.add(match[0]);
857 start = match.end;
858 }
859
860 // Add the final part, if any.
861 if (start < path.length) {
862 parts.add(path.substring(start));
863 separators.add('');
864 }
865
866 return new _ParsedPath(style, root, isRootRelative, parts, separators);
867 }
868 }
869
870 /// An enum type describing a "flavor" of path.
871 abstract class Style {
872 /// POSIX-style paths use "/" (forward slash) as separators. Absolute paths
873 /// start with "/". Used by UNIX, Linux, Mac OS X, and others.
874 static final posix = new _PosixStyle();
875
876 /// Windows paths use "\" (backslash) as separators. Absolute paths start with
877 /// a drive letter followed by a colon (example, "C:") or two backslashes
878 /// ("\\") for UNC paths.
879 // TODO(rnystrom): The UNC root prefix should include the drive name too, not
880 // just the "\\".
881 static final windows = new _WindowsStyle();
882
883 /// URLs aren't filesystem paths, but they're supported to make it easier to
884 /// manipulate URL paths in the browser.
885 ///
886 /// URLs use "/" (forward slash) as separators. Absolute paths either start
887 /// with a protocol and optional hostname (e.g. `http://dartlang.org`,
888 /// `file://`) or with "/".
889 static final url = new _UrlStyle();
890
891 /// The style of the host platform.
892 ///
893 /// When running on the command line, this will be [windows] or [posix] based
894 /// on the host operating system. On a browser, this will be [url].
895 static final platform = _getPlatformStyle();
896
897 /// Gets the type of the host platform.
898 static Style _getPlatformStyle() {
899 // If we're running a Dart file in the browser from a `file:` URI,
900 // [Uri.base] will point to a file. If we're running on the standalone,
901 // it will point to a directory. We can use that fact to determine which
902 // style to use.
903 if (Uri.base.scheme != 'file') return Style.url;
904 if (!Uri.base.path.endsWith('/')) return Style.url;
905 if (new Uri(path: 'a/b').toFilePath() == 'a\\b') return Style.windows;
906 return Style.posix;
907 }
908
909 /// The name of this path style. Will be "posix" or "windows".
910 String get name;
911
912 /// The path separator for this style. On POSIX, this is `/`. On Windows,
913 /// it's `\`.
914 String get separator;
915
916 /// The [Pattern] that can be used to match a separator for a path in this
917 /// style. Windows allows both "/" and "\" as path separators even though "\"
918 /// is the canonical one.
919 Pattern get separatorPattern;
920
921 /// The [Pattern] that matches path components that need a separator after
922 /// them.
923 ///
924 /// Windows and POSIX styles just need separators when the previous component
925 /// doesn't already end in a separator, but the URL always needs to place a
926 /// separator between the root and the first component, even if the root
927 /// already ends in a separator character. For example, to join "file://" and
928 /// "usr", an additional "/" is needed (making "file:///usr").
929 Pattern get needsSeparatorPattern;
930
931 /// The [Pattern] that can be used to match the root prefix of an absolute
932 /// path in this style.
933 Pattern get rootPattern;
934
935 /// The [Pattern] that can be used to match the root prefix of a root-relative
936 /// path in this style.
937 ///
938 /// This can be null to indicate that this style doesn't support root-relative
939 /// paths.
940 final Pattern relativeRootPattern = null;
941
942 /// A [Builder] that uses this style.
943 Builder get builder => new Builder(style: this);
944
945 /// Gets the root prefix of [path] if path is absolute. If [path] is relative,
946 /// returns `null`.
947 String getRoot(String path) {
948 // TODO(rnystrom): Use firstMatch() when #7080 is fixed.
949 var matches = rootPattern.allMatches(path);
950 if (matches.isNotEmpty) return matches.first[0];
951 return getRelativeRoot(path);
952 }
953
954 /// Gets the root prefix of [path] if it's root-relative.
955 ///
956 /// If [path] is relative or absolute and not root-relative, returns `null`.
957 String getRelativeRoot(String path) {
958 if (relativeRootPattern == null) return null;
959 // TODO(rnystrom): Use firstMatch() when #7080 is fixed.
960 var matches = relativeRootPattern.allMatches(path);
961 if (matches.isEmpty) return null;
962 return matches.first[0];
963 }
964
965 /// Returns the path represented by [uri] in this style.
966 String pathFromUri(Uri uri);
967
968 /// Returns the URI that represents the relative path made of [parts].
969 Uri relativePathToUri(String path) =>
970 new Uri(pathSegments: builder.split(path));
971
972 /// Returns the URI that represents [path], which is assumed to be absolute.
973 Uri absolutePathToUri(String path);
974
975 String toString() => name;
976 }
977
978 /// The style for POSIX paths.
979 class _PosixStyle extends Style {
980 _PosixStyle();
981
982 final name = 'posix';
983 final separator = '/';
984 final separatorPattern = new RegExp(r'/');
985 final needsSeparatorPattern = new RegExp(r'[^/]$');
986 final rootPattern = new RegExp(r'^/');
987
988 String pathFromUri(Uri uri) {
989 if (uri.scheme == '' || uri.scheme == 'file') {
990 return Uri.decodeComponent(uri.path);
991 }
992 throw new ArgumentError("Uri $uri must have scheme 'file:'.");
993 }
994
995 Uri absolutePathToUri(String path) {
996 var parsed = builder._parse(path);
997 if (parsed.parts.isEmpty) {
998 // If the path is a bare root (e.g. "/"), [components] will
999 // currently be empty. We add two empty components so the URL constructor
1000 // produces "file:///", with a trailing slash.
1001 parsed.parts.addAll(["", ""]);
1002 } else if (parsed.hasTrailingSeparator) {
1003 // If the path has a trailing slash, add a single empty component so the
1004 // URI has a trailing slash as well.
1005 parsed.parts.add("");
1006 }
1007
1008 return new Uri(scheme: 'file', pathSegments: parsed.parts);
1009 }
1010 }
1011
1012 /// The style for Windows paths.
1013 class _WindowsStyle extends Style {
1014 _WindowsStyle();
1015
1016 final name = 'windows';
1017 final separator = '\\';
1018 final separatorPattern = new RegExp(r'[/\\]');
1019 final needsSeparatorPattern = new RegExp(r'[^/\\]$');
1020 final rootPattern = new RegExp(r'^(\\\\[^\\]+\\[^\\/]+|[a-zA-Z]:[/\\])');
1021
1022 // Matches a back or forward slash that's not followed by another back or
1023 // forward slash.
1024 final relativeRootPattern = new RegExp(r"^[/\\](?![/\\])");
1025
1026 String pathFromUri(Uri uri) {
1027 if (uri.scheme != '' && uri.scheme != 'file') {
1028 throw new ArgumentError("Uri $uri must have scheme 'file:'.");
1029 }
1030
1031 var path = uri.path;
1032 if (uri.host == '') {
1033 // Drive-letter paths look like "file:///C:/path/to/file". The
1034 // replaceFirst removes the extra initial slash.
1035 if (path.startsWith('/')) path = path.replaceFirst("/", "");
1036 } else {
1037 // Network paths look like "file://hostname/path/to/file".
1038 path = '\\\\${uri.host}$path';
1039 }
1040 return Uri.decodeComponent(path.replaceAll("/", "\\"));
1041 }
1042
1043 Uri absolutePathToUri(String path) {
1044 var parsed = builder._parse(path);
1045 if (parsed.root.startsWith(r'\\')) {
1046 // Network paths become "file://server/share/path/to/file".
1047
1048 // The root is of the form "\\server\share". We want "server" to be the
1049 // URI host, and "share" to be the first element of the path.
1050 var rootParts = parsed.root.split('\\').where((part) => part != '');
1051 parsed.parts.insert(0, rootParts.last);
1052
1053 if (parsed.hasTrailingSeparator) {
1054 // If the path has a trailing slash, add a single empty component so the
1055 // URI has a trailing slash as well.
1056 parsed.parts.add("");
1057 }
1058
1059 return new Uri(scheme: 'file', host: rootParts.first,
1060 pathSegments: parsed.parts);
1061 } else {
1062 // Drive-letter paths become "file:///C:/path/to/file".
1063
1064 // If the path is a bare root (e.g. "C:\"), [parsed.parts] will currently
1065 // be empty. We add an empty component so the URL constructor produces
1066 // "file:///C:/", with a trailing slash. We also add an empty component if
1067 // the URL otherwise has a trailing slash.
1068 if (parsed.parts.length == 0 || parsed.hasTrailingSeparator) {
1069 parsed.parts.add("");
1070 }
1071
1072 // Get rid of the trailing "\" in "C:\" because the URI constructor will
1073 // add a separator on its own.
1074 parsed.parts.insert(0, parsed.root.replaceAll(separatorPattern, ""));
1075
1076 return new Uri(scheme: 'file', pathSegments: parsed.parts);
1077 }
1078 }
1079 }
1080
1081 /// The style for URL paths.
1082 class _UrlStyle extends Style {
1083 _UrlStyle();
1084
1085 final name = 'url';
1086 final separator = '/';
1087 final separatorPattern = new RegExp(r'/');
1088 final needsSeparatorPattern = new RegExp(
1089 r"(^[a-zA-Z][-+.a-zA-Z\d]*://|[^/])$");
1090 final rootPattern = new RegExp(r"[a-zA-Z][-+.a-zA-Z\d]*://[^/]*");
1091 final relativeRootPattern = new RegExp(r"^/");
1092
1093 String pathFromUri(Uri uri) => uri.toString();
1094
1095 Uri relativePathToUri(String path) => Uri.parse(path);
1096 Uri absolutePathToUri(String path) => Uri.parse(path);
1097 }
1098
1099 // TODO(rnystrom): Make this public?
1100 class _ParsedPath {
1101 /// The [Style] that was used to parse this path.
1102 Style style;
1103
1104 /// The absolute root portion of the path, or `null` if the path is relative.
1105 /// On POSIX systems, this will be `null` or "/". On Windows, it can be
1106 /// `null`, "//" for a UNC path, or something like "C:\" for paths with drive
1107 /// letters.
1108 String root;
1109
1110 /// Whether this path is root-relative.
1111 ///
1112 /// See [Builder.isRootRelative].
1113 bool isRootRelative;
1114
1115 /// The path-separated parts of the path. All but the last will be
1116 /// directories.
1117 List<String> parts;
1118
1119 /// The path separators preceding each part.
1120 ///
1121 /// The first one will be an empty string unless the root requires a separator
1122 /// between it and the path. The last one will be an empty string unless the
1123 /// path ends with a trailing separator.
1124 List<String> separators;
1125
1126 /// The file extension of the last non-empty part, or "" if it doesn't have
1127 /// one.
1128 String get extension => _splitExtension()[1];
1129
1130 /// `true` if this is an absolute path.
1131 bool get isAbsolute => root != null;
1132
1133 _ParsedPath(this.style, this.root, this.isRootRelative, this.parts,
1134 this.separators);
1135
1136 String get basename {
1137 var copy = this.clone();
1138 copy.removeTrailingSeparators();
1139 if (copy.parts.isEmpty) return root == null ? '' : root;
1140 return copy.parts.last;
1141 }
1142
1143 String get basenameWithoutExtension => _splitExtension()[0];
1144
1145 bool get hasTrailingSeparator =>
1146 !parts.isEmpty && (parts.last == '' || separators.last != '');
1147
1148 void removeTrailingSeparators() {
1149 while (!parts.isEmpty && parts.last == '') {
1150 parts.removeLast();
1151 separators.removeLast();
1152 }
1153 if (separators.length > 0) separators[separators.length - 1] = '';
1154 }
1155
1156 void normalize() {
1157 // Handle '.', '..', and empty parts.
1158 var leadingDoubles = 0;
1159 var newParts = [];
1160 for (var part in parts) {
1161 if (part == '.' || part == '') {
1162 // Do nothing. Ignore it.
1163 } else if (part == '..') {
1164 // Pop the last part off.
1165 if (newParts.length > 0) {
1166 newParts.removeLast();
1167 } else {
1168 // Backed out past the beginning, so preserve the "..".
1169 leadingDoubles++;
1170 }
1171 } else {
1172 newParts.add(part);
1173 }
1174 }
1175
1176 // A relative path can back out from the start directory.
1177 if (!isAbsolute) {
1178 _growListFront(newParts, leadingDoubles, '..');
1179 }
1180
1181 // If we collapsed down to nothing, do ".".
1182 if (newParts.length == 0 && !isAbsolute) {
1183 newParts.add('.');
1184 }
1185
1186 // Canonicalize separators.
1187 var newSeparators = new List.generate(
1188 newParts.length, (_) => style.separator, growable: true);
1189 newSeparators.insert(0,
1190 isAbsolute && newParts.length > 0 &&
1191 root.contains(style.needsSeparatorPattern) ?
1192 style.separator : '');
1193
1194 parts = newParts;
1195 separators = newSeparators;
1196
1197 // Normalize the Windows root if needed.
1198 if (root != null && style == Style.windows) {
1199 root = root.replaceAll('/', '\\');
1200 }
1201 removeTrailingSeparators();
1202 }
1203
1204 String toString() {
1205 var builder = new StringBuffer();
1206 if (root != null) builder.write(root);
1207 for (var i = 0; i < parts.length; i++) {
1208 builder.write(separators[i]);
1209 builder.write(parts[i]);
1210 }
1211 builder.write(separators.last);
1212
1213 return builder.toString();
1214 }
1215
1216 /// Splits the last non-empty part of the path into a `[basename, extension`]
1217 /// pair.
1218 ///
1219 /// Returns a two-element list. The first is the name of the file without any
1220 /// extension. The second is the extension or "" if it has none.
1221 List<String> _splitExtension() {
1222 var file = parts.lastWhere((p) => p != '', orElse: () => null);
1223
1224 if (file == null) return ['', ''];
1225 if (file == '..') return ['..', ''];
1226
1227 var lastDot = file.lastIndexOf('.');
1228
1229 // If there is no dot, or it's the first character, like '.bashrc', it
1230 // doesn't count.
1231 if (lastDot <= 0) return [file, ''];
1232
1233 return [file.substring(0, lastDot), file.substring(lastDot)];
1234 }
1235
1236 _ParsedPath clone() => new _ParsedPath(
1237 style, root, isRootRelative,
1238 new List.from(parts), new List.from(separators));
1239 }
1240
1241 /// An exception class that's thrown when a path operation is unable to be
1242 /// computed accurately.
1243 class PathException implements Exception {
1244 String message;
1245
1246 PathException(this.message);
1247
1248 String toString() => "PathException: $message";
1249 }
OLDNEW
« no previous file with comments | « pkg/path/README.md ('k') | pkg/path/lib/src/context.dart » ('j') | pkg/path/lib/src/context.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698