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