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 library legacy_path; | 5 library legacy_path; |
6 | 6 |
7 import 'dart:io'; | 7 import 'dart:io'; |
8 import 'dart:math'; | 8 import 'dart:math'; |
9 | 9 |
10 // TODO: Remove this class, and use the URI class for all path manipulation. | 10 // TODO: Remove this class, and use the URI class for all path manipulation. |
11 class Path { | 11 class Path { |
12 final String _path; | 12 final String _path; |
13 final bool isWindowsShare; | 13 final bool isWindowsShare; |
14 | 14 |
15 Path(String source) | 15 Path(String source) |
16 : _path = _clean(source), isWindowsShare = _isWindowsShare(source); | 16 : _path = _clean(source), |
| 17 isWindowsShare = _isWindowsShare(source); |
17 | 18 |
18 Path.raw(String source) : _path = source, isWindowsShare = false; | 19 Path.raw(String source) |
| 20 : _path = source, |
| 21 isWindowsShare = false; |
19 | 22 |
20 Path._internal(String this._path, bool this.isWindowsShare); | 23 Path._internal(String this._path, bool this.isWindowsShare); |
21 | 24 |
22 static String _clean(String source) { | 25 static String _clean(String source) { |
23 if (Platform.operatingSystem == 'windows') return _cleanWindows(source); | 26 if (Platform.operatingSystem == 'windows') return _cleanWindows(source); |
24 // Remove trailing slash from directories: | 27 // Remove trailing slash from directories: |
25 if (source.length > 1 && source.endsWith('/')) { | 28 if (source.length > 1 && source.endsWith('/')) { |
26 return source.substring(0, source.length - 1); | 29 return source.substring(0, source.length - 1); |
27 } | 30 } |
28 return source; | 31 return source; |
(...skipping 22 matching lines...) Expand all Loading... |
51 bool get hasTrailingSeparator => _path.endsWith('/'); | 54 bool get hasTrailingSeparator => _path.endsWith('/'); |
52 | 55 |
53 String toString() => _path; | 56 String toString() => _path; |
54 | 57 |
55 Path relativeTo(Path base) { | 58 Path relativeTo(Path base) { |
56 // Returns a path "relative" such that | 59 // Returns a path "relative" such that |
57 // base.join(relative) == this.canonicalize. | 60 // base.join(relative) == this.canonicalize. |
58 // Throws exception if an impossible case is reached. | 61 // Throws exception if an impossible case is reached. |
59 if (base.isAbsolute != isAbsolute || | 62 if (base.isAbsolute != isAbsolute || |
60 base.isWindowsShare != isWindowsShare) { | 63 base.isWindowsShare != isWindowsShare) { |
61 throw new ArgumentError( | 64 throw new ArgumentError("Invalid case of Path.relativeTo(base):\n" |
62 "Invalid case of Path.relativeTo(base):\n" | |
63 " Path and base must both be relative, or both absolute.\n" | 65 " Path and base must both be relative, or both absolute.\n" |
64 " Arguments: $_path.relativeTo($base)"); | 66 " Arguments: $_path.relativeTo($base)"); |
65 } | 67 } |
66 | 68 |
67 var basePath = base.toString(); | 69 var basePath = base.toString(); |
68 // Handle drive letters specially on Windows. | 70 // Handle drive letters specially on Windows. |
69 if (base.isAbsolute && Platform.operatingSystem == 'windows') { | 71 if (base.isAbsolute && Platform.operatingSystem == 'windows') { |
70 bool baseHasDrive = | 72 bool baseHasDrive = |
71 basePath.length >= 4 && basePath[2] == ':' && basePath[3] == '/'; | 73 basePath.length >= 4 && basePath[2] == ':' && basePath[3] == '/'; |
72 bool pathHasDrive = | 74 bool pathHasDrive = |
73 _path.length >= 4 && _path[2] == ':' && _path[3] == '/'; | 75 _path.length >= 4 && _path[2] == ':' && _path[3] == '/'; |
74 if (baseHasDrive && pathHasDrive) { | 76 if (baseHasDrive && pathHasDrive) { |
75 int baseDrive = basePath.codeUnitAt(1) | 32; // Convert to uppercase. | 77 int baseDrive = basePath.codeUnitAt(1) | 32; // Convert to uppercase. |
76 if (baseDrive >= 'a'.codeUnitAt(0) && | 78 if (baseDrive >= 'a'.codeUnitAt(0) && |
77 baseDrive <= 'z'.codeUnitAt(0) && | 79 baseDrive <= 'z'.codeUnitAt(0) && |
78 baseDrive == (_path.codeUnitAt(1) | 32)) { | 80 baseDrive == (_path.codeUnitAt(1) | 32)) { |
79 if(basePath[1] != _path[1]) { | 81 if (basePath[1] != _path[1]) { |
80 // Replace the drive letter in basePath with that from _path. | 82 // Replace the drive letter in basePath with that from _path. |
81 basePath = '/${_path[1]}:/${basePath.substring(4)}'; | 83 basePath = '/${_path[1]}:/${basePath.substring(4)}'; |
82 base = new Path(basePath); | 84 base = new Path(basePath); |
83 } | 85 } |
84 } else { | 86 } else { |
85 throw new ArgumentError( | 87 throw new ArgumentError("Invalid case of Path.relativeTo(base):\n" |
86 "Invalid case of Path.relativeTo(base):\n" | |
87 " Base path and target path are on different Windows drives.\n" | 88 " Base path and target path are on different Windows drives.\n" |
88 " Arguments: $_path.relativeTo($base)"); | 89 " Arguments: $_path.relativeTo($base)"); |
89 } | 90 } |
90 } else if (baseHasDrive != pathHasDrive) { | 91 } else if (baseHasDrive != pathHasDrive) { |
91 throw new ArgumentError( | 92 throw new ArgumentError("Invalid case of Path.relativeTo(base):\n" |
92 "Invalid case of Path.relativeTo(base):\n" | |
93 " Base path must start with a drive letter if and " | 93 " Base path must start with a drive letter if and " |
94 "only if target path does.\n" | 94 "only if target path does.\n" |
95 " Arguments: $_path.relativeTo($base)"); | 95 " Arguments: $_path.relativeTo($base)"); |
96 } | 96 } |
97 | |
98 } | 97 } |
99 if (_path.startsWith(basePath)) { | 98 if (_path.startsWith(basePath)) { |
100 if (_path == basePath) return new Path('.'); | 99 if (_path == basePath) return new Path('.'); |
101 // There must be a '/' at the end of the match, or immediately after. | 100 // There must be a '/' at the end of the match, or immediately after. |
102 int matchEnd = basePath.length; | 101 int matchEnd = basePath.length; |
103 if (_path[matchEnd - 1] == '/' || _path[matchEnd] == '/') { | 102 if (_path[matchEnd - 1] == '/' || _path[matchEnd] == '/') { |
104 // Drop any extra '/' characters at matchEnd | 103 // Drop any extra '/' characters at matchEnd |
105 while (matchEnd < _path.length && _path[matchEnd] == '/') { | 104 while (matchEnd < _path.length && _path[matchEnd] == '/') { |
106 matchEnd++; | 105 matchEnd++; |
107 } | 106 } |
(...skipping 10 matching lines...) Expand all Loading... |
118 pathSegments = []; | 117 pathSegments = []; |
119 } | 118 } |
120 int common = 0; | 119 int common = 0; |
121 int length = min(pathSegments.length, baseSegments.length); | 120 int length = min(pathSegments.length, baseSegments.length); |
122 while (common < length && pathSegments[common] == baseSegments[common]) { | 121 while (common < length && pathSegments[common] == baseSegments[common]) { |
123 common++; | 122 common++; |
124 } | 123 } |
125 final segments = new List<String>(); | 124 final segments = new List<String>(); |
126 | 125 |
127 if (common < baseSegments.length && baseSegments[common] == '..') { | 126 if (common < baseSegments.length && baseSegments[common] == '..') { |
128 throw new ArgumentError( | 127 throw new ArgumentError("Invalid case of Path.relativeTo(base):\n" |
129 "Invalid case of Path.relativeTo(base):\n" | |
130 " Base path has more '..'s than path does.\n" | 128 " Base path has more '..'s than path does.\n" |
131 " Arguments: $_path.relativeTo($base)"); | 129 " Arguments: $_path.relativeTo($base)"); |
132 } | 130 } |
133 for (int i = common; i < baseSegments.length; i++) { | 131 for (int i = common; i < baseSegments.length; i++) { |
134 segments.add('..'); | 132 segments.add('..'); |
135 } | 133 } |
136 for (int i = common; i < pathSegments.length; i++) { | 134 for (int i = common; i < pathSegments.length; i++) { |
137 segments.add('${pathSegments[i]}'); | 135 segments.add('${pathSegments[i]}'); |
138 } | 136 } |
139 if (segments.isEmpty) { | 137 if (segments.isEmpty) { |
140 segments.add('.'); | 138 segments.add('.'); |
141 } | 139 } |
142 if (hasTrailingSeparator) { | 140 if (hasTrailingSeparator) { |
143 segments.add(''); | 141 segments.add(''); |
144 } | 142 } |
145 return new Path(segments.join('/')); | 143 return new Path(segments.join('/')); |
146 } | 144 } |
147 | 145 |
148 | |
149 Path join(Path further) { | 146 Path join(Path further) { |
150 if (further.isAbsolute) { | 147 if (further.isAbsolute) { |
151 throw new ArgumentError( | 148 throw new ArgumentError( |
152 "Path.join called with absolute Path as argument."); | 149 "Path.join called with absolute Path as argument."); |
153 } | 150 } |
154 if (isEmpty) { | 151 if (isEmpty) { |
155 return further.canonicalize(); | 152 return further.canonicalize(); |
156 } | 153 } |
157 if (hasTrailingSeparator) { | 154 if (hasTrailingSeparator) { |
158 var joined = new Path._internal('$_path${further}', isWindowsShare); | 155 var joined = new Path._internal('$_path${further}', isWindowsShare); |
159 return joined.canonicalize(); | 156 return joined.canonicalize(); |
160 } | 157 } |
161 var joined = new Path._internal('$_path/${further}', isWindowsShare); | 158 var joined = new Path._internal('$_path/${further}', isWindowsShare); |
162 return joined.canonicalize(); | 159 return joined.canonicalize(); |
163 } | 160 } |
164 | 161 |
165 // Note: The URI RFC names for canonicalize, join, and relativeTo | 162 // Note: The URI RFC names for canonicalize, join, and relativeTo |
166 // are normalize, resolve, and relativize. But resolve and relativize | 163 // are normalize, resolve, and relativize. But resolve and relativize |
167 // drop the last segment of the base path (the filename), on URIs. | 164 // drop the last segment of the base path (the filename), on URIs. |
168 Path canonicalize() { | 165 Path canonicalize() { |
169 if (isCanonical) return this; | 166 if (isCanonical) return this; |
170 return makeCanonical(); | 167 return makeCanonical(); |
171 } | 168 } |
172 | 169 |
173 bool get isCanonical { | 170 bool get isCanonical { |
174 // Contains no consecutive path separators. | 171 // Contains no consecutive path separators. |
175 // Contains no segments that are '.'. | 172 // Contains no segments that are '.'. |
176 // Absolute paths have no segments that are '..'. | 173 // Absolute paths have no segments that are '..'. |
177 // All '..' segments of a relative path are at the beginning. | 174 // All '..' segments of a relative path are at the beginning. |
178 if (isEmpty) return false; // The canonical form of '' is '.'. | 175 if (isEmpty) return false; // The canonical form of '' is '.'. |
179 if (_path == '.') return true; | 176 if (_path == '.') return true; |
180 List segs = _path.split('/'); // Don't mask the getter 'segments'. | 177 List segs = _path.split('/'); // Don't mask the getter 'segments'. |
181 if (segs[0] == '') { // Absolute path | 178 if (segs[0] == '') { |
182 segs[0] = null; // Faster than removeRange(). | 179 // Absolute path |
183 } else { // A canonical relative path may start with .. segments. | 180 segs[0] = null; // Faster than removeRange(). |
184 for (int pos = 0; | 181 } else { |
185 pos < segs.length && segs[pos] == '..'; | 182 // A canonical relative path may start with .. segments. |
186 ++pos) { | 183 for (int pos = 0; pos < segs.length && segs[pos] == '..'; ++pos) { |
187 segs[pos] = null; | 184 segs[pos] = null; |
188 } | 185 } |
189 } | 186 } |
190 if (segs.last == '') segs.removeLast(); // Path ends with /. | 187 if (segs.last == '') segs.removeLast(); // Path ends with /. |
191 // No remaining segments can be ., .., or empty. | 188 // No remaining segments can be ., .., or empty. |
192 return !segs.any((s) => s == '' || s == '.' || s == '..'); | 189 return !segs.any((s) => s == '' || s == '.' || s == '..'); |
193 } | 190 } |
194 | 191 |
195 Path makeCanonical() { | 192 Path makeCanonical() { |
196 bool isAbs = isAbsolute; | 193 bool isAbs = isAbsolute; |
197 List segs = segments(); | 194 List segs = segments(); |
198 String drive; | 195 String drive; |
199 if (isAbs && | 196 if (isAbs && !segs.isEmpty && segs[0].length == 2 && segs[0][1] == ':') { |
200 !segs.isEmpty && | |
201 segs[0].length == 2 && | |
202 segs[0][1] == ':') { | |
203 drive = segs[0]; | 197 drive = segs[0]; |
204 segs.removeRange(0, 1); | 198 segs.removeRange(0, 1); |
205 } | 199 } |
206 List newSegs = []; | 200 List newSegs = []; |
207 for (String segment in segs) { | 201 for (String segment in segs) { |
208 switch (segment) { | 202 switch (segment) { |
209 case '..': | 203 case '..': |
210 // Absolute paths drop leading .. markers, including after a drive. | 204 // Absolute paths drop leading .. markers, including after a drive. |
211 if (newSegs.isEmpty) { | 205 if (newSegs.isEmpty) { |
212 if (isAbs) { | 206 if (isAbs) { |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
308 while (pos > 0 && _path[pos - 1] == '/') --pos; | 302 while (pos > 0 && _path[pos - 1] == '/') --pos; |
309 var dirPath = (pos > 0) ? _path.substring(0, pos) : '/'; | 303 var dirPath = (pos > 0) ? _path.substring(0, pos) : '/'; |
310 return new Path._internal(dirPath, isWindowsShare); | 304 return new Path._internal(dirPath, isWindowsShare); |
311 } | 305 } |
312 | 306 |
313 String get filename { | 307 String get filename { |
314 int pos = _path.lastIndexOf('/'); | 308 int pos = _path.lastIndexOf('/'); |
315 return _path.substring(pos + 1); | 309 return _path.substring(pos + 1); |
316 } | 310 } |
317 } | 311 } |
OLD | NEW |