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