Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(437)

Side by Side Diff: utils/pub/utils.dart

Issue 14297021: Move pub into sdk/lib/_internal. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Disallow package: imports of pub. Created 7 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « utils/pub/system_cache.dart ('k') | utils/pub/validator.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 /// Generic utility functions. Stuff that should possibly be in core.
6 library utils;
7
8 import 'dart:async';
9 import 'dart:crypto';
10 import 'dart:io';
11 import 'dart:isolate';
12 import 'dart:uri';
13
14 /// A pair of values.
15 class Pair<E, F> {
16 E first;
17 F last;
18
19 Pair(this.first, this.last);
20
21 String toString() => '($first, $last)';
22
23 bool operator==(other) {
24 if (other is! Pair) return false;
25 return other.first == first && other.last == last;
26 }
27
28 int get hashCode => first.hashCode ^ last.hashCode;
29 }
30
31 /// A completer that waits until all added [Future]s complete.
32 // TODO(rnystrom): Copied from web_components. Remove from here when it gets
33 // added to dart:core. (See #6626.)
34 class FutureGroup<T> {
35 int _pending = 0;
36 Completer<List<T>> _completer = new Completer<List<T>>();
37 final List<Future<T>> futures = <Future<T>>[];
38 bool completed = false;
39
40 final List<T> _values = <T>[];
41
42 /// Wait for [task] to complete.
43 Future<T> add(Future<T> task) {
44 if (completed) {
45 throw new StateError("The FutureGroup has already completed.");
46 }
47
48 _pending++;
49 futures.add(task.then((value) {
50 if (completed) return;
51
52 _pending--;
53 _values.add(value);
54
55 if (_pending <= 0) {
56 completed = true;
57 _completer.complete(_values);
58 }
59 }).catchError((e) {
60 if (completed) return;
61
62 completed = true;
63 _completer.completeError(e);
64 }));
65
66 return task;
67 }
68
69 Future<List> get future => _completer.future;
70 }
71
72 // TODO(rnystrom): Move into String?
73 /// Pads [source] to [length] by adding spaces at the end.
74 String padRight(String source, int length) {
75 final result = new StringBuffer();
76 result.write(source);
77
78 while (result.length < length) {
79 result.write(' ');
80 }
81
82 return result.toString();
83 }
84
85 /// Flattens nested lists inside an iterable into a single list containing only
86 /// non-list elements.
87 List flatten(Iterable nested) {
88 var result = [];
89 helper(list) {
90 for (var element in list) {
91 if (element is List) {
92 helper(element);
93 } else {
94 result.add(element);
95 }
96 }
97 }
98 helper(nested);
99 return result;
100 }
101
102 /// Asserts that [iter] contains only one element, and returns it.
103 only(Iterable iter) {
104 var iterator = iter.iterator;
105 var currentIsValid = iterator.moveNext();
106 assert(currentIsValid);
107 var obj = iterator.current;
108 assert(!iterator.moveNext());
109 return obj;
110 }
111
112 /// Returns a set containing all elements in [minuend] that are not in
113 /// [subtrahend].
114 Set setMinus(Iterable minuend, Iterable subtrahend) {
115 var minuendSet = new Set.from(minuend);
116 minuendSet.removeAll(subtrahend);
117 return minuendSet;
118 }
119
120 /// Replace each instance of [matcher] in [source] with the return value of
121 /// [fn].
122 String replace(String source, Pattern matcher, String fn(Match)) {
123 var buffer = new StringBuffer();
124 var start = 0;
125 for (var match in matcher.allMatches(source)) {
126 buffer.write(source.substring(start, match.start));
127 start = match.end;
128 buffer.write(fn(match));
129 }
130 buffer.write(source.substring(start));
131 return buffer.toString();
132 }
133
134 /// Returns whether or not [str] ends with [matcher].
135 bool endsWithPattern(String str, Pattern matcher) {
136 for (var match in matcher.allMatches(str)) {
137 if (match.end == str.length) return true;
138 }
139 return false;
140 }
141
142 /// Returns the hex-encoded sha1 hash of [source].
143 String sha1(String source) {
144 var sha = new SHA1();
145 sha.add(source.codeUnits);
146 return CryptoUtils.bytesToHex(sha.close());
147 }
148
149 /// Returns a [Future] that completes in [milliseconds].
150 Future sleep(int milliseconds) {
151 var completer = new Completer();
152 new Timer(new Duration(milliseconds: milliseconds), completer.complete);
153 return completer.future;
154 }
155
156 /// Configures [future] so that its result (success or exception) is passed on
157 /// to [completer].
158 void chainToCompleter(Future future, Completer completer) {
159 future.then((value) => completer.complete(value),
160 onError: (e) => completer.completeError(e));
161 }
162
163 // TODO(nweiz): remove this when issue 7964 is fixed.
164 /// Returns a [Future] that will complete to the first element of [stream].
165 /// Unlike [Stream.first], this is safe to use with single-subscription streams.
166 Future streamFirst(Stream stream) {
167 var completer = new Completer();
168 var subscription;
169 subscription = stream.listen((value) {
170 subscription.cancel();
171 completer.complete(value);
172 }, onError: (e) {
173 completer.completeError(e);
174 }, onDone: () {
175 completer.completeError(new StateError("No elements"));
176 }, cancelOnError: true);
177 return completer.future;
178 }
179
180 /// Returns a wrapped version of [stream] along with a [StreamSubscription] that
181 /// can be used to control the wrapped stream.
182 Pair<Stream, StreamSubscription> streamWithSubscription(Stream stream) {
183 var controller = new StreamController();
184 var controllerStream = stream.isBroadcast ?
185 controller.stream.asBroadcastStream() :
186 controller.stream;
187 var subscription = stream.listen(controller.add,
188 onError: controller.addError,
189 onDone: controller.close);
190 return new Pair<Stream, StreamSubscription>(controllerStream, subscription);
191 }
192
193 // TODO(nweiz): remove this when issue 7787 is fixed.
194 /// Creates two single-subscription [Stream]s that each emit all values and
195 /// errors from [stream]. This is useful if [stream] is single-subscription but
196 /// multiple subscribers are necessary.
197 Pair<Stream, Stream> tee(Stream stream) {
198 var controller1 = new StreamController();
199 var controller2 = new StreamController();
200 stream.listen((value) {
201 controller1.add(value);
202 controller2.add(value);
203 }, onError: (error) {
204 controller1.addError(error);
205 controller2.addError(error);
206 }, onDone: () {
207 controller1.close();
208 controller2.close();
209 });
210 return new Pair<Stream, Stream>(controller1.stream, controller2.stream);
211 }
212
213 /// A regular expression matching a trailing CR character.
214 final _trailingCR = new RegExp(r"\r$");
215
216 // TODO(nweiz): Use `text.split(new RegExp("\r\n?|\n\r?"))` when issue 9360 is
217 // fixed.
218 /// Splits [text] on its line breaks in a Windows-line-break-friendly way.
219 List<String> splitLines(String text) =>
220 text.split("\n").map((line) => line.replaceFirst(_trailingCR, "")).toList();
221
222 /// Converts a stream of arbitrarily chunked strings into a line-by-line stream.
223 /// The lines don't include line termination characters. A single trailing
224 /// newline is ignored.
225 Stream<String> streamToLines(Stream<String> stream) {
226 var buffer = new StringBuffer();
227 return stream.transform(new StreamTransformer(
228 handleData: (chunk, sink) {
229 var lines = splitLines(chunk);
230 var leftover = lines.removeLast();
231 for (var line in lines) {
232 if (!buffer.isEmpty) {
233 buffer.write(line);
234 line = buffer.toString();
235 buffer = new StringBuffer();
236 }
237
238 sink.add(line);
239 }
240 buffer.write(leftover);
241 },
242 handleDone: (sink) {
243 if (!buffer.isEmpty) sink.add(buffer.toString());
244 sink.close();
245 }));
246 }
247
248 /// Like [Iterable.where], but allows [test] to return [Future]s and uses the
249 /// results of those [Future]s as the test.
250 Future<Iterable> futureWhere(Iterable iter, test(value)) {
251 return Future.wait(iter.map((e) {
252 var result = test(e);
253 if (result is! Future) result = new Future.value(result);
254 return result.then((result) => new Pair(e, result));
255 }))
256 .then((pairs) => pairs.where((pair) => pair.last))
257 .then((pairs) => pairs.map((pair) => pair.first));
258 }
259
260 // TODO(nweiz): unify the following functions with the utility functions in
261 // pkg/http.
262
263 /// Like [String.split], but only splits on the first occurrence of the pattern.
264 /// This will always return an array of two elements or fewer.
265 List<String> split1(String toSplit, String pattern) {
266 if (toSplit.isEmpty) return <String>[];
267
268 var index = toSplit.indexOf(pattern);
269 if (index == -1) return [toSplit];
270 return [toSplit.substring(0, index),
271 toSplit.substring(index + pattern.length)];
272 }
273
274 /// Adds additional query parameters to [url], overwriting the original
275 /// parameters if a name conflict occurs.
276 Uri addQueryParameters(Uri url, Map<String, String> parameters) {
277 var queryMap = queryToMap(url.query);
278 mapAddAll(queryMap, parameters);
279 return url.resolve("?${mapToQuery(queryMap)}");
280 }
281
282 /// Convert a URL query string (or `application/x-www-form-urlencoded` body)
283 /// into a [Map] from parameter names to values.
284 Map<String, String> queryToMap(String queryList) {
285 var map = {};
286 for (var pair in queryList.split("&")) {
287 var split = split1(pair, "=");
288 if (split.isEmpty) continue;
289 var key = urlDecode(split[0]);
290 var value = split.length > 1 ? urlDecode(split[1]) : "";
291 map[key] = value;
292 }
293 return map;
294 }
295
296 /// Convert a [Map] from parameter names to values to a URL query string.
297 String mapToQuery(Map<String, String> map) {
298 var pairs = <List<String>>[];
299 map.forEach((key, value) {
300 key = encodeUriComponent(key);
301 value = (value == null || value.isEmpty) ? null : encodeUriComponent(value);
302 pairs.add([key, value]);
303 });
304 return pairs.map((pair) {
305 if (pair[1] == null) return pair[0];
306 return "${pair[0]}=${pair[1]}";
307 }).join("&");
308 }
309
310 // TODO(nweiz): remove this when issue 9068 has been fixed.
311 /// Whether [uri1] and [uri2] are equal. This consider HTTP URIs to default to
312 /// port 80, and HTTPs URIs to default to port 443.
313 bool urisEqual(Uri uri1, Uri uri2) =>
314 canonicalizeUri(uri1) == canonicalizeUri(uri2);
315
316 /// Return [uri] with redundant port information removed.
317 Uri canonicalizeUri(Uri uri) {
318 if (uri == null) return null;
319
320 var sansPort = new Uri.fromComponents(
321 scheme: uri.scheme, userInfo: uri.userInfo, domain: uri.domain,
322 path: uri.path, query: uri.query, fragment: uri.fragment);
323 if (uri.scheme == 'http' && uri.port == 80) return sansPort;
324 if (uri.scheme == 'https' && uri.port == 443) return sansPort;
325 return uri;
326 }
327
328 /// Add all key/value pairs from [source] to [destination], overwriting any
329 /// pre-existing values.
330 void mapAddAll(Map destination, Map source) =>
331 source.forEach((key, value) => destination[key] = value);
332
333 /// Decodes a URL-encoded string. Unlike [decodeUriComponent], this includes
334 /// replacing `+` with ` `.
335 String urlDecode(String encoded) =>
336 decodeUriComponent(encoded.replaceAll("+", " "));
337
338 /// Takes a simple data structure (composed of [Map]s, [Iterable]s, scalar
339 /// objects, and [Future]s) and recursively resolves all the [Future]s contained
340 /// within. Completes with the fully resolved structure.
341 Future awaitObject(object) {
342 // Unroll nested futures.
343 if (object is Future) return object.then(awaitObject);
344 if (object is Iterable) {
345 return Future.wait(object.map(awaitObject).toList());
346 }
347 if (object is! Map) return new Future.value(object);
348
349 var pairs = <Future<Pair>>[];
350 object.forEach((key, value) {
351 pairs.add(awaitObject(value)
352 .then((resolved) => new Pair(key, resolved)));
353 });
354 return Future.wait(pairs).then((resolvedPairs) {
355 var map = {};
356 for (var pair in resolvedPairs) {
357 map[pair.first] = pair.last;
358 }
359 return map;
360 });
361 }
362
363 /// An exception class for exceptions that are intended to be seen by the user.
364 /// These exceptions won't have any debugging information printed when they're
365 /// thrown.
366 class ApplicationException implements Exception {
367 final String message;
368
369 ApplicationException(this.message);
370 }
371
372 /// Throw a [ApplicationException] with [message].
373 void fail(String message) {
374 throw new ApplicationException(message);
375 }
376
377 /// Returns whether [error] is a user-facing error object. This includes both
378 /// [ApplicationException] and any dart:io errors.
379 bool isUserFacingException(error) {
380 return error is ApplicationException ||
381 // TODO(nweiz): clean up this branch when issue 9955 is fixed.
382 error is DirectoryIOException ||
383 error is FileIOException ||
384 error is HttpException ||
385 error is HttpParserException ||
386 error is LinkIOException ||
387 error is MimeParserException ||
388 error is OSError ||
389 error is ProcessException ||
390 error is SocketIOException ||
391 error is WebSocketException;
392 }
OLDNEW
« no previous file with comments | « utils/pub/system_cache.dart ('k') | utils/pub/validator.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698