| // Copyright (c) 2012, 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 comprehensive, cross-platform path manipulation library. |
| /// |
| /// The path library was designed to be imported with a prefix, though you don't |
| /// have to if you don't want to: |
| /// |
| /// import 'package:path/path.dart' as p; |
| /// |
| /// The most common way to use the library is through the top-level functions. |
| /// These manipulate path strings based on your current working directory and |
| /// the path style (POSIX, Windows, or URLs) of the host platform. For example: |
| /// |
| /// p.join('directory', 'file.txt'); |
| /// |
| /// This calls the top-level [join] function to join "directory" and "file.txt" |
| /// using the current platform's directory separator. |
| /// |
| /// If you want to work with paths for a specific platform regardless of the |
| /// underlying platform that the program is running on, you can create a |
| /// [Context] and give it an explicit [Style]: |
| /// |
| /// var context = p.Context(style: Style.windows); |
| /// context.join('directory', 'file.txt'); |
| /// |
| /// This will join "directory" and "file.txt" using the Windows path separator, |
| /// even when the program is run on a POSIX machine. |
| library; |
| |
| import 'src/context.dart'; |
| import 'src/style.dart'; |
| |
| export 'src/context.dart' hide createInternal; |
| export 'src/path_exception.dart'; |
| export 'src/path_map.dart'; |
| export 'src/path_set.dart'; |
| export 'src/style.dart'; |
| |
| /// A default context for manipulating POSIX paths. |
| final Context posix = Context(style: Style.posix); |
| |
| /// A default context for manipulating Windows paths. |
| final Context windows = Context(style: Style.windows); |
| |
| /// A default context for manipulating URLs. |
| /// |
| /// URL path equality is undefined for paths that differ only in their |
| /// percent-encoding or only in the case of their host segment. |
| final Context url = Context(style: Style.url); |
| |
| /// The system path context. |
| /// |
| /// This differs from a context created with [Context.new] in that its |
| /// [Context.current] is always the current working directory, rather than being |
| /// set once when the context is created. |
| final Context context = createInternal(); |
| |
| /// Returns the [Style] of the current context. |
| /// |
| /// This is the style that all top-level path functions will use. |
| Style get style => context.style; |
| |
| /// Gets the path to the current working directory. |
| /// |
| /// In the browser, this means the current URL, without the last file segment. |
| String get current { |
| // If the current working directory gets deleted out from under the program, |
| // accessing it will throw an IO exception. In order to avoid transient |
| // errors, if we already have a cached working directory, catch the error and |
| // use that. |
| Uri uri; |
| try { |
| uri = Uri.base; |
| } on Exception { |
| if (_current != null) return _current!; |
| rethrow; |
| } |
| |
| // Converting the base URI to a file path is pretty slow, and the base URI |
| // rarely changes in practice, so we cache the result here. |
| if (uri == _currentUriBase) return _current!; |
| _currentUriBase = uri; |
| |
| if (Style.platform == Style.url) { |
| _current = uri.resolve('.').toString(); |
| } else { |
| final path = uri.toFilePath(); |
| // Remove trailing '/' or '\' unless it is the only thing left |
| // (for instance the root on Linux). |
| final lastIndex = path.length - 1; |
| assert(path[lastIndex] == '/' || path[lastIndex] == '\\'); |
| _current = lastIndex == 0 ? path : path.substring(0, lastIndex); |
| } |
| return _current!; |
| } |
| |
| /// The last value returned by [Uri.base]. |
| /// |
| /// This is used to cache the current working directory. |
| Uri? _currentUriBase; |
| |
| /// The last known value of the current working directory. |
| /// |
| /// This is cached because [current] is called frequently but rarely actually |
| /// changes. |
| String? _current; |
| |
| /// Gets the path separator for the current platform. This is `\` on Windows |
| /// and `/` on other platforms (including the browser). |
| String get separator => context.separator; |
| |
| /// Returns a new path with the given path parts appended to [current]. |
| /// |
| /// Equivalent to [join()] with [current] as the first argument. Example: |
| /// |
| /// p.absolute('path', 'to/foo'); // -> '/your/current/dir/path/to/foo' |
| /// |
| /// Does not [normalize] or [canonicalize] paths. |
| String absolute(String part1, |
| [String? part2, |
| String? part3, |
| String? part4, |
| String? part5, |
| String? part6, |
| String? part7, |
| String? part8, |
| String? part9, |
| String? part10, |
| String? part11, |
| String? part12, |
| String? part13, |
| String? part14, |
| String? part15]) => |
| context.absolute(part1, part2, part3, part4, part5, part6, part7, part8, |
| part9, part10, part11, part12, part13, part14, part15); |
| |
| /// Gets the part of [path] after the last separator. |
| /// |
| /// p.basename('path/to/foo.dart'); // -> 'foo.dart' |
| /// p.basename('path/to'); // -> 'to' |
| /// |
| /// Trailing separators are ignored. |
| /// |
| /// p.basename('path/to/'); // -> 'to' |
| String basename(String path) => context.basename(path); |
| |
| /// Gets the part of [path] after the last separator, and without any trailing |
| /// file extension. |
| /// |
| /// p.basenameWithoutExtension('path/to/foo.dart'); // -> 'foo' |
| /// |
| /// Trailing separators are ignored. |
| /// |
| /// p.basenameWithoutExtension('path/to/foo.dart/'); // -> 'foo' |
| String basenameWithoutExtension(String path) => |
| context.basenameWithoutExtension(path); |
| |
| /// Gets the part of [path] before the last separator. |
| /// |
| /// p.dirname('path/to/foo.dart'); // -> 'path/to' |
| /// p.dirname('path/to'); // -> 'path' |
| /// |
| /// Trailing separators are ignored. |
| /// |
| /// p.dirname('path/to/'); // -> 'path' |
| /// |
| /// If an absolute path contains no directories, only a root, then the root |
| /// is returned. |
| /// |
| /// p.dirname('/'); // -> '/' (posix) |
| /// p.dirname('c:\'); // -> 'c:\' (windows) |
| /// |
| /// If a relative path has no directories, then '.' is returned. |
| /// |
| /// p.dirname('foo'); // -> '.' |
| /// p.dirname(''); // -> '.' |
| String dirname(String path) => context.dirname(path); |
| |
| /// Gets the file extension of [path]: the portion of [basename] from the last |
| /// `.` to the end (including the `.` itself). |
| /// |
| /// p.extension('path/to/foo.dart'); // -> '.dart' |
| /// p.extension('path/to/foo'); // -> '' |
| /// p.extension('path.to/foo'); // -> '' |
| /// p.extension('path/to/foo.dart.js'); // -> '.js' |
| /// |
| /// If the file name starts with a `.`, then that is not considered the |
| /// extension: |
| /// |
| /// p.extension('~/.bashrc'); // -> '' |
| /// p.extension('~/.notes.txt'); // -> '.txt' |
| /// |
| /// Takes an optional parameter `level` which makes possible to return |
| /// multiple extensions having `level` number of dots. If `level` exceeds the |
| /// number of dots, the full extension is returned. The value of `level` must |
| /// be greater than 0, else `RangeError` is thrown. |
| /// |
| /// p.extension('foo.bar.dart.js', 2); // -> '.dart.js |
| /// p.extension('foo.bar.dart.js', 3); // -> '.bar.dart.js' |
| /// p.extension('foo.bar.dart.js', 10); // -> '.bar.dart.js' |
| /// p.extension('path/to/foo.bar.dart.js', 2); // -> '.dart.js' |
| String extension(String path, [int level = 1]) => |
| context.extension(path, level); |
| |
| /// Returns the root of [path], if it's absolute, or the empty string if it's |
| /// relative. |
| /// |
| /// // Unix |
| /// p.rootPrefix('path/to/foo'); // -> '' |
| /// p.rootPrefix('/path/to/foo'); // -> '/' |
| /// |
| /// // Windows |
| /// p.rootPrefix(r'path\to\foo'); // -> '' |
| /// p.rootPrefix(r'C:\path\to\foo'); // -> r'C:\' |
| /// p.rootPrefix(r'\\server\share\a\b'); // -> r'\\server\share' |
| /// |
| /// // URL |
| /// p.rootPrefix('path/to/foo'); // -> '' |
| /// p.rootPrefix('https://dart.dev/path/to/foo'); |
| /// // -> 'https://dart.dev' |
| String rootPrefix(String path) => context.rootPrefix(path); |
| |
| /// Returns `true` if [path] is an absolute path and `false` if it is a |
| /// relative path. |
| /// |
| /// On POSIX systems, absolute paths start with a `/` (forward slash). On |
| /// Windows, an absolute path starts with `\\`, or a drive letter followed by |
| /// `:/` or `:\`. For URLs, absolute paths either start with a protocol and |
| /// optional hostname (e.g. `https://dart.dev`, `file://`) or with a `/`. |
| /// |
| /// URLs that start with `/` are known as "root-relative", since they're |
| /// relative to the root of the current URL. Since root-relative paths are still |
| /// absolute in every other sense, [isAbsolute] will return true for them. They |
| /// can be detected using [isRootRelative]. |
| bool isAbsolute(String path) => context.isAbsolute(path); |
| |
| /// Returns `true` if [path] is a relative path and `false` if it is absolute. |
| /// On POSIX systems, absolute paths start with a `/` (forward slash). On |
| /// Windows, an absolute path starts with `\\`, or a drive letter followed by |
| /// `:/` or `:\`. |
| bool isRelative(String path) => context.isRelative(path); |
| |
| /// Returns `true` if [path] is a root-relative path and `false` if it's not. |
| /// |
| /// URLs that start with `/` are known as "root-relative", since they're |
| /// relative to the root of the current URL. Since root-relative paths are still |
| /// absolute in every other sense, [isAbsolute] will return true for them. They |
| /// can be detected using [isRootRelative]. |
| /// |
| /// No POSIX and Windows paths are root-relative. |
| bool isRootRelative(String path) => context.isRootRelative(path); |
| |
| /// Joins the given path parts into a single path using the current platform's |
| /// [separator]. Example: |
| /// |
| /// p.join('path', 'to', 'foo'); // -> 'path/to/foo' |
| /// |
| /// If any part ends in a path separator, then a redundant separator will not |
| /// be added: |
| /// |
| /// p.join('path/', 'to', 'foo'); // -> 'path/to/foo' |
| /// |
| /// If a part is an absolute path, then anything before that will be ignored: |
| /// |
| /// p.join('path', '/to', 'foo'); // -> '/to/foo' |
| String join(String part1, |
| [String? part2, |
| String? part3, |
| String? part4, |
| String? part5, |
| String? part6, |
| String? part7, |
| String? part8, |
| String? part9, |
| String? part10, |
| String? part11, |
| String? part12, |
| String? part13, |
| String? part14, |
| String? part15, |
| String? part16]) => |
| context.join(part1, part2, part3, part4, part5, part6, part7, part8, part9, |
| part10, part11, part12, part13, part14, part15, part16); |
| |
| /// Joins the given path parts into a single path using the current platform's |
| /// [separator]. Example: |
| /// |
| /// p.joinAll(['path', 'to', 'foo']); // -> 'path/to/foo' |
| /// |
| /// If any part ends in a path separator, then a redundant separator will not |
| /// be added: |
| /// |
| /// p.joinAll(['path/', 'to', 'foo']); // -> 'path/to/foo' |
| /// |
| /// If a part is an absolute path, then anything before that will be ignored: |
| /// |
| /// p.joinAll(['path', '/to', 'foo']); // -> '/to/foo' |
| /// |
| /// For a fixed number of parts, [join] is usually terser. |
| String joinAll(Iterable<String> parts) => context.joinAll(parts); |
| |
| /// Splits [path] into its components using the current platform's [separator]. |
| /// |
| /// p.split('path/to/foo'); // -> ['path', 'to', 'foo'] |
| /// |
| /// The path will *not* be normalized before splitting. |
| /// |
| /// p.split('path/../foo'); // -> ['path', '..', 'foo'] |
| /// |
| /// If [path] is absolute, the root directory will be the first element in the |
| /// array. Example: |
| /// |
| /// // Unix |
| /// p.split('/path/to/foo'); // -> ['/', 'path', 'to', 'foo'] |
| /// |
| /// // Windows |
| /// p.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo'] |
| /// p.split(r'\\server\share\path\to\foo'); |
| /// // -> [r'\\server\share', 'foo', 'bar', 'baz'] |
| /// |
| /// // Browser |
| /// p.split('https://dart.dev/path/to/foo'); |
| /// // -> ['https://dart.dev', 'path', 'to', 'foo'] |
| List<String> split(String path) => context.split(path); |
| |
| /// Canonicalizes [path]. |
| /// |
| /// This is guaranteed to return the same path for two different input paths |
| /// if and only if both input paths point to the same location. Unlike |
| /// [normalize], it returns absolute paths when possible and canonicalizes |
| /// ASCII case on Windows. |
| /// |
| /// Note that this does not resolve symlinks. |
| /// |
| /// If you want a map that uses path keys, it's probably more efficient to use a |
| /// Map with [equals] and [hash] specified as the callbacks to use for keys than |
| /// it is to canonicalize every key. |
| String canonicalize(String path) => context.canonicalize(path); |
| |
| /// Normalizes [path], simplifying it by handling `..`, and `.`, and |
| /// removing redundant path separators whenever possible. |
| /// |
| /// Note that this is *not* guaranteed to return the same result for two |
| /// equivalent input paths. For that, see [canonicalize]. Or, if you're using |
| /// paths as map keys use [equals] and [hash] as the key callbacks. |
| /// |
| /// p.normalize('path/./to/..//file.text'); // -> 'path/file.txt' |
| String normalize(String path) => context.normalize(path); |
| |
| /// Attempts to convert [path] to an equivalent relative path from the current |
| /// directory. |
| /// |
| /// // Given current directory is /root/path: |
| /// p.relative('/root/path/a/b.dart'); // -> 'a/b.dart' |
| /// p.relative('/root/other.dart'); // -> '../other.dart' |
| /// |
| /// If the [from] argument is passed, [path] is made relative to that instead. |
| /// |
| /// p.relative('/root/path/a/b.dart', from: '/root/path'); // -> 'a/b.dart' |
| /// p.relative('/root/other.dart', from: '/root/path'); |
| /// // -> '../other.dart' |
| /// |
| /// If [path] and/or [from] are relative paths, they are assumed to be relative |
| /// to the current directory. |
| /// |
| /// Since there is no relative path from one drive letter to another on Windows, |
| /// or from one hostname to another for URLs, this will return an absolute path |
| /// in those cases. |
| /// |
| /// // Windows |
| /// p.relative(r'D:\other', from: r'C:\home'); // -> 'D:\other' |
| /// |
| /// // URL |
| /// p.relative('https://dart.dev', from: 'https://pub.dev'); |
| /// // -> 'https://dart.dev' |
| String relative(String path, {String? from}) => |
| context.relative(path, from: from); |
| |
| /// Returns `true` if [child] is a path beneath `parent`, and `false` otherwise. |
| /// |
| /// p.isWithin('/root/path', '/root/path/a'); // -> true |
| /// p.isWithin('/root/path', '/root/other'); // -> false |
| /// p.isWithin('/root/path', '/root/path') // -> false |
| bool isWithin(String parent, String child) => context.isWithin(parent, child); |
| |
| /// Returns `true` if [path1] points to the same location as [path2], and |
| /// `false` otherwise. |
| /// |
| /// The [hash] function returns a hash code that matches these equality |
| /// semantics. |
| bool equals(String path1, String path2) => context.equals(path1, path2); |
| |
| /// Returns a hash code for [path] such that, if [equals] returns `true` for two |
| /// paths, their hash codes are the same. |
| /// |
| /// Note that the same path may have different hash codes on different platforms |
| /// or with different [current] directories. |
| int hash(String path) => context.hash(path); |
| |
| /// Removes a trailing extension from the last part of [path]. |
| /// |
| /// p.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' |
| String withoutExtension(String path) => context.withoutExtension(path); |
| |
| /// Returns [path] with the trailing extension set to [extension]. |
| /// |
| /// If [path] doesn't have a trailing extension, this just adds [extension] to |
| /// the end. |
| /// |
| /// p.setExtension('path/to/foo.dart', '.js') // -> 'path/to/foo.js' |
| /// p.setExtension('path/to/foo.dart.js', '.map') |
| /// // -> 'path/to/foo.dart.map' |
| /// p.setExtension('path/to/foo', '.js') // -> 'path/to/foo.js' |
| String setExtension(String path, String extension) => |
| context.setExtension(path, extension); |
| |
| /// Returns the path represented by [uri], which may be a [String] or a [Uri]. |
| /// |
| /// For POSIX and Windows styles, [uri] must be a `file:` URI. For the URL |
| /// style, this will just convert [uri] to a string. |
| /// |
| /// // POSIX |
| /// p.fromUri('file:///path/to/foo') // -> '/path/to/foo' |
| /// |
| /// // Windows |
| /// p.fromUri('file:///C:/path/to/foo') // -> r'C:\path\to\foo' |
| /// |
| /// // URL |
| /// p.fromUri('https://dart.dev/path/to/foo') |
| /// // -> 'https://dart.dev/path/to/foo' |
| /// |
| /// If [uri] is relative, a relative path will be returned. |
| /// |
| /// p.fromUri('path/to/foo'); // -> 'path/to/foo' |
| String fromUri(Object? uri) => context.fromUri(uri!); |
| |
| /// Returns the URI that represents [path]. |
| /// |
| /// For POSIX and Windows styles, this will return a `file:` URI. For the URL |
| /// style, this will just convert [path] to a [Uri]. |
| /// |
| /// // POSIX |
| /// p.toUri('/path/to/foo') |
| /// // -> Uri.parse('file:///path/to/foo') |
| /// |
| /// // Windows |
| /// p.toUri(r'C:\path\to\foo') |
| /// // -> Uri.parse('file:///C:/path/to/foo') |
| /// |
| /// // URL |
| /// p.toUri('https://dart.dev/path/to/foo') |
| /// // -> Uri.parse('https://dart.dev/path/to/foo') |
| /// |
| /// If [path] is relative, a relative URI will be returned. |
| /// |
| /// p.toUri('path/to/foo') // -> Uri.parse('path/to/foo') |
| Uri toUri(String path) => context.toUri(path); |
| |
| /// Returns a terse, human-readable representation of [uri]. |
| /// |
| /// [uri] can be a [String] or a [Uri]. If it can be made relative to the |
| /// current working directory, that's done. Otherwise, it's returned as-is. This |
| /// gracefully handles non-`file:` URIs for [Style.posix] and [Style.windows]. |
| /// |
| /// The returned value is meant for human consumption, and may be either URI- |
| /// or path-formatted. |
| /// |
| /// // POSIX at "/root/path" |
| /// p.prettyUri('file:///root/path/a/b.dart'); // -> 'a/b.dart' |
| /// p.prettyUri('https://dart.dev/'); // -> 'https://dart.dev' |
| /// |
| /// // Windows at "C:\root\path" |
| /// p.prettyUri('file:///C:/root/path/a/b.dart'); // -> r'a\b.dart' |
| /// p.prettyUri('https://dart.dev/'); // -> 'https://dart.dev' |
| /// |
| /// // URL at "https://dart.dev/root/path" |
| /// p.prettyUri('https://dart.dev/root/path/a/b.dart'); // -> r'a/b.dart' |
| /// p.prettyUri('file:///root/path'); // -> 'file:///root/path' |
| String prettyUri(Object? uri) => context.prettyUri(uri!); |