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 class _Path implements Path { | |
6 final String _path; | |
7 | |
8 _Path(String source) : _path = source; | |
9 _Path.fromNative(String source) : _path = _clean(source); | |
10 | |
11 int get hashCode => _path.hashCode; | |
12 | |
13 static String _clean(String source) { | |
14 switch (Platform.operatingSystem) { | |
15 case 'windows': | |
16 return _cleanWindows(source); | |
17 default: | |
18 return source; | |
19 } | |
20 } | |
21 | |
22 static String _cleanWindows(source) { | |
23 // Change \ to /. | |
24 var clean = source.replaceAll('\\', '/'); | |
25 // Add / before intial [Drive letter]: | |
26 if (clean.length >= 2 && clean[1] == ':') { | |
27 clean = '/$clean'; | |
28 } | |
29 return clean; | |
30 } | |
31 | |
32 bool get isEmpty => _path.isEmpty; | |
33 bool get isAbsolute => _path.startsWith('/'); | |
34 bool get hasTrailingSeparator => _path.endsWith('/'); | |
35 | |
36 String toString() => _path; | |
37 | |
38 Path relativeTo(Path base) { | |
39 // Throws exception if an unimplemented or impossible case is reached. | |
40 // Returns a path "relative" such that | |
41 // base.join(relative) == this.canonicalize. | |
42 // Throws an exception if no such path exists, or the case is not | |
43 // implemented yet. | |
44 var basePath = base.toString(); | |
45 if (base.isAbsolute && _path.startsWith(basePath)) { | |
46 if (_path == basePath) return new Path('.'); | |
47 if (base.hasTrailingSeparator) { | |
48 return new Path(_path.substring(basePath.length)); | |
49 } | |
50 if (_path[basePath.length] == '/') { | |
51 return new Path(_path.substring(basePath.length + 1)); | |
52 } | |
53 } else if (base.isAbsolute && isAbsolute) { | |
54 List<String> baseSegments = base.canonicalize().segments(); | |
55 List<String> pathSegments = canonicalize().segments(); | |
56 int common = 0; | |
57 int length = min(pathSegments.length, baseSegments.length); | |
58 while (common < length && pathSegments[common] == baseSegments[common]) { | |
59 common++; | |
60 } | |
61 final sb = new StringBuffer(); | |
62 | |
63 for (int i = common + 1; i < baseSegments.length; i++) { | |
64 sb.add('../'); | |
65 } | |
66 if (base.hasTrailingSeparator) { | |
67 sb.add('../'); | |
68 } | |
69 for (int i = common; i < pathSegments.length - 1; i++) { | |
70 sb.add('${pathSegments[i]}/'); | |
71 } | |
72 sb.add('${pathSegments.last}'); | |
73 if (hasTrailingSeparator) { | |
74 sb.add('/'); | |
75 } | |
76 return new Path(sb.toString()); | |
77 } | |
78 throw new NotImplementedException( | |
79 "Unimplemented case of Path.relativeTo(base):\n" | |
80 " Only absolute paths are handled at present.\n" | |
81 " Arguments: $_path.relativeTo($base)"); | |
82 } | |
83 | |
84 Path join(Path further) { | |
85 if (further.isAbsolute) { | |
86 throw new ArgumentError( | |
87 "Path.join called with absolute Path as argument."); | |
88 } | |
89 if (isEmpty) { | |
90 return further.canonicalize(); | |
91 } | |
92 if (hasTrailingSeparator) { | |
93 return new Path('$_path${further}').canonicalize(); | |
94 } | |
95 return new Path('$_path/${further}').canonicalize(); | |
96 } | |
97 | |
98 // Note: The URI RFC names for these operations are normalize, resolve, and | |
99 // relativize. | |
100 Path canonicalize() { | |
101 if (isCanonical) return this; | |
102 return makeCanonical(); | |
103 } | |
104 | |
105 bool get isCanonical { | |
106 // Contains no consecutive path separators. | |
107 // Contains no segments that are '.'. | |
108 // Absolute paths have no segments that are '..'. | |
109 // All '..' segments of a relative path are at the beginning. | |
110 if (isEmpty) return false; // The canonical form of '' is '.'. | |
111 if (_path == '.') return true; | |
112 List segs = _path.split('/'); // Don't mask the getter 'segments'. | |
113 if (segs[0] == '') { // Absolute path | |
114 segs[0] = null; // Faster than removeRange(). | |
115 } else { // A canonical relative path may start with .. segments. | |
116 for (int pos = 0; | |
117 pos < segs.length && segs[pos] == '..'; | |
118 ++pos) { | |
119 segs[pos] = null; | |
120 } | |
121 } | |
122 if (segs.last == '') segs.removeLast(); // Path ends with /. | |
123 // No remaining segments can be ., .., or empty. | |
124 return !segs.some((s) => s == '' || s == '.' || s == '..'); | |
125 } | |
126 | |
127 Path makeCanonical() { | |
128 bool isAbs = isAbsolute; | |
129 List segs = segments(); | |
130 String drive; | |
131 if (isAbs && | |
132 !segs.isEmpty && | |
133 segs[0].length == 2 && | |
134 segs[0][1] == ':') { | |
135 drive = segs[0]; | |
136 segs.removeRange(0, 1); | |
137 } | |
138 List newSegs = []; | |
139 for (String segment in segs) { | |
140 switch (segment) { | |
141 case '..': | |
142 // Absolute paths drop leading .. markers, including after a drive. | |
143 if (newSegs.isEmpty) { | |
144 if (isAbs) { | |
145 // Do nothing: drop the segment. | |
146 } else { | |
147 newSegs.add('..'); | |
148 } | |
149 } else if (newSegs.last == '..') { | |
150 newSegs.add('..'); | |
151 } else { | |
152 newSegs.removeLast(); | |
153 } | |
154 break; | |
155 case '.': | |
156 case '': | |
157 // Do nothing - drop the segment. | |
158 break; | |
159 default: | |
160 newSegs.add(segment); | |
161 break; | |
162 } | |
163 } | |
164 | |
165 List segmentsToJoin = []; | |
166 if (isAbs) { | |
167 segmentsToJoin.add(''); | |
168 if (drive != null) { | |
169 segmentsToJoin.add(drive); | |
170 } | |
171 } | |
172 | |
173 if (newSegs.isEmpty) { | |
174 if (isAbs) { | |
175 segmentsToJoin.add(''); | |
176 } else { | |
177 segmentsToJoin.add('.'); | |
178 } | |
179 } else { | |
180 segmentsToJoin.addAll(newSegs); | |
181 if (hasTrailingSeparator) { | |
182 segmentsToJoin.add(''); | |
183 } | |
184 } | |
185 return new Path(Strings.join(segmentsToJoin, '/')); | |
186 } | |
187 | |
188 String toNativePath() { | |
189 if (Platform.operatingSystem == 'windows') { | |
190 String nativePath = _path; | |
191 // Drop '/' before a drive letter. | |
192 if (nativePath.length > 3 && | |
193 nativePath.startsWith('/') && | |
194 nativePath[2] == ':') { | |
195 nativePath = nativePath.substring(1); | |
196 } | |
197 nativePath = nativePath.replaceAll('/', '\\'); | |
198 return nativePath; | |
199 } | |
200 return _path; | |
201 } | |
202 | |
203 List<String> segments() { | |
204 List result = _path.split('/'); | |
205 if (isAbsolute) result.removeRange(0, 1); | |
206 if (hasTrailingSeparator) result.removeLast(); | |
207 return result; | |
208 } | |
209 | |
210 Path append(String finalSegment) { | |
211 if (isEmpty) { | |
212 return new Path(finalSegment); | |
213 } else if (hasTrailingSeparator) { | |
214 return new Path('$_path$finalSegment'); | |
215 } else { | |
216 return new Path('$_path/$finalSegment'); | |
217 } | |
218 } | |
219 | |
220 String get filenameWithoutExtension { | |
221 var name = filename; | |
222 if (name == '.' || name == '..') return name; | |
223 int pos = name.lastIndexOf('.'); | |
224 return (pos < 0) ? name : name.substring(0, pos); | |
225 } | |
226 | |
227 String get extension { | |
228 var name = filename; | |
229 int pos = name.lastIndexOf('.'); | |
230 return (pos < 0) ? '' : name.substring(pos + 1); | |
231 } | |
232 | |
233 Path get directoryPath { | |
234 int pos = _path.lastIndexOf('/'); | |
235 if (pos < 0) return new Path(''); | |
236 while (pos > 0 && _path[pos - 1] == '/') --pos; | |
237 return new Path((pos > 0) ? _path.substring(0, pos) : '/'); | |
238 } | |
239 | |
240 String get filename { | |
241 int pos = _path.lastIndexOf('/'); | |
242 return _path.substring(pos + 1); | |
243 } | |
244 } | |
OLD | NEW |