| 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 part of utils; |  | 
| 6 |  | 
| 7 class Path { |  | 
| 8   final String _path; |  | 
| 9   final bool isWindowsShare; |  | 
| 10 |  | 
| 11   Path(String source) |  | 
| 12       : _path = _clean(source), isWindowsShare = _isWindowsShare(source); |  | 
| 13 |  | 
| 14   Path.raw(String source) : _path = source, isWindowsShare = false; |  | 
| 15 |  | 
| 16   Path._internal(String this._path, bool this.isWindowsShare); |  | 
| 17 |  | 
| 18   static String _clean(String source) { |  | 
| 19     if (Platform.operatingSystem == 'windows') return _cleanWindows(source); |  | 
| 20     // Remove trailing slash from directories: |  | 
| 21     if (source.length > 1 && source.endsWith('/')) { |  | 
| 22       return source.substring(0, source.length - 1); |  | 
| 23     } |  | 
| 24     return source; |  | 
| 25   } |  | 
| 26 |  | 
| 27   static String _cleanWindows(String source) { |  | 
| 28     // Change \ to /. |  | 
| 29     var clean = source.replaceAll('\\', '/'); |  | 
| 30     // Add / before intial [Drive letter]: |  | 
| 31     if (clean.length >= 2 && clean[1] == ':') { |  | 
| 32       clean = '/$clean'; |  | 
| 33     } |  | 
| 34     if (_isWindowsShare(source)) { |  | 
| 35       return clean.substring(1, clean.length); |  | 
| 36     } |  | 
| 37     return clean; |  | 
| 38   } |  | 
| 39 |  | 
| 40   static bool _isWindowsShare(String source) { |  | 
| 41     return Platform.operatingSystem == 'windows' && source.startsWith('\\\\'); |  | 
| 42   } |  | 
| 43 |  | 
| 44   int get hashCode => _path.hashCode; |  | 
| 45   bool get isEmpty => _path.isEmpty; |  | 
| 46   bool get isAbsolute => _path.startsWith('/'); |  | 
| 47   bool get hasTrailingSeparator => _path.endsWith('/'); |  | 
| 48 |  | 
| 49   String toString() => _path; |  | 
| 50 |  | 
| 51   Path relativeTo(Path base) { |  | 
| 52     // Returns a path "relative" such that |  | 
| 53     // base.join(relative) == this.canonicalize. |  | 
| 54     // Throws exception if an impossible case is reached. |  | 
| 55     if (base.isAbsolute != isAbsolute || |  | 
| 56         base.isWindowsShare != isWindowsShare) { |  | 
| 57       throw new ArgumentError( |  | 
| 58           "Invalid case of Path.relativeTo(base):\n" |  | 
| 59           "  Path and base must both be relative, or both absolute.\n" |  | 
| 60           "  Arguments: $_path.relativeTo($base)"); |  | 
| 61     } |  | 
| 62 |  | 
| 63     var basePath = base.toString(); |  | 
| 64     // Handle drive letters specially on Windows. |  | 
| 65     if (base.isAbsolute && Platform.operatingSystem == 'windows') { |  | 
| 66       bool baseHasDrive = |  | 
| 67           basePath.length >= 4 && basePath[2] == ':' && basePath[3] == '/'; |  | 
| 68       bool pathHasDrive = |  | 
| 69           _path.length >= 4 && _path[2] == ':' && _path[3] == '/'; |  | 
| 70       if (baseHasDrive && pathHasDrive) { |  | 
| 71         int baseDrive = basePath.codeUnitAt(1) | 32;  // Convert to uppercase. |  | 
| 72         if (baseDrive >= 'a'.codeUnitAt(0) && |  | 
| 73             baseDrive <= 'z'.codeUnitAt(0) && |  | 
| 74             baseDrive == (_path.codeUnitAt(1) | 32)) { |  | 
| 75           if(basePath[1] != _path[1]) { |  | 
| 76             // Replace the drive letter in basePath with that from _path. |  | 
| 77             basePath = '/${_path[1]}:/${basePath.substring(4)}'; |  | 
| 78             base = new Path(basePath); |  | 
| 79           } |  | 
| 80         } else { |  | 
| 81           throw new ArgumentError( |  | 
| 82               "Invalid case of Path.relativeTo(base):\n" |  | 
| 83               "  Base path and target path are on different Windows drives.\n" |  | 
| 84               "  Arguments: $_path.relativeTo($base)"); |  | 
| 85         } |  | 
| 86       } else if (baseHasDrive != pathHasDrive) { |  | 
| 87         throw new ArgumentError( |  | 
| 88             "Invalid case of Path.relativeTo(base):\n" |  | 
| 89             "  Base path must start with a drive letter if and " |  | 
| 90             "only if target path does.\n" |  | 
| 91             "  Arguments: $_path.relativeTo($base)"); |  | 
| 92       } |  | 
| 93 |  | 
| 94     } |  | 
| 95     if (_path.startsWith(basePath)) { |  | 
| 96       if (_path == basePath) return new Path('.'); |  | 
| 97       // There must be a '/' at the end of the match, or immediately after. |  | 
| 98       int matchEnd = basePath.length; |  | 
| 99       if (_path[matchEnd - 1] == '/' || _path[matchEnd] == '/') { |  | 
| 100         // Drop any extra '/' characters at matchEnd |  | 
| 101         while (matchEnd < _path.length && _path[matchEnd] == '/') { |  | 
| 102           matchEnd++; |  | 
| 103         } |  | 
| 104         return new Path(_path.substring(matchEnd)).canonicalize(); |  | 
| 105       } |  | 
| 106     } |  | 
| 107 |  | 
| 108     List<String> baseSegments = base.canonicalize().segments(); |  | 
| 109     List<String> pathSegments = canonicalize().segments(); |  | 
| 110     if (baseSegments.length == 1 && baseSegments[0] == '.') { |  | 
| 111       baseSegments = []; |  | 
| 112     } |  | 
| 113     if (pathSegments.length == 1 && pathSegments[0] == '.') { |  | 
| 114       pathSegments = []; |  | 
| 115     } |  | 
| 116     int common = 0; |  | 
| 117     int length = min(pathSegments.length, baseSegments.length); |  | 
| 118     while (common < length && pathSegments[common] == baseSegments[common]) { |  | 
| 119       common++; |  | 
| 120     } |  | 
| 121     final segments = new List<String>(); |  | 
| 122 |  | 
| 123     if (common < baseSegments.length && baseSegments[common] == '..') { |  | 
| 124       throw new ArgumentError( |  | 
| 125           "Invalid case of Path.relativeTo(base):\n" |  | 
| 126           "  Base path has more '..'s than path does.\n" |  | 
| 127           "  Arguments: $_path.relativeTo($base)"); |  | 
| 128     } |  | 
| 129     for (int i = common; i < baseSegments.length; i++) { |  | 
| 130       segments.add('..'); |  | 
| 131     } |  | 
| 132     for (int i = common; i < pathSegments.length; i++) { |  | 
| 133       segments.add('${pathSegments[i]}'); |  | 
| 134     } |  | 
| 135     if (segments.isEmpty) { |  | 
| 136       segments.add('.'); |  | 
| 137     } |  | 
| 138     if (hasTrailingSeparator) { |  | 
| 139         segments.add(''); |  | 
| 140     } |  | 
| 141     return new Path(segments.join('/')); |  | 
| 142   } |  | 
| 143 |  | 
| 144 |  | 
| 145   Path join(Path further) { |  | 
| 146     if (further.isAbsolute) { |  | 
| 147       throw new ArgumentError( |  | 
| 148           "Path.join called with absolute Path as argument."); |  | 
| 149     } |  | 
| 150     if (isEmpty) { |  | 
| 151       return further.canonicalize(); |  | 
| 152     } |  | 
| 153     if (hasTrailingSeparator) { |  | 
| 154       var joined = new Path._internal('$_path${further}', isWindowsShare); |  | 
| 155       return joined.canonicalize(); |  | 
| 156     } |  | 
| 157     var joined = new Path._internal('$_path/${further}', isWindowsShare); |  | 
| 158     return joined.canonicalize(); |  | 
| 159   } |  | 
| 160 |  | 
| 161   // Note: The URI RFC names for canonicalize, join, and relativeTo |  | 
| 162   // are normalize, resolve, and relativize.  But resolve and relativize |  | 
| 163   // drop the last segment of the base path (the filename), on URIs. |  | 
| 164   Path canonicalize() { |  | 
| 165     if (isCanonical) return this; |  | 
| 166     return makeCanonical(); |  | 
| 167   } |  | 
| 168 |  | 
| 169   bool get isCanonical { |  | 
| 170     // Contains no consecutive path separators. |  | 
| 171     // Contains no segments that are '.'. |  | 
| 172     // Absolute paths have no segments that are '..'. |  | 
| 173     // All '..' segments of a relative path are at the beginning. |  | 
| 174     if (isEmpty) return false;  // The canonical form of '' is '.'. |  | 
| 175     if (_path == '.') return true; |  | 
| 176     List segs = _path.split('/');  // Don't mask the getter 'segments'. |  | 
| 177     if (segs[0] == '') {  // Absolute path |  | 
| 178       segs[0] = null;  // Faster than removeRange(). |  | 
| 179     } else {  // A canonical relative path may start with .. segments. |  | 
| 180       for (int pos = 0; |  | 
| 181            pos < segs.length && segs[pos] == '..'; |  | 
| 182            ++pos) { |  | 
| 183         segs[pos] = null; |  | 
| 184       } |  | 
| 185     } |  | 
| 186     if (segs.last == '') segs.removeLast();  // Path ends with /. |  | 
| 187     // No remaining segments can be ., .., or empty. |  | 
| 188     return !segs.any((s) => s == '' || s == '.' || s == '..'); |  | 
| 189   } |  | 
| 190 |  | 
| 191   Path makeCanonical() { |  | 
| 192     bool isAbs = isAbsolute; |  | 
| 193     List segs = segments(); |  | 
| 194     String drive; |  | 
| 195     if (isAbs && |  | 
| 196         !segs.isEmpty && |  | 
| 197         segs[0].length == 2 && |  | 
| 198         segs[0][1] == ':') { |  | 
| 199       drive = segs[0]; |  | 
| 200       segs.removeRange(0, 1); |  | 
| 201     } |  | 
| 202     List newSegs = []; |  | 
| 203     for (String segment in segs) { |  | 
| 204       switch (segment) { |  | 
| 205         case '..': |  | 
| 206           // Absolute paths drop leading .. markers, including after a drive. |  | 
| 207           if (newSegs.isEmpty) { |  | 
| 208             if (isAbs) { |  | 
| 209               // Do nothing: drop the segment. |  | 
| 210             } else { |  | 
| 211               newSegs.add('..'); |  | 
| 212             } |  | 
| 213           } else if (newSegs.last == '..') { |  | 
| 214             newSegs.add('..'); |  | 
| 215           } else { |  | 
| 216             newSegs.removeLast(); |  | 
| 217           } |  | 
| 218           break; |  | 
| 219         case '.': |  | 
| 220         case '': |  | 
| 221           // Do nothing - drop the segment. |  | 
| 222           break; |  | 
| 223         default: |  | 
| 224           newSegs.add(segment); |  | 
| 225           break; |  | 
| 226       } |  | 
| 227     } |  | 
| 228 |  | 
| 229     List segmentsToJoin = []; |  | 
| 230     if (isAbs) { |  | 
| 231       segmentsToJoin.add(''); |  | 
| 232       if (drive != null) { |  | 
| 233         segmentsToJoin.add(drive); |  | 
| 234       } |  | 
| 235     } |  | 
| 236 |  | 
| 237     if (newSegs.isEmpty) { |  | 
| 238       if (isAbs) { |  | 
| 239         segmentsToJoin.add(''); |  | 
| 240       } else { |  | 
| 241         segmentsToJoin.add('.'); |  | 
| 242       } |  | 
| 243     } else { |  | 
| 244       segmentsToJoin.addAll(newSegs); |  | 
| 245       if (hasTrailingSeparator) { |  | 
| 246         segmentsToJoin.add(''); |  | 
| 247       } |  | 
| 248     } |  | 
| 249     return new Path._internal(segmentsToJoin.join('/'), isWindowsShare); |  | 
| 250   } |  | 
| 251 |  | 
| 252   String toNativePath() { |  | 
| 253     if (isEmpty) return '.'; |  | 
| 254     if (Platform.operatingSystem == 'windows') { |  | 
| 255       String nativePath = _path; |  | 
| 256       // Drop '/' before a drive letter. |  | 
| 257       if (nativePath.length >= 3 && |  | 
| 258           nativePath.startsWith('/') && |  | 
| 259           nativePath[2] == ':') { |  | 
| 260         nativePath = nativePath.substring(1); |  | 
| 261       } |  | 
| 262       nativePath = nativePath.replaceAll('/', '\\'); |  | 
| 263       if (isWindowsShare) { |  | 
| 264         return '\\$nativePath'; |  | 
| 265       } |  | 
| 266       return nativePath; |  | 
| 267     } |  | 
| 268     return _path; |  | 
| 269   } |  | 
| 270 |  | 
| 271   List<String> segments() { |  | 
| 272     List result = _path.split('/'); |  | 
| 273     if (isAbsolute) result.removeRange(0, 1); |  | 
| 274     if (hasTrailingSeparator) result.removeLast(); |  | 
| 275     return result; |  | 
| 276   } |  | 
| 277 |  | 
| 278   Path append(String finalSegment) { |  | 
| 279     if (isEmpty) { |  | 
| 280       return new Path._internal(finalSegment, isWindowsShare); |  | 
| 281     } else if (hasTrailingSeparator) { |  | 
| 282       return new Path._internal('$_path$finalSegment', isWindowsShare); |  | 
| 283     } else { |  | 
| 284       return new Path._internal('$_path/$finalSegment', isWindowsShare); |  | 
| 285     } |  | 
| 286   } |  | 
| 287 |  | 
| 288   String get filenameWithoutExtension { |  | 
| 289     var name = filename; |  | 
| 290     if (name == '.' || name == '..') return name; |  | 
| 291     int pos = name.lastIndexOf('.'); |  | 
| 292     return (pos < 0) ? name : name.substring(0, pos); |  | 
| 293   } |  | 
| 294 |  | 
| 295   String get extension { |  | 
| 296     var name = filename; |  | 
| 297     int pos = name.lastIndexOf('.'); |  | 
| 298     return (pos < 0) ? '' : name.substring(pos + 1); |  | 
| 299   } |  | 
| 300 |  | 
| 301   Path get directoryPath { |  | 
| 302     int pos = _path.lastIndexOf('/'); |  | 
| 303     if (pos < 0) return new Path(''); |  | 
| 304     while (pos > 0 && _path[pos - 1] == '/') --pos; |  | 
| 305     var dirPath = (pos > 0) ? _path.substring(0, pos) : '/'; |  | 
| 306     return new Path._internal(dirPath, isWindowsShare); |  | 
| 307   } |  | 
| 308 |  | 
| 309   String get filename { |  | 
| 310     int pos = _path.lastIndexOf('/'); |  | 
| 311     return _path.substring(pos + 1); |  | 
| 312   } |  | 
| 313 } |  | 
| OLD | NEW | 
|---|