1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:math' show Random;
import 'package:crypto/crypto.dart';
import 'package:intl/intl.dart';
import 'package:quiver/time.dart';
import '../globals.dart';
import 'context.dart';
import 'file_system.dart';
import 'platform.dart';
bool get isRunningOnBot {
return
platform.environment['BOT'] == 'true' ||
// https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
platform.environment['TRAVIS'] == 'true' ||
platform.environment['CONTINUOUS_INTEGRATION'] == 'true' ||
platform.environment.containsKey('CI') || // Travis and AppVeyor
// https://www.appveyor.com/docs/environment-variables/
platform.environment.containsKey('APPVEYOR') ||
// https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html
(platform.environment.containsKey('AWS_REGION') && platform.environment.containsKey('CODEBUILD_INITIATOR')) ||
// https://wiki.jenkins.io/display/JENKINS/Building+a+software+project#Buildingasoftwareproject-belowJenkinsSetEnvironmentVariables
platform.environment.containsKey('JENKINS_URL') ||
// Properties on Flutter's Chrome Infra bots.
platform.environment['CHROME_HEADLESS'] == '1' ||
platform.environment.containsKey('BUILDBOT_BUILDERNAME');
}
String hex(List<int> bytes) {
final StringBuffer result = new StringBuffer();
for (int part in bytes)
result.write('${part < 16 ? '0' : ''}${part.toRadixString(16)}');
return result.toString();
}
String calculateSha(File file) {
return hex(sha1.convert(file.readAsBytesSync()).bytes);
}
/// Convert `foo_bar` to `fooBar`.
String camelCase(String str) {
int index = str.indexOf('_');
while (index != -1 && index < str.length - 2) {
str = str.substring(0, index) +
str.substring(index + 1, index + 2).toUpperCase() +
str.substring(index + 2);
index = str.indexOf('_');
}
return str;
}
String toTitleCase(String str) {
if (str.isEmpty)
return str;
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
/// Return the plural of the given word (`cat(s)`).
String pluralize(String word, int count) => count == 1 ? word : word + 's';
/// Return the name of an enum item.
String getEnumName(dynamic enumItem) {
final String name = '$enumItem';
final int index = name.indexOf('.');
return index == -1 ? name : name.substring(index + 1);
}
File getUniqueFile(Directory dir, String baseName, String ext) {
final FileSystem fs = dir.fileSystem;
int i = 1;
while (true) {
final String name = '${baseName}_${i.toString().padLeft(2, '0')}.$ext';
final File file = fs.file(fs.path.join(dir.path, name));
if (!file.existsSync())
return file;
i++;
}
}
String toPrettyJson(Object jsonable) {
return const JsonEncoder.withIndent(' ').convert(jsonable) + '\n';
}
/// Return a String - with units - for the size in MB of the given number of bytes.
String getSizeAsMB(int bytesLength) {
return '${(bytesLength / (1024 * 1024)).toStringAsFixed(1)}MB';
}
final NumberFormat kSecondsFormat = new NumberFormat('0.0');
final NumberFormat kMillisecondsFormat = new NumberFormat.decimalPattern();
String getElapsedAsSeconds(Duration duration) {
final double seconds = duration.inMilliseconds / Duration.MILLISECONDS_PER_SECOND;
return '${kSecondsFormat.format(seconds)}s';
}
String getElapsedAsMilliseconds(Duration duration) {
return '${kMillisecondsFormat.format(duration.inMilliseconds)}ms';
}
/// Return a relative path if [fullPath] is contained by the cwd, else return an
/// absolute path.
String getDisplayPath(String fullPath) {
final String cwd = fs.currentDirectory.path + fs.path.separator;
return fullPath.startsWith(cwd) ? fullPath.substring(cwd.length) : fullPath;
}
/// A class to maintain a list of items, fire events when items are added or
/// removed, and calculate a diff of changes when a new list of items is
/// available.
class ItemListNotifier<T> {
ItemListNotifier() {
_items = new Set<T>();
}
ItemListNotifier.from(List<T> items) {
_items = new Set<T>.from(items);
}
Set<T> _items;
final StreamController<T> _addedController = new StreamController<T>.broadcast();
final StreamController<T> _removedController = new StreamController<T>.broadcast();
Stream<T> get onAdded => _addedController.stream;
Stream<T> get onRemoved => _removedController.stream;
List<T> get items => _items.toList();
void updateWithNewList(List<T> updatedList) {
final Set<T> updatedSet = new Set<T>.from(updatedList);
final Set<T> addedItems = updatedSet.difference(_items);
final Set<T> removedItems = _items.difference(updatedSet);
_items = updatedSet;
addedItems.forEach(_addedController.add);
removedItems.forEach(_removedController.add);
}
/// Close the streams.
void dispose() {
_addedController.close();
_removedController.close();
}
}
class SettingsFile {
SettingsFile();
SettingsFile.parse(String contents) {
for (String line in contents.split('\n')) {
line = line.trim();
if (line.startsWith('#') || line.isEmpty)
continue;
final int index = line.indexOf('=');
if (index != -1)
values[line.substring(0, index)] = line.substring(index + 1);
}
}
factory SettingsFile.parseFromFile(File file) {
return new SettingsFile.parse(file.readAsStringSync());
}
final Map<String, String> values = <String, String>{};
void writeContents(File file) {
file.writeAsStringSync(values.keys.map((String key) {
return '$key=${values[key]}';
}).join('\n'));
}
}
/// A UUID generator. This will generate unique IDs in the format:
///
/// f47ac10b-58cc-4372-a567-0e02b2c3d479
///
/// The generated UUIDs are 128 bit numbers encoded in a specific string format.
///
/// For more information, see
/// http://en.wikipedia.org/wiki/Universally_unique_identifier.
class Uuid {
final Random _random = new Random();
/// Generate a version 4 (random) UUID. This is a UUID scheme that only uses
/// random numbers as the source of the generated UUID.
String generateV4() {
// Generate xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx / 8-4-4-4-12.
final int special = 8 + _random.nextInt(4);
return
'${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}-'
'${_bitsDigits(16, 4)}-'
'4${_bitsDigits(12, 3)}-'
'${_printDigits(special, 1)}${_bitsDigits(12, 3)}-'
'${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}';
}
String _bitsDigits(int bitCount, int digitCount) =>
_printDigits(_generateBits(bitCount), digitCount);
int _generateBits(int bitCount) => _random.nextInt(1 << bitCount);
String _printDigits(int value, int count) =>
value.toRadixString(16).padLeft(count, '0');
}
Clock get clock => context.putIfAbsent(Clock, () => const Clock());
typedef Future<Null> AsyncCallback();
/// A [Timer] inspired class that:
/// - has a different initial value for the first callback delay
/// - waits for a callback to be complete before it starts the next timer
class Poller {
Poller(this.callback, this.pollingInterval, { this.initialDelay: Duration.ZERO }) {
new Future<Null>.delayed(initialDelay, _handleCallback);
}
final AsyncCallback callback;
final Duration initialDelay;
final Duration pollingInterval;
bool _cancelled = false;
Timer _timer;
Future<Null> _handleCallback() async {
if (_cancelled)
return;
try {
await callback();
} catch (error) {
printTrace('Error from poller: $error');
}
if (!_cancelled)
_timer = new Timer(pollingInterval, _handleCallback);
}
/// Cancels the poller.
void cancel() {
_cancelled = true;
_timer?.cancel();
_timer = null;
}
}
/// Returns a [Future] that completes when all given [Future]s complete.
///
/// Uses [Future.wait] but removes null elements from the provided
/// `futures` iterable first.
///
/// The returned [Future<List>] will be shorter than the given `futures` if
/// it contains nulls.
Future<List<T>> waitGroup<T>(Iterable<Future<T>> futures) {
return Future.wait<T>(futures.where((Future<T> future) => future != null));
}