OLD | NEW |
---|---|
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 Loading... | |
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 } | |
OLD | NEW |