| 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, current); |
| 44 } |
| 45 |
| 46 /// Create a [Context] to be used internally within path. |
| 47 Context._internal() |
| 48 : style = Style.platform, |
| 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 = [part1, part2, part3, part4, part5, part6, part7, part8]; |
| 201 _validateArgList("join", parts); |
| 202 return joinAll(parts.where((part) => part != null)); |
| 203 } |
| 204 |
| 205 /// Joins the given path parts into a single path. Example: |
| 206 /// |
| 207 /// context.joinAll(['path', 'to', 'foo']); // -> 'path/to/foo' |
| 208 /// |
| 209 /// If any part ends in a path separator, then a redundant separator will not |
| 210 /// be added: |
| 211 /// |
| 212 /// context.joinAll(['path/', 'to', 'foo']); // -> 'path/to/foo |
| 213 /// |
| 214 /// If a part is an absolute path, then anything before that will be ignored: |
| 215 /// |
| 216 /// context.joinAll(['path', '/to', 'foo']); // -> '/to/foo' |
| 217 /// |
| 218 /// For a fixed number of parts, [join] is usually terser. |
| 219 String joinAll(Iterable<String> parts) { |
| 220 var buffer = new StringBuffer(); |
| 221 var needsSeparator = false; |
| 222 var isAbsoluteAndNotRootRelative = false; |
| 223 |
| 224 for (var part in parts.where((part) => part != '')) { |
| 225 if (this.isRootRelative(part) && isAbsoluteAndNotRootRelative) { |
| 226 // If the new part is root-relative, it preserves the previous root but |
| 227 // replaces the path after it. |
| 228 var parsed = _parse(part); |
| 229 parsed.root = this.rootPrefix(buffer.toString()); |
| 230 if (style.needsSeparator(parsed.root)) { |
| 231 parsed.separators[0] = style.separator; |
| 232 } |
| 233 buffer.clear(); |
| 234 buffer.write(parsed.toString()); |
| 235 } else if (this.isAbsolute(part)) { |
| 236 isAbsoluteAndNotRootRelative = !this.isRootRelative(part); |
| 237 // An absolute path discards everything before it. |
| 238 buffer.clear(); |
| 239 buffer.write(part); |
| 240 } else { |
| 241 if (part.length > 0 && style.containsSeparator(part[0])) { |
| 242 // The part starts with a separator, so we don't need to add one. |
| 243 } else if (needsSeparator) { |
| 244 buffer.write(separator); |
| 245 } |
| 246 |
| 247 buffer.write(part); |
| 248 } |
| 249 |
| 250 // Unless this part ends with a separator, we'll need to add one before |
| 251 // the next part. |
| 252 needsSeparator = style.needsSeparator(part); |
| 253 } |
| 254 |
| 255 return buffer.toString(); |
| 256 } |
| 257 |
| 258 // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed. |
| 259 /// Splits [path] into its components using the current platform's |
| 260 /// [separator]. Example: |
| 261 /// |
| 262 /// context.split('path/to/foo'); // -> ['path', 'to', 'foo'] |
| 263 /// |
| 264 /// The path will *not* be normalized before splitting. |
| 265 /// |
| 266 /// context.split('path/../foo'); // -> ['path', '..', 'foo'] |
| 267 /// |
| 268 /// If [path] is absolute, the root directory will be the first element in the |
| 269 /// array. Example: |
| 270 /// |
| 271 /// // Unix |
| 272 /// context.split('/path/to/foo'); // -> ['/', 'path', 'to', 'foo'] |
| 273 /// |
| 274 /// // Windows |
| 275 /// context.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo'] |
| 276 List<String> split(String path) { |
| 277 var parsed = _parse(path); |
| 278 // Filter out empty parts that exist due to multiple separators in a row. |
| 279 parsed.parts = parsed.parts.where((part) => !part.isEmpty).toList(); |
| 280 if (parsed.root != null) parsed.parts.insert(0, parsed.root); |
| 281 return parsed.parts; |
| 282 } |
| 283 |
| 284 /// Normalizes [path], simplifying it by handling `..`, and `.`, and |
| 285 /// removing redundant path separators whenever possible. |
| 286 /// |
| 287 /// context.normalize('path/./to/..//file.text'); // -> 'path/file.txt' |
| 288 String normalize(String path) { |
| 289 var parsed = _parse(path); |
| 290 parsed.normalize(); |
| 291 return parsed.toString(); |
| 292 } |
| 293 |
| 294 /// Attempts to convert [path] to an equivalent relative path relative to |
| 295 /// [root]. |
| 296 /// |
| 297 /// var context = new Context(current: '/root/path'); |
| 298 /// context.relative('/root/path/a/b.dart'); // -> 'a/b.dart' |
| 299 /// context.relative('/root/other.dart'); // -> '../other.dart' |
| 300 /// |
| 301 /// If the [from] argument is passed, [path] is made relative to that instead. |
| 302 /// |
| 303 /// context.relative('/root/path/a/b.dart', |
| 304 /// from: '/root/path'); // -> 'a/b.dart' |
| 305 /// context.relative('/root/other.dart', |
| 306 /// from: '/root/path'); // -> '../other.dart' |
| 307 /// |
| 308 /// If [path] and/or [from] are relative paths, they are assumed to be |
| 309 /// relative to [current]. |
| 310 /// |
| 311 /// Since there is no relative path from one drive letter to another on |
| 312 /// Windows, this will return an absolute path in that case. |
| 313 /// |
| 314 /// context.relative(r'D:\other', from: r'C:\other'); // -> 'D:\other' |
| 315 /// |
| 316 /// This will also return an absolute path if an absolute [path] is passed to |
| 317 /// a context with a relative path for [current]. |
| 318 /// |
| 319 /// var context = new Context(r'some/relative/path'); |
| 320 /// context.relative(r'/absolute/path'); // -> '/absolute/path' |
| 321 /// |
| 322 /// If [root] is relative, it may be impossible to determine a path from |
| 323 /// [from] to [path]. For example, if [root] and [path] are "." and [from] is |
| 324 /// "/", no path can be determined. In this case, a [PathException] will be |
| 325 /// thrown. |
| 326 String relative(String path, {String from}) { |
| 327 from = from == null ? current : this.join(current, from); |
| 328 |
| 329 // We can't determine the path from a relative path to an absolute path. |
| 330 if (this.isRelative(from) && this.isAbsolute(path)) { |
| 331 return this.normalize(path); |
| 332 } |
| 333 |
| 334 // If the given path is relative, resolve it relative to the context's |
| 335 // current directory. |
| 336 if (this.isRelative(path) || this.isRootRelative(path)) { |
| 337 path = this.absolute(path); |
| 338 } |
| 339 |
| 340 // If the path is still relative and `from` is absolute, we're unable to |
| 341 // find a path from `from` to `path`. |
| 342 if (this.isRelative(path) && this.isAbsolute(from)) { |
| 343 throw new PathException('Unable to find a path to "$path" from "$from".'); |
| 344 } |
| 345 |
| 346 var fromParsed = _parse(from)..normalize(); |
| 347 var pathParsed = _parse(path)..normalize(); |
| 348 |
| 349 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '.') { |
| 350 return pathParsed.toString(); |
| 351 } |
| 352 |
| 353 // If the root prefixes don't match (for example, different drive letters |
| 354 // on Windows), then there is no relative path, so just return the absolute |
| 355 // one. In Windows, drive letters are case-insenstive and we allow |
| 356 // calculation of relative paths, even if a path has not been normalized. |
| 357 if (fromParsed.root != pathParsed.root && |
| 358 ((fromParsed.root == null || pathParsed.root == null) || |
| 359 fromParsed.root.toLowerCase().replaceAll('/', '\\') != |
| 360 pathParsed.root.toLowerCase().replaceAll('/', '\\'))) { |
| 361 return pathParsed.toString(); |
| 362 } |
| 363 |
| 364 // Strip off their common prefix. |
| 365 while (fromParsed.parts.length > 0 && |
| 366 pathParsed.parts.length > 0 && |
| 367 fromParsed.parts[0] == pathParsed.parts[0]) { |
| 368 fromParsed.parts.removeAt(0); |
| 369 fromParsed.separators.removeAt(1); |
| 370 pathParsed.parts.removeAt(0); |
| 371 pathParsed.separators.removeAt(1); |
| 372 } |
| 373 |
| 374 // If there are any directories left in the from path, we need to walk up |
| 375 // out of them. If a directory left in the from path is '..', it cannot |
| 376 // be cancelled by adding a '..'. |
| 377 if (fromParsed.parts.length > 0 && fromParsed.parts[0] == '..') { |
| 378 throw new PathException('Unable to find a path to "$path" from "$from".'); |
| 379 } |
| 380 pathParsed.parts.insertAll( |
| 381 0, new List.filled(fromParsed.parts.length, '..')); |
| 382 pathParsed.separators[0] = ''; |
| 383 pathParsed.separators.insertAll( |
| 384 1, new List.filled(fromParsed.parts.length, style.separator)); |
| 385 |
| 386 // Corner case: the paths completely collapsed. |
| 387 if (pathParsed.parts.length == 0) return '.'; |
| 388 |
| 389 // Corner case: path was '.' and some '..' directories were added in front. |
| 390 // Don't add a final '/.' in that case. |
| 391 if (pathParsed.parts.length > 1 && pathParsed.parts.last == '.') { |
| 392 pathParsed.parts.removeLast(); |
| 393 pathParsed.separators |
| 394 ..removeLast() |
| 395 ..removeLast() |
| 396 ..add(''); |
| 397 } |
| 398 |
| 399 // Make it relative. |
| 400 pathParsed.root = ''; |
| 401 pathParsed.removeTrailingSeparators(); |
| 402 |
| 403 return pathParsed.toString(); |
| 404 } |
| 405 |
| 406 /// Returns `true` if [child] is a path beneath `parent`, and `false` |
| 407 /// otherwise. |
| 408 /// |
| 409 /// path.isWithin('/root/path', '/root/path/a'); // -> true |
| 410 /// path.isWithin('/root/path', '/root/other'); // -> false |
| 411 /// path.isWithin('/root/path', '/root/path'); // -> false |
| 412 bool isWithin(String parent, String child) { |
| 413 var relative; |
| 414 try { |
| 415 relative = this.relative(child, from: parent); |
| 416 } on PathException catch (_) { |
| 417 // If no relative path from [parent] to [child] is found, [child] |
| 418 // definitely isn't a child of [parent]. |
| 419 return false; |
| 420 } |
| 421 |
| 422 var parts = this.split(relative); |
| 423 return this.isRelative(relative) && |
| 424 parts.first != '..' && |
| 425 parts.first != '.'; |
| 426 } |
| 427 |
| 428 /// Removes a trailing extension from the last part of [path]. |
| 429 /// |
| 430 /// context.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' |
| 431 String withoutExtension(String path) { |
| 432 var parsed = _parse(path); |
| 433 |
| 434 for (var i = parsed.parts.length - 1; i >= 0; i--) { |
| 435 if (!parsed.parts[i].isEmpty) { |
| 436 parsed.parts[i] = parsed.basenameWithoutExtension; |
| 437 break; |
| 438 } |
| 439 } |
| 440 |
| 441 return parsed.toString(); |
| 442 } |
| 443 |
| 444 /// Returns the path represented by [uri], which may be a [String] or a [Uri]. |
| 445 /// |
| 446 /// For POSIX and Windows styles, [uri] must be a `file:` URI. For the URL |
| 447 /// style, this will just convert [uri] to a string. |
| 448 /// |
| 449 /// // POSIX |
| 450 /// context.fromUri('file:///path/to/foo') |
| 451 /// // -> '/path/to/foo' |
| 452 /// |
| 453 /// // Windows |
| 454 /// context.fromUri('file:///C:/path/to/foo') |
| 455 /// // -> r'C:\path\to\foo' |
| 456 /// |
| 457 /// // URL |
| 458 /// context.fromUri('http://dartlang.org/path/to/foo') |
| 459 /// // -> 'http://dartlang.org/path/to/foo' |
| 460 /// |
| 461 /// If [uri] is relative, a relative path will be returned. |
| 462 /// |
| 463 /// path.fromUri('path/to/foo'); // -> 'path/to/foo' |
| 464 String fromUri(uri) { |
| 465 if (uri is String) uri = Uri.parse(uri); |
| 466 return style.pathFromUri(uri); |
| 467 } |
| 468 |
| 469 /// Returns the URI that represents [path]. |
| 470 /// |
| 471 /// For POSIX and Windows styles, this will return a `file:` URI. For the URL |
| 472 /// style, this will just convert [path] to a [Uri]. |
| 473 /// |
| 474 /// // POSIX |
| 475 /// context.toUri('/path/to/foo') |
| 476 /// // -> Uri.parse('file:///path/to/foo') |
| 477 /// |
| 478 /// // Windows |
| 479 /// context.toUri(r'C:\path\to\foo') |
| 480 /// // -> Uri.parse('file:///C:/path/to/foo') |
| 481 /// |
| 482 /// // URL |
| 483 /// context.toUri('http://dartlang.org/path/to/foo') |
| 484 /// // -> Uri.parse('http://dartlang.org/path/to/foo') |
| 485 Uri toUri(String path) { |
| 486 if (isRelative(path)) { |
| 487 return style.relativePathToUri(path); |
| 488 } else { |
| 489 return style.absolutePathToUri(join(current, path)); |
| 490 } |
| 491 } |
| 492 |
| 493 /// Returns a terse, human-readable representation of [uri]. |
| 494 /// |
| 495 /// [uri] can be a [String] or a [Uri]. If it can be made relative to the |
| 496 /// current working directory, that's done. Otherwise, it's returned as-is. |
| 497 /// This gracefully handles non-`file:` URIs for [Style.posix] and |
| 498 /// [Style.windows]. |
| 499 /// |
| 500 /// The returned value is meant for human consumption, and may be either URI- |
| 501 /// or path-formatted. |
| 502 /// |
| 503 /// // POSIX |
| 504 /// var context = new Context(current: '/root/path'); |
| 505 /// context.prettyUri('file:///root/path/a/b.dart'); // -> 'a/b.dart' |
| 506 /// context.prettyUri('http://dartlang.org/'); // -> 'http://dartlang.org' |
| 507 /// |
| 508 /// // Windows |
| 509 /// var context = new Context(current: r'C:\root\path'); |
| 510 /// context.prettyUri('file:///C:/root/path/a/b.dart'); // -> r'a\b.dart' |
| 511 /// context.prettyUri('http://dartlang.org/'); // -> 'http://dartlang.org' |
| 512 /// |
| 513 /// // URL |
| 514 /// var context = new Context(current: 'http://dartlang.org/root/path'); |
| 515 /// context.prettyUri('http://dartlang.org/root/path/a/b.dart'); |
| 516 /// // -> r'a/b.dart' |
| 517 /// context.prettyUri('file:///root/path'); // -> 'file:///root/path' |
| 518 String prettyUri(uri) { |
| 519 if (uri is String) uri = Uri.parse(uri); |
| 520 if (uri.scheme == 'file' && style == Style.url) return uri.toString(); |
| 521 if (uri.scheme != 'file' && uri.scheme != '' && style != Style.url) { |
| 522 return uri.toString(); |
| 523 } |
| 524 |
| 525 var path = normalize(fromUri(uri)); |
| 526 var rel = relative(path); |
| 527 |
| 528 // Only return a relative path if it's actually shorter than the absolute |
| 529 // path. This avoids ugly things like long "../" chains to get to the root |
| 530 // and then go back down. |
| 531 return split(rel).length > split(path).length ? path : rel; |
| 532 } |
| 533 |
| 534 ParsedPath _parse(String path) => new ParsedPath.parse(path, style); |
| 535 } |
| 536 |
| 537 /// Validates that there are no non-null arguments following a null one and |
| 538 /// throws an appropriate [ArgumentError] on failure. |
| 539 _validateArgList(String method, List<String> args) { |
| 540 for (var i = 1; i < args.length; i++) { |
| 541 // Ignore nulls hanging off the end. |
| 542 if (args[i] == null || args[i - 1] != null) continue; |
| 543 |
| 544 var numArgs; |
| 545 for (numArgs = args.length; numArgs >= 1; numArgs--) { |
| 546 if (args[numArgs - 1] != null) break; |
| 547 } |
| 548 |
| 549 // Show the arguments. |
| 550 var message = new StringBuffer(); |
| 551 message.write("$method("); |
| 552 message.write(args |
| 553 .take(numArgs) |
| 554 .map((arg) => arg == null ? "null" : '"$arg"') |
| 555 .join(", ")); |
| 556 message.write("): part ${i - 1} was null, but part $i was not."); |
| 557 throw new ArgumentError(message.toString()); |
| 558 } |
| 559 } |
| OLD | NEW |