OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2013, 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 library path.context; |
| 6 |
| 7 import 'internal_style.dart'; |
| 8 import 'style.dart'; |
| 9 import 'parsed_path.dart'; |
| 10 import 'path_exception.dart'; |
| 11 import '../path.dart' as p; |
| 12 |
| 13 Context createInternal() => new Context._internal(); |
| 14 |
| 15 /// An instantiable class for manipulating paths. Unlike the top-level |
| 16 /// functions, this lets you explicitly select what platform the paths will use. |
| 17 class Context { |
| 18 /// Creates a new path context for the given style and current directory. |
| 19 /// |
| 20 /// If [style] is omitted, it uses the host operating system's path style. If |
| 21 /// only [current] is omitted, it defaults ".". If *both* [style] and |
| 22 /// [current] are omitted, [current] defaults to the real current working |
| 23 /// directory. |
| 24 /// |
| 25 /// On the browser, [style] defaults to [Style.url] and [current] defaults to |
| 26 /// the current URL. |
| 27 factory Context({Style style, String current}) { |
| 28 if (current == null) { |
| 29 if (style == null) { |
| 30 current = p.current; |
| 31 } else { |
| 32 current = "."; |
| 33 } |
| 34 } |
| 35 |
| 36 if (style == null) { |
| 37 style = Style.platform; |
| 38 } else if (style is! InternalStyle) { |
| 39 throw new ArgumentError("Only styles defined by the path package are " |
| 40 "allowed."); |
| 41 } |
| 42 |
| 43 return new Context._(style as InternalStyle, current); |
| 44 } |
| 45 |
| 46 /// Create a [Context] to be used internally within path. |
| 47 Context._internal() |
| 48 : style = Style.platform as InternalStyle, |
| 49 _current = null; |
| 50 |
| 51 Context._(this.style, this._current); |
| 52 |
| 53 /// The style of path that this context works with. |
| 54 final InternalStyle style; |
| 55 |
| 56 /// The current directory given when Context was created. If null, current |
| 57 /// directory is evaluated from 'p.current'. |
| 58 final String _current; |
| 59 |
| 60 /// The current directory that relative paths are relative to. |
| 61 String get current => _current != null ? _current : p.current; |
| 62 |
| 63 /// Gets the path separator for the context's [style]. On Mac and Linux, |
| 64 /// this is `/`. On Windows, it's `\`. |
| 65 String get separator => style.separator; |
| 66 |
| 67 /// Creates a new path by appending the given path parts to [current]. |
| 68 /// Equivalent to [join()] with [current] as the first argument. Example: |
| 69 /// |
| 70 /// var context = new Context(current: '/root'); |
| 71 /// context.absolute('path', 'to', 'foo'); // -> '/root/path/to/foo' |
| 72 /// |
| 73 /// If [current] isn't absolute, this won't return an absolute path. |
| 74 String absolute(String part1, [String part2, String part3, String part4, |
| 75 String part5, String part6, String part7]) { |
| 76 return join(current, part1, part2, part3, part4, part5, part6, part7); |
| 77 } |
| 78 |
| 79 /// Gets the part of [path] after the last separator on the context's |
| 80 /// platform. |
| 81 /// |
| 82 /// context.basename('path/to/foo.dart'); // -> 'foo.dart' |
| 83 /// context.basename('path/to'); // -> 'to' |
| 84 /// |
| 85 /// Trailing separators are ignored. |
| 86 /// |
| 87 /// context.basename('path/to/'); // -> 'to' |
| 88 String basename(String path) => _parse(path).basename; |
| 89 |
| 90 /// Gets the part of [path] after the last separator on the context's |
| 91 /// platform, and without any trailing file extension. |
| 92 /// |
| 93 /// context.basenameWithoutExtension('path/to/foo.dart'); // -> 'foo' |
| 94 /// |
| 95 /// Trailing separators are ignored. |
| 96 /// |
| 97 /// context.basenameWithoutExtension('path/to/foo.dart/'); // -> 'foo' |
| 98 String basenameWithoutExtension(String path) => |
| 99 _parse(path).basenameWithoutExtension; |
| 100 |
| 101 /// Gets the part of [path] before the last separator. |
| 102 /// |
| 103 /// context.dirname('path/to/foo.dart'); // -> 'path/to' |
| 104 /// context.dirname('path/to'); // -> 'path' |
| 105 /// |
| 106 /// Trailing separators are ignored. |
| 107 /// |
| 108 /// context.dirname('path/to/'); // -> 'path' |
| 109 String dirname(String path) { |
| 110 var parsed = _parse(path); |
| 111 parsed.removeTrailingSeparators(); |
| 112 if (parsed.parts.isEmpty) return parsed.root == null ? '.' : parsed.root; |
| 113 if (parsed.parts.length == 1) { |
| 114 return parsed.root == null ? '.' : parsed.root; |
| 115 } |
| 116 parsed.parts.removeLast(); |
| 117 parsed.separators.removeLast(); |
| 118 parsed.removeTrailingSeparators(); |
| 119 return parsed.toString(); |
| 120 } |
| 121 |
| 122 /// Gets the file extension of [path]: the portion of [basename] from the last |
| 123 /// `.` to the end (including the `.` itself). |
| 124 /// |
| 125 /// context.extension('path/to/foo.dart'); // -> '.dart' |
| 126 /// context.extension('path/to/foo'); // -> '' |
| 127 /// context.extension('path.to/foo'); // -> '' |
| 128 /// context.extension('path/to/foo.dart.js'); // -> '.js' |
| 129 /// |
| 130 /// If the file name starts with a `.`, then it is not considered an |
| 131 /// extension: |
| 132 /// |
| 133 /// context.extension('~/.bashrc'); // -> '' |
| 134 /// context.extension('~/.notes.txt'); // -> '.txt' |
| 135 String extension(String path) => _parse(path).extension; |
| 136 |
| 137 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed. |
| 138 /// Returns the root of [path] if it's absolute, or an empty string if it's |
| 139 /// relative. |
| 140 /// |
| 141 /// // Unix |
| 142 /// context.rootPrefix('path/to/foo'); // -> '' |
| 143 /// context.rootPrefix('/path/to/foo'); // -> '/' |
| 144 /// |
| 145 /// // Windows |
| 146 /// context.rootPrefix(r'path\to\foo'); // -> '' |
| 147 /// context.rootPrefix(r'C:\path\to\foo'); // -> r'C:\' |
| 148 /// |
| 149 /// // URL |
| 150 /// context.rootPrefix('path/to/foo'); // -> '' |
| 151 /// context.rootPrefix('http://dartlang.org/path/to/foo'); |
| 152 /// // -> 'http://dartlang.org' |
| 153 String rootPrefix(String path) => path.substring(0, style.rootLength(path)); |
| 154 |
| 155 /// Returns `true` if [path] is an absolute path and `false` if it is a |
| 156 /// relative path. |
| 157 /// |
| 158 /// On POSIX systems, absolute paths start with a `/` (forward slash). On |
| 159 /// Windows, an absolute path starts with `\\`, or a drive letter followed by |
| 160 /// `:/` or `:\`. For URLs, absolute paths either start with a protocol and |
| 161 /// optional hostname (e.g. `http://dartlang.org`, `file://`) or with a `/`. |
| 162 /// |
| 163 /// URLs that start with `/` are known as "root-relative", since they're |
| 164 /// relative to the root of the current URL. Since root-relative paths are |
| 165 /// still absolute in every other sense, [isAbsolute] will return true for |
| 166 /// them. They can be detected using [isRootRelative]. |
| 167 bool isAbsolute(String path) => style.rootLength(path) > 0; |
| 168 |
| 169 /// Returns `true` if [path] is a relative path and `false` if it is absolute. |
| 170 /// On POSIX systems, absolute paths start with a `/` (forward slash). On |
| 171 /// Windows, an absolute path starts with `\\`, or a drive letter followed by |
| 172 /// `:/` or `:\`. |
| 173 bool isRelative(String path) => !this.isAbsolute(path); |
| 174 |
| 175 /// Returns `true` if [path] is a root-relative path and `false` if it's not. |
| 176 /// |
| 177 /// URLs that start with `/` are known as "root-relative", since they're |
| 178 /// relative to the root of the current URL. Since root-relative paths are |
| 179 /// still absolute in every other sense, [isAbsolute] will return true for |
| 180 /// them. They can be detected using [isRootRelative]. |
| 181 /// |
| 182 /// No POSIX and Windows paths are root-relative. |
| 183 bool isRootRelative(String path) => style.isRootRelative(path); |
| 184 |
| 185 /// Joins the given path parts into a single path. Example: |
| 186 /// |
| 187 /// context.join('path', 'to', 'foo'); // -> 'path/to/foo' |
| 188 /// |
| 189 /// If any part ends in a path separator, then a redundant separator will not |
| 190 /// be added: |
| 191 /// |
| 192 /// context.join('path/', 'to', 'foo'); // -> 'path/to/foo |
| 193 /// |
| 194 /// If a part is an absolute path, then anything before that will be ignored: |
| 195 /// |
| 196 /// context.join('path', '/to', 'foo'); // -> '/to/foo' |
| 197 /// |
| 198 String join(String part1, [String part2, String part3, String part4, |
| 199 String part5, String part6, String part7, String part8]) { |
| 200 var parts = <String>[ |
| 201 part1, |
| 202 part2, |
| 203 part3, |
| 204 part4, |
| 205 part5, |
| 206 part6, |
| 207 part7, |
| 208 part8 |
| 209 ]; |
| 210 _validateArgList("join", parts); |
| 211 return joinAll(parts.where((part) => part != null)); |
| 212 } |
| 213 |
| 214 /// Joins the given path parts into a single path. Example: |
| 215 /// |
| 216 /// context.joinAll(['path', 'to', 'foo']); // -> 'path/to/foo' |
| 217 /// |
| 218 /// If any part ends in a path separator, then a redundant separator will not |
| 219 /// be added: |
| 220 /// |
| 221 /// context.joinAll(['path/', 'to', 'foo']); // -> 'path/to/foo |
| 222 /// |
| 223 /// If a part is an absolute path, then anything before that will be ignored: |
| 224 /// |
| 225 /// context.joinAll(['path', '/to', 'foo']); // -> '/to/foo' |
| 226 /// |
| 227 /// For a fixed number of parts, [join] is usually terser. |
| 228 String joinAll(Iterable<String> parts) { |
| 229 var buffer = new StringBuffer(); |
| 230 var needsSeparator = false; |
| 231 var isAbsoluteAndNotRootRelative = false; |
| 232 |
| 233 for (var part in parts.where((part) => part != '')) { |
| 234 if (this.isRootRelative(part) && isAbsoluteAndNotRootRelative) { |
| 235 // If the new part is root-relative, it preserves the previous root but |
| 236 // replaces the path after it. |
| 237 var parsed = _parse(part); |
| 238 parsed.root = this.rootPrefix(buffer.toString()); |
| 239 if (style.needsSeparator(parsed.root)) { |
| 240 parsed.separators[0] = style.separator; |
| 241 } |
| 242 buffer.clear(); |
| 243 buffer.write(parsed.toString()); |
| 244 } else if (this.isAbsolute(part)) { |
| 245 isAbsoluteAndNotRootRelative = !this.isRootRelative(part); |
| 246 // An absolute path discards everything before it. |
| 247 buffer.clear(); |
| 248 buffer.write(part); |
| 249 } else { |
| 250 if (part.length > 0 && style.containsSeparator(part[0])) { |
| 251 // The part starts with a separator, so we don't need to add one. |
| 252 } else if (needsSeparator) { |
| 253 buffer.write(separator); |
| 254 } |
| 255 |
| 256 buffer.write(part); |
| 257 } |
| 258 |
| 259 // Unless this part ends with a separator, we'll need to add one before |
| 260 // the next part. |
| 261 needsSeparator = style.needsSeparator(part); |
| 262 } |
| 263 |
| 264 return buffer.toString(); |
| 265 } |
| 266 |
| 267 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed. |
| 268 /// Splits [path] into its components using the current platform's |
| 269 /// [separator]. Example: |
| 270 /// |
| 271 /// context.split('path/to/foo'); // -> ['path', 'to', 'foo'] |
| 272 /// |
| 273 /// The path will *not* be normalized before splitting. |
| 274 /// |
| 275 /// context.split('path/../foo'); // -> ['path', '..', 'foo'] |
| 276 /// |
| 277 /// If [path] is absolute, the root directory will be the first element in the |
| 278 /// array. Example: |
| 279 /// |
| 280 /// // Unix |
| 281 /// context.split('/path/to/foo'); // -> ['/', 'path', 'to', 'foo'] |
| 282 /// |
| 283 /// // Windows |
| 284 /// context.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo'] |
| 285 List<String> split(String path) { |
| 286 var parsed = _parse(path); |
| 287 // Filter out empty parts that exist due to multiple separators in a row. |
| 288 parsed.parts = parsed.parts.where((part) => !part.isEmpty).toList(); |
| 289 if (parsed.root != null) parsed.parts.insert(0, parsed.root); |
| 290 return parsed.parts; |
| 291 } |
| 292 |
| 293 /// Normalizes [path], simplifying it by handling `..`, and `.`, and |
| 294 /// removing redundant path separators whenever possible. |
| 295 /// |
| 296 /// context.normalize('path/./to/..//file.text'); // -> 'path/file.txt' |
| 297 String normalize(String path) { |
| 298 var parsed = _parse(path); |
| 299 parsed.normalize(); |
| 300 return parsed.toString(); |
| 301 } |
| 302 |
| 303 /// Attempts to convert [path] to an equivalent relative path relative to |
| 304 /// [root]. |
| 305 /// |
| 306 /// var context = new Context(current: '/root/path'); |
| 307 /// context.relative('/root/path/a/b.dart'); // -> 'a/b.dart' |
| 308 /// context.relative('/root/other.dart'); // -> '../other.dart' |
| 309 /// |
| 310 /// If the [from] argument is passed, [path] is made relative to that instead. |
| 311 /// |
| 312 /// context.relative('/root/path/a/b.dart', |
| 313 /// from: '/root/path'); // -> 'a/b.dart' |
| 314 /// context.relative('/root/other.dart', |
| 315 /// from: '/root/path'); // -> '../other.dart' |
| 316 /// |
| 317 /// If [path] and/or [from] are relative paths, they are assumed to be |
| 318 /// relative to [current]. |
| 319 /// |
| 320 /// Since there is no relative path from one drive letter to another on |
| 321 /// Windows, this will return an absolute path in that case. |
| 322 /// |
| 323 /// context.relative(r'D:\other', from: r'C:\other'); // -> 'D:\other' |
| 324 /// |
| 325 /// This will also return an absolute path if an absolute [path] is passed to |
| 326 /// a context with a relative path for [current]. |
| 327 /// |
| 328 /// var context = new Context(r'some/relative/path'); |
| 329 /// context.relative(r'/absolute/path'); // -> '/absolute/path' |
| 330 /// |
| 331 /// If [root] is relative, it may be impossible to determine a path from |
| 332 /// [from] to [path]. For example, if [root] and [path] are "." and [from] is |
| 333 /// "/", no path can be determined. In this case, a [PathException] will be |
| 334 /// thrown. |
| 335 String relative(String path, {String from}) { |
| 336 // Avoid calling [current] since it is slow and calling join() when |
| 337 // [from] is absolute does nothing. |
| 338 if (from == null) { |
| 339 from = current; |
| 340 } else if (this.isRelative(from) || this.isRootRelative(from)) { |
| 341 from = this.join(current, from); |
| 342 } |
| 343 |
| 344 // We can't determine the path from a relative path to an absolute path. |
| 345 if (this.isRelative(from) && this.isAbsolute(path)) { |
| 346 return this.normalize(path); |
| 347 } |
| 348 |
| 349 // If the given path is relative, resolve it relative to the context's |
| 350 // current directory. |
| 351 if (this.isRelative(path) || this.isRootRelative(path)) { |
| 352 path = this.absolute(path); |
| 353 } |
| 354 |
| 355 // If the path is still relative and `from` is absolute, we're unable to |
| 356 // find a path from `from` to `path`. |
| 357 if (this.isRelative(path) && this.isAbsolute(from)) { |
| 358 throw new PathException('Unable to find a path to "$path" from "$from".'); |
| 359 } |
| 360 |
| 361 var fromParsed = _parse(from)..normalize(); |
| 362 var pathParsed = _parse(path)..normalize(); |
| 363 |
| 364 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '.') { |
| 365 return pathParsed.toString(); |
| 366 } |
| 367 |
| 368 // If the root prefixes don't match (for example, different drive letters |
| 369 // on Windows), then there is no relative path, so just return the absolute |
| 370 // one. In Windows, drive letters are case-insenstive and we allow |
| 371 // calculation of relative paths, even if a path has not been normalized. |
| 372 if (fromParsed.root != pathParsed.root && |
| 373 ((fromParsed.root == null || pathParsed.root == null) || |
| 374 fromParsed.root.toLowerCase().replaceAll('/', '\\') != |
| 375 pathParsed.root.toLowerCase().replaceAll('/', '\\'))) { |
| 376 return pathParsed.toString(); |
| 377 } |
| 378 |
| 379 // Strip off their common prefix. |
| 380 while (fromParsed.parts.length > 0 && |
| 381 pathParsed.parts.length > 0 && |
| 382 fromParsed.parts[0] == pathParsed.parts[0]) { |
| 383 fromParsed.parts.removeAt(0); |
| 384 fromParsed.separators.removeAt(1); |
| 385 pathParsed.parts.removeAt(0); |
| 386 pathParsed.separators.removeAt(1); |
| 387 } |
| 388 |
| 389 // If there are any directories left in the from path, we need to walk up |
| 390 // out of them. If a directory left in the from path is '..', it cannot |
| 391 // be cancelled by adding a '..'. |
| 392 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '..') { |
| 393 throw new PathException('Unable to find a path to "$path" from "$from".'); |
| 394 } |
| 395 pathParsed.parts.insertAll( |
| 396 0, new List.filled(fromParsed.parts.length, '..')); |
| 397 pathParsed.separators[0] = ''; |
| 398 pathParsed.separators.insertAll( |
| 399 1, new List.filled(fromParsed.parts.length, style.separator)); |
| 400 |
| 401 // Corner case: the paths completely collapsed. |
| 402 if (pathParsed.parts.length == 0) return '.'; |
| 403 |
| 404 // Corner case: path was '.' and some '..' directories were added in front. |
| 405 // Don't add a final '/.' in that case. |
| 406 if (pathParsed.parts.length > 1 && pathParsed.parts.last == '.') { |
| 407 pathParsed.parts.removeLast(); |
| 408 pathParsed.separators |
| 409 ..removeLast() |
| 410 ..removeLast() |
| 411 ..add(''); |
| 412 } |
| 413 |
| 414 // Make it relative. |
| 415 pathParsed.root = ''; |
| 416 pathParsed.removeTrailingSeparators(); |
| 417 |
| 418 return pathParsed.toString(); |
| 419 } |
| 420 |
| 421 /// Returns `true` if [child] is a path beneath `parent`, and `false` |
| 422 /// otherwise. |
| 423 /// |
| 424 /// path.isWithin('/root/path', '/root/path/a'); // -> true |
| 425 /// path.isWithin('/root/path', '/root/other'); // -> false |
| 426 /// path.isWithin('/root/path', '/root/path'); // -> false |
| 427 bool isWithin(String parent, String child) { |
| 428 var relative; |
| 429 try { |
| 430 relative = this.relative(child, from: parent); |
| 431 } on PathException catch (_) { |
| 432 // If no relative path from [parent] to [child] is found, [child] |
| 433 // definitely isn't a child of [parent]. |
| 434 return false; |
| 435 } |
| 436 |
| 437 var parts = this.split(relative); |
| 438 return this.isRelative(relative) && |
| 439 parts.first != '..' && |
| 440 parts.first != '.'; |
| 441 } |
| 442 |
| 443 /// Removes a trailing extension from the last part of [path]. |
| 444 /// |
| 445 /// context.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' |
| 446 String withoutExtension(String path) { |
| 447 var parsed = _parse(path); |
| 448 |
| 449 for (var i = parsed.parts.length - 1; i >= 0; i--) { |
| 450 if (!parsed.parts[i].isEmpty) { |
| 451 parsed.parts[i] = parsed.basenameWithoutExtension; |
| 452 break; |
| 453 } |
| 454 } |
| 455 |
| 456 return parsed.toString(); |
| 457 } |
| 458 |
| 459 /// Returns the path represented by [uri], which may be a [String] or a [Uri]. |
| 460 /// |
| 461 /// For POSIX and Windows styles, [uri] must be a `file:` URI. For the URL |
| 462 /// style, this will just convert [uri] to a string. |
| 463 /// |
| 464 /// // POSIX |
| 465 /// context.fromUri('file:///path/to/foo') |
| 466 /// // -> '/path/to/foo' |
| 467 /// |
| 468 /// // Windows |
| 469 /// context.fromUri('file:///C:/path/to/foo') |
| 470 /// // -> r'C:\path\to\foo' |
| 471 /// |
| 472 /// // URL |
| 473 /// context.fromUri('http://dartlang.org/path/to/foo') |
| 474 /// // -> 'http://dartlang.org/path/to/foo' |
| 475 /// |
| 476 /// If [uri] is relative, a relative path will be returned. |
| 477 /// |
| 478 /// path.fromUri('path/to/foo'); // -> 'path/to/foo' |
| 479 String fromUri(uri) { |
| 480 if (uri is String) uri = Uri.parse(uri); |
| 481 return style.pathFromUri(uri); |
| 482 } |
| 483 |
| 484 /// Returns the URI that represents [path]. |
| 485 /// |
| 486 /// For POSIX and Windows styles, this will return a `file:` URI. For the URL |
| 487 /// style, this will just convert [path] to a [Uri]. |
| 488 /// |
| 489 /// // POSIX |
| 490 /// context.toUri('/path/to/foo') |
| 491 /// // -> Uri.parse('file:///path/to/foo') |
| 492 /// |
| 493 /// // Windows |
| 494 /// context.toUri(r'C:\path\to\foo') |
| 495 /// // -> Uri.parse('file:///C:/path/to/foo') |
| 496 /// |
| 497 /// // URL |
| 498 /// context.toUri('http://dartlang.org/path/to/foo') |
| 499 /// // -> Uri.parse('http://dartlang.org/path/to/foo') |
| 500 Uri toUri(String path) { |
| 501 if (isRelative(path)) { |
| 502 return style.relativePathToUri(path); |
| 503 } else { |
| 504 return style.absolutePathToUri(join(current, path)); |
| 505 } |
| 506 } |
| 507 |
| 508 /// Returns a terse, human-readable representation of [uri]. |
| 509 /// |
| 510 /// [uri] can be a [String] or a [Uri]. If it can be made relative to the |
| 511 /// current working directory, that's done. Otherwise, it's returned as-is. |
| 512 /// This gracefully handles non-`file:` URIs for [Style.posix] and |
| 513 /// [Style.windows]. |
| 514 /// |
| 515 /// The returned value is meant for human consumption, and may be either URI- |
| 516 /// or path-formatted. |
| 517 /// |
| 518 /// // POSIX |
| 519 /// var context = new Context(current: '/root/path'); |
| 520 /// context.prettyUri('file:///root/path/a/b.dart'); // -> 'a/b.dart' |
| 521 /// context.prettyUri('http://dartlang.org/'); // -> 'http://dartlang.org' |
| 522 /// |
| 523 /// // Windows |
| 524 /// var context = new Context(current: r'C:\root\path'); |
| 525 /// context.prettyUri('file:///C:/root/path/a/b.dart'); // -> r'a\b.dart' |
| 526 /// context.prettyUri('http://dartlang.org/'); // -> 'http://dartlang.org' |
| 527 /// |
| 528 /// // URL |
| 529 /// var context = new Context(current: 'http://dartlang.org/root/path'); |
| 530 /// context.prettyUri('http://dartlang.org/root/path/a/b.dart'); |
| 531 /// // -> r'a/b.dart' |
| 532 /// context.prettyUri('file:///root/path'); // -> 'file:///root/path' |
| 533 String prettyUri(uri) { |
| 534 if (uri is String) uri = Uri.parse(uri); |
| 535 if (uri.scheme == 'file' && style == Style.url) return uri.toString(); |
| 536 if (uri.scheme != 'file' && uri.scheme != '' && style != Style.url) { |
| 537 return uri.toString(); |
| 538 } |
| 539 |
| 540 var path = normalize(fromUri(uri)); |
| 541 var rel = relative(path); |
| 542 |
| 543 // Only return a relative path if it's actually shorter than the absolute |
| 544 // path. This avoids ugly things like long "../" chains to get to the root |
| 545 // and then go back down. |
| 546 return split(rel).length > split(path).length ? path : rel; |
| 547 } |
| 548 |
| 549 ParsedPath _parse(String path) => new ParsedPath.parse(path, style); |
| 550 } |
| 551 |
| 552 /// Validates that there are no non-null arguments following a null one and |
| 553 /// throws an appropriate [ArgumentError] on failure. |
| 554 _validateArgList(String method, List<String> args) { |
| 555 for (var i = 1; i < args.length; i++) { |
| 556 // Ignore nulls hanging off the end. |
| 557 if (args[i] == null || args[i - 1] != null) continue; |
| 558 |
| 559 var numArgs; |
| 560 for (numArgs = args.length; numArgs >= 1; numArgs--) { |
| 561 if (args[numArgs - 1] != null) break; |
| 562 } |
| 563 |
| 564 // Show the arguments. |
| 565 var message = new StringBuffer(); |
| 566 message.write("$method("); |
| 567 message.write(args |
| 568 .take(numArgs) |
| 569 .map((arg) => arg == null ? "null" : '"$arg"') |
| 570 .join(", ")); |
| 571 message.write("): part ${i - 1} was null, but part $i was not."); |
| 572 throw new ArgumentError(message.toString()); |
| 573 } |
| 574 } |
OLD | NEW |