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 |