Line data Source code
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
3 : // BSD-style license that can be found in the LICENSE file.
4 :
5 : /// A comprehensive, cross-platform path manipulation library.
6 : ///
7 : /// The path library was designed to be imported with a prefix, though you don't
8 : /// have to if you don't want to:
9 : ///
10 : /// import 'package:path/path.dart' as p;
11 : ///
12 : /// The most common way to use the library is through the top-level functions.
13 : /// These manipulate path strings based on your current working directory and
14 : /// the path style (POSIX, Windows, or URLs) of the host platform. For example:
15 : ///
16 : /// p.join('directory', 'file.txt');
17 : ///
18 : /// This calls the top-level [join] function to join "directory" and "file.txt"
19 : /// using the current platform's directory separator.
20 : ///
21 : /// If you want to work with paths for a specific platform regardless of the
22 : /// underlying platform that the program is running on, you can create a
23 : /// [Context] and give it an explicit [Style]:
24 : ///
25 : /// var context = p.Context(style: Style.windows);
26 : /// context.join('directory', 'file.txt');
27 : ///
28 : /// This will join "directory" and "file.txt" using the Windows path separator,
29 : /// even when the program is run on a POSIX machine.
30 : import 'src/context.dart';
31 : import 'src/style.dart';
32 :
33 : export 'src/context.dart' hide createInternal;
34 : export 'src/path_exception.dart';
35 : export 'src/path_map.dart';
36 : export 'src/path_set.dart';
37 : export 'src/style.dart';
38 :
39 : /// A default context for manipulating POSIX paths.
40 0 : final Context posix = Context(style: Style.posix);
41 :
42 : /// A default context for manipulating Windows paths.
43 0 : final Context windows = Context(style: Style.windows);
44 :
45 : /// A default context for manipulating URLs.
46 : ///
47 : /// URL path equality is undefined for paths that differ only in their
48 : /// percent-encoding or only in the case of their host segment.
49 0 : final Context url = Context(style: Style.url);
50 :
51 : /// The system path context.
52 : ///
53 : /// This differs from a context created with [new Context] in that its
54 : /// [Context.current] is always the current working directory, rather than being
55 : /// set once when the context is created.
56 33 : final Context context = createInternal();
57 :
58 : /// Returns the [Style] of the current context.
59 : ///
60 : /// This is the style that all top-level path functions will use.
61 0 : Style get style => context.style;
62 :
63 : /// Gets the path to the current working directory.
64 : ///
65 : /// In the browser, this means the current URL, without the last file segment.
66 11 : String get current {
67 : // If the current working directory gets deleted out from under the program,
68 : // accessing it will throw an IO exception. In order to avoid transient
69 : // errors, if we already have a cached working directory, catch the error and
70 : // use that.
71 : Uri uri;
72 : try {
73 11 : uri = Uri.base;
74 0 : } on Exception {
75 : if (_current != null) return _current!;
76 : rethrow;
77 : }
78 :
79 : // Converting the base URI to a file path is pretty slow, and the base URI
80 : // rarely changes in practice, so we cache the result here.
81 11 : if (uri == _currentUriBase) return _current!;
82 : _currentUriBase = uri;
83 :
84 33 : if (Style.platform == Style.url) {
85 0 : _current = uri.resolve('.').toString();
86 : } else {
87 11 : final path = uri.toFilePath();
88 : // Remove trailing '/' or '\' unless it is the only thing left
89 : // (for instance the root on Linux).
90 22 : final lastIndex = path.length - 1;
91 22 : assert(path[lastIndex] == '/' || path[lastIndex] == '\\');
92 22 : _current = lastIndex == 0 ? path : path.substring(0, lastIndex);
93 : }
94 : return _current!;
95 : }
96 :
97 : /// The last value returned by [Uri.base].
98 : ///
99 : /// This is used to cache the current working directory.
100 : Uri? _currentUriBase;
101 :
102 : /// The last known value of the current working directory.
103 : ///
104 : /// This is cached because [current] is called frequently but rarely actually
105 : /// changes.
106 : String? _current;
107 :
108 : /// Gets the path separator for the current platform. This is `\` on Windows
109 : /// and `/` on other platforms (including the browser).
110 0 : String get separator => context.separator;
111 :
112 : /// Returns a new path with the given path parts appended to [current].
113 : ///
114 : /// Equivalent to [join()] with [current] as the first argument. Example:
115 : ///
116 : /// p.absolute('path', 'to/foo'); // -> '/your/current/dir/path/to/foo'
117 : ///
118 : /// Does not [normalize] or [canonicalize] paths.
119 0 : String absolute(String part1,
120 : [String? part2,
121 : String? part3,
122 : String? part4,
123 : String? part5,
124 : String? part6,
125 : String? part7]) =>
126 0 : context.absolute(part1, part2, part3, part4, part5, part6, part7);
127 :
128 : /// Gets the part of [path] after the last separator.
129 : ///
130 : /// p.basename('path/to/foo.dart'); // -> 'foo.dart'
131 : /// p.basename('path/to'); // -> 'to'
132 : ///
133 : /// Trailing separators are ignored.
134 : ///
135 : /// p.basename('path/to/'); // -> 'to'
136 0 : String basename(String path) => context.basename(path);
137 :
138 : /// Gets the part of [path] after the last separator, and without any trailing
139 : /// file extension.
140 : ///
141 : /// p.basenameWithoutExtension('path/to/foo.dart'); // -> 'foo'
142 : ///
143 : /// Trailing separators are ignored.
144 : ///
145 : /// p.basenameWithoutExtension('path/to/foo.dart/'); // -> 'foo'
146 0 : String basenameWithoutExtension(String path) =>
147 0 : context.basenameWithoutExtension(path);
148 :
149 : /// Gets the part of [path] before the last separator.
150 : ///
151 : /// p.dirname('path/to/foo.dart'); // -> 'path/to'
152 : /// p.dirname('path/to'); // -> 'path'
153 : ///
154 : /// Trailing separators are ignored.
155 : ///
156 : /// p.dirname('path/to/'); // -> 'path'
157 : ///
158 : /// If an absolute path contains no directories, only a root, then the root
159 : /// is returned.
160 : ///
161 : /// p.dirname('/'); // -> '/' (posix)
162 : /// p.dirname('c:\'); // -> 'c:\' (windows)
163 : ///
164 : /// If a relative path has no directories, then '.' is returned.
165 : ///
166 : /// p.dirname('foo'); // -> '.'
167 : /// p.dirname(''); // -> '.'
168 0 : String dirname(String path) => context.dirname(path);
169 :
170 : /// Gets the file extension of [path]: the portion of [basename] from the last
171 : /// `.` to the end (including the `.` itself).
172 : ///
173 : /// p.extension('path/to/foo.dart'); // -> '.dart'
174 : /// p.extension('path/to/foo'); // -> ''
175 : /// p.extension('path.to/foo'); // -> ''
176 : /// p.extension('path/to/foo.dart.js'); // -> '.js'
177 : ///
178 : /// If the file name starts with a `.`, then that is not considered the
179 : /// extension:
180 : ///
181 : /// p.extension('~/.bashrc'); // -> ''
182 : /// p.extension('~/.notes.txt'); // -> '.txt'
183 : ///
184 : /// Takes an optional parameter `level` which makes possible to return
185 : /// multiple extensions having `level` number of dots. If `level` exceeds the
186 : /// number of dots, the full extension is returned. The value of `level` must
187 : /// be greater than 0, else `RangeError` is thrown.
188 : ///
189 : /// p.extension('foo.bar.dart.js', 2); // -> '.dart.js
190 : /// p.extension('foo.bar.dart.js', 3); // -> '.bar.dart.js'
191 : /// p.extension('foo.bar.dart.js', 10); // -> '.bar.dart.js'
192 : /// p.extension('path/to/foo.bar.dart.js', 2); // -> '.dart.js'
193 0 : String extension(String path, [int level = 1]) =>
194 0 : context.extension(path, level);
195 :
196 : /// Returns the root of [path], if it's absolute, or the empty string if it's
197 : /// relative.
198 : ///
199 : /// // Unix
200 : /// p.rootPrefix('path/to/foo'); // -> ''
201 : /// p.rootPrefix('/path/to/foo'); // -> '/'
202 : ///
203 : /// // Windows
204 : /// p.rootPrefix(r'path\to\foo'); // -> ''
205 : /// p.rootPrefix(r'C:\path\to\foo'); // -> r'C:\'
206 : /// p.rootPrefix(r'\\server\share\a\b'); // -> r'\\server\share'
207 : ///
208 : /// // URL
209 : /// p.rootPrefix('path/to/foo'); // -> ''
210 : /// p.rootPrefix('https://dart.dev/path/to/foo');
211 : /// // -> 'https://dart.dev'
212 0 : String rootPrefix(String path) => context.rootPrefix(path);
213 :
214 : /// Returns `true` if [path] is an absolute path and `false` if it is a
215 : /// relative path.
216 : ///
217 : /// On POSIX systems, absolute paths start with a `/` (forward slash). On
218 : /// Windows, an absolute path starts with `\\`, or a drive letter followed by
219 : /// `:/` or `:\`. For URLs, absolute paths either start with a protocol and
220 : /// optional hostname (e.g. `https://dart.dev`, `file://`) or with a `/`.
221 : ///
222 : /// URLs that start with `/` are known as "root-relative", since they're
223 : /// relative to the root of the current URL. Since root-relative paths are still
224 : /// absolute in every other sense, [isAbsolute] will return true for them. They
225 : /// can be detected using [isRootRelative].
226 0 : bool isAbsolute(String path) => context.isAbsolute(path);
227 :
228 : /// Returns `true` if [path] is a relative path and `false` if it is absolute.
229 : /// On POSIX systems, absolute paths start with a `/` (forward slash). On
230 : /// Windows, an absolute path starts with `\\`, or a drive letter followed by
231 : /// `:/` or `:\`.
232 0 : bool isRelative(String path) => context.isRelative(path);
233 :
234 : /// Returns `true` if [path] is a root-relative path and `false` if it's not.
235 : ///
236 : /// URLs that start with `/` are known as "root-relative", since they're
237 : /// relative to the root of the current URL. Since root-relative paths are still
238 : /// absolute in every other sense, [isAbsolute] will return true for them. They
239 : /// can be detected using [isRootRelative].
240 : ///
241 : /// No POSIX and Windows paths are root-relative.
242 0 : bool isRootRelative(String path) => context.isRootRelative(path);
243 :
244 : /// Joins the given path parts into a single path using the current platform's
245 : /// [separator]. Example:
246 : ///
247 : /// p.join('path', 'to', 'foo'); // -> 'path/to/foo'
248 : ///
249 : /// If any part ends in a path separator, then a redundant separator will not
250 : /// be added:
251 : ///
252 : /// p.join('path/', 'to', 'foo'); // -> 'path/to/foo
253 : ///
254 : /// If a part is an absolute path, then anything before that will be ignored:
255 : ///
256 : /// p.join('path', '/to', 'foo'); // -> '/to/foo'
257 0 : String join(String part1,
258 : [String? part2,
259 : String? part3,
260 : String? part4,
261 : String? part5,
262 : String? part6,
263 : String? part7,
264 : String? part8]) =>
265 0 : context.join(part1, part2, part3, part4, part5, part6, part7, part8);
266 :
267 : /// Joins the given path parts into a single path using the current platform's
268 : /// [separator]. Example:
269 : ///
270 : /// p.joinAll(['path', 'to', 'foo']); // -> 'path/to/foo'
271 : ///
272 : /// If any part ends in a path separator, then a redundant separator will not
273 : /// be added:
274 : ///
275 : /// p.joinAll(['path/', 'to', 'foo']); // -> 'path/to/foo
276 : ///
277 : /// If a part is an absolute path, then anything before that will be ignored:
278 : ///
279 : /// p.joinAll(['path', '/to', 'foo']); // -> '/to/foo'
280 : ///
281 : /// For a fixed number of parts, [join] is usually terser.
282 0 : String joinAll(Iterable<String> parts) => context.joinAll(parts);
283 :
284 : /// Splits [path] into its components using the current platform's [separator].
285 : ///
286 : /// p.split('path/to/foo'); // -> ['path', 'to', 'foo']
287 : ///
288 : /// The path will *not* be normalized before splitting.
289 : ///
290 : /// p.split('path/../foo'); // -> ['path', '..', 'foo']
291 : ///
292 : /// If [path] is absolute, the root directory will be the first element in the
293 : /// array. Example:
294 : ///
295 : /// // Unix
296 : /// p.split('/path/to/foo'); // -> ['/', 'path', 'to', 'foo']
297 : ///
298 : /// // Windows
299 : /// p.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo']
300 : /// p.split(r'\\server\share\path\to\foo');
301 : /// // -> [r'\\server\share', 'foo', 'bar', 'baz']
302 : ///
303 : /// // Browser
304 : /// p.split('https://dart.dev/path/to/foo');
305 : /// // -> ['https://dart.dev', 'path', 'to', 'foo']
306 0 : List<String> split(String path) => context.split(path);
307 :
308 : /// Canonicalizes [path].
309 : ///
310 : /// This is guaranteed to return the same path for two different input paths
311 : /// if and only if both input paths point to the same location. Unlike
312 : /// [normalize], it returns absolute paths when possible and canonicalizes
313 : /// ASCII case on Windows.
314 : ///
315 : /// Note that this does not resolve symlinks.
316 : ///
317 : /// If you want a map that uses path keys, it's probably more efficient to use a
318 : /// Map with [equals] and [hash] specified as the callbacks to use for keys than
319 : /// it is to canonicalize every key.
320 0 : String canonicalize(String path) => context.canonicalize(path);
321 :
322 : /// Normalizes [path], simplifying it by handling `..`, and `.`, and
323 : /// removing redundant path separators whenever possible.
324 : ///
325 : /// Note that this is *not* guaranteed to return the same result for two
326 : /// equivalent input paths. For that, see [canonicalize]. Or, if you're using
327 : /// paths as map keys use [equals] and [hash] as the key callbacks.
328 : ///
329 : /// p.normalize('path/./to/..//file.text'); // -> 'path/file.txt'
330 0 : String normalize(String path) => context.normalize(path);
331 :
332 : /// Attempts to convert [path] to an equivalent relative path from the current
333 : /// directory.
334 : ///
335 : /// // Given current directory is /root/path:
336 : /// p.relative('/root/path/a/b.dart'); // -> 'a/b.dart'
337 : /// p.relative('/root/other.dart'); // -> '../other.dart'
338 : ///
339 : /// If the [from] argument is passed, [path] is made relative to that instead.
340 : ///
341 : /// p.relative('/root/path/a/b.dart', from: '/root/path'); // -> 'a/b.dart'
342 : /// p.relative('/root/other.dart', from: '/root/path');
343 : /// // -> '../other.dart'
344 : ///
345 : /// If [path] and/or [from] are relative paths, they are assumed to be relative
346 : /// to the current directory.
347 : ///
348 : /// Since there is no relative path from one drive letter to another on Windows,
349 : /// or from one hostname to another for URLs, this will return an absolute path
350 : /// in those cases.
351 : ///
352 : /// // Windows
353 : /// p.relative(r'D:\other', from: r'C:\home'); // -> 'D:\other'
354 : ///
355 : /// // URL
356 : /// p.relative('https://dart.dev', from: 'https://pub.dev');
357 : /// // -> 'https://dart.dev'
358 0 : String relative(String path, {String? from}) =>
359 0 : context.relative(path, from: from);
360 :
361 : /// Returns `true` if [child] is a path beneath `parent`, and `false` otherwise.
362 : ///
363 : /// p.isWithin('/root/path', '/root/path/a'); // -> true
364 : /// p.isWithin('/root/path', '/root/other'); // -> false
365 : /// p.isWithin('/root/path', '/root/path') // -> false
366 0 : bool isWithin(String parent, String child) => context.isWithin(parent, child);
367 :
368 : /// Returns `true` if [path1] points to the same location as [path2], and
369 : /// `false` otherwise.
370 : ///
371 : /// The [hash] function returns a hash code that matches these equality
372 : /// semantics.
373 0 : bool equals(String path1, String path2) => context.equals(path1, path2);
374 :
375 : /// Returns a hash code for [path] such that, if [equals] returns `true` for two
376 : /// paths, their hash codes are the same.
377 : ///
378 : /// Note that the same path may have different hash codes on different platforms
379 : /// or with different [current] directories.
380 0 : int hash(String path) => context.hash(path);
381 :
382 : /// Removes a trailing extension from the last part of [path].
383 : ///
384 : /// p.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo'
385 0 : String withoutExtension(String path) => context.withoutExtension(path);
386 :
387 : /// Returns [path] with the trailing extension set to [extension].
388 : ///
389 : /// If [path] doesn't have a trailing extension, this just adds [extension] to
390 : /// the end.
391 : ///
392 : /// p.setExtension('path/to/foo.dart', '.js') // -> 'path/to/foo.js'
393 : /// p.setExtension('path/to/foo.dart.js', '.map')
394 : /// // -> 'path/to/foo.dart.map'
395 : /// p.setExtension('path/to/foo', '.js') // -> 'path/to/foo.js'
396 0 : String setExtension(String path, String extension) =>
397 0 : context.setExtension(path, extension);
398 :
399 : /// Returns the path represented by [uri], which may be a [String] or a [Uri].
400 : ///
401 : /// For POSIX and Windows styles, [uri] must be a `file:` URI. For the URL
402 : /// style, this will just convert [uri] to a string.
403 : ///
404 : /// // POSIX
405 : /// p.fromUri('file:///path/to/foo') // -> '/path/to/foo'
406 : ///
407 : /// // Windows
408 : /// p.fromUri('file:///C:/path/to/foo') // -> r'C:\path\to\foo'
409 : ///
410 : /// // URL
411 : /// p.fromUri('https://dart.dev/path/to/foo')
412 : /// // -> 'https://dart.dev/path/to/foo'
413 : ///
414 : /// If [uri] is relative, a relative path will be returned.
415 : ///
416 : /// p.fromUri('path/to/foo'); // -> 'path/to/foo'
417 0 : String fromUri(uri) => context.fromUri(uri);
418 :
419 : /// Returns the URI that represents [path].
420 : ///
421 : /// For POSIX and Windows styles, this will return a `file:` URI. For the URL
422 : /// style, this will just convert [path] to a [Uri].
423 : ///
424 : /// // POSIX
425 : /// p.toUri('/path/to/foo')
426 : /// // -> Uri.parse('file:///path/to/foo')
427 : ///
428 : /// // Windows
429 : /// p.toUri(r'C:\path\to\foo')
430 : /// // -> Uri.parse('file:///C:/path/to/foo')
431 : ///
432 : /// // URL
433 : /// p.toUri('https://dart.dev/path/to/foo')
434 : /// // -> Uri.parse('https://dart.dev/path/to/foo')
435 : ///
436 : /// If [path] is relative, a relative URI will be returned.
437 : ///
438 : /// p.toUri('path/to/foo') // -> Uri.parse('path/to/foo')
439 0 : Uri toUri(String path) => context.toUri(path);
440 :
441 : /// Returns a terse, human-readable representation of [uri].
442 : ///
443 : /// [uri] can be a [String] or a [Uri]. If it can be made relative to the
444 : /// current working directory, that's done. Otherwise, it's returned as-is. This
445 : /// gracefully handles non-`file:` URIs for [Style.posix] and [Style.windows].
446 : ///
447 : /// The returned value is meant for human consumption, and may be either URI-
448 : /// or path-formatted.
449 : ///
450 : /// // POSIX at "/root/path"
451 : /// p.prettyUri('file:///root/path/a/b.dart'); // -> 'a/b.dart'
452 : /// p.prettyUri('https://dart.dev/'); // -> 'https://dart.dev'
453 : ///
454 : /// // Windows at "C:\root\path"
455 : /// p.prettyUri('file:///C:/root/path/a/b.dart'); // -> r'a\b.dart'
456 : /// p.prettyUri('https://dart.dev/'); // -> 'https://dart.dev'
457 : ///
458 : /// // URL at "https://dart.dev/root/path"
459 : /// p.prettyUri('https://dart.dev/root/path/a/b.dart'); // -> r'a/b.dart'
460 : /// p.prettyUri('file:///root/path'); // -> 'file:///root/path'
461 33 : String prettyUri(uri) => context.prettyUri(uri);
|