utils.dart 7.84 KB
Newer Older
1 2 3 4 5
// 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';
6
import 'dart:convert';
7
import 'dart:math' show Random;
Devon Carew's avatar
Devon Carew committed
8 9

import 'package:crypto/crypto.dart';
10
import 'package:intl/intl.dart';
11
import 'package:quiver/time.dart';
Devon Carew's avatar
Devon Carew committed
12

13
import '../globals.dart';
14
import 'context.dart';
15
import 'file_system.dart';
16
import 'platform.dart';
17

18 19
bool get isRunningOnBot {
  return
20
    platform.environment['BOT'] == 'true' ||
21 22 23

    // https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
    platform.environment['TRAVIS'] == 'true' ||
24
    platform.environment['CONTINUOUS_INTEGRATION'] == 'true' ||
25 26 27 28 29 30
    platform.environment.containsKey('CI') || // Travis and AppVeyor

    // https://www.appveyor.com/docs/environment-variables/
    platform.environment.containsKey('APPVEYOR') ||

    // Properties on Flutter's Chrome Infra bots.
31
    platform.environment['CHROME_HEADLESS'] == '1' ||
32
    platform.environment.containsKey('BUILDBOT_BUILDERNAME');
33 34
}

Ian Hickson's avatar
Ian Hickson committed
35
String hex(List<int> bytes) {
36
  final StringBuffer result = new StringBuffer();
Ian Hickson's avatar
Ian Hickson committed
37 38 39 40 41
  for (int part in bytes)
    result.write('${part < 16 ? '0' : ''}${part.toRadixString(16)}');
  return result.toString();
}

Devon Carew's avatar
Devon Carew committed
42
String calculateSha(File file) {
Ian Hickson's avatar
Ian Hickson committed
43
  return hex(sha1.convert(file.readAsBytesSync()).bytes);
Devon Carew's avatar
Devon Carew committed
44
}
45

46 47 48 49 50 51 52 53 54 55 56 57
/// 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;
}

58 59 60 61 62 63
String toTitleCase(String str) {
  if (str.isEmpty)
    return str;
  return str.substring(0, 1).toUpperCase() + str.substring(1);
}

64 65 66
/// Return the plural of the given word (`cat(s)`).
String pluralize(String word, int count) => count == 1 ? word : word + 's';

67 68
/// Return the name of an enum item.
String getEnumName(dynamic enumItem) {
69 70
  final String name = '$enumItem';
  final int index = name.indexOf('.');
71 72 73
  return index == -1 ? name : name.substring(index + 1);
}

Devon Carew's avatar
Devon Carew committed
74
File getUniqueFile(Directory dir, String baseName, String ext) {
75
  final FileSystem fs = dir.fileSystem;
Devon Carew's avatar
Devon Carew committed
76 77 78
  int i = 1;

  while (true) {
79 80
    final String name = '${baseName}_${i.toString().padLeft(2, '0')}.$ext';
    final File file = fs.file(fs.path.join(dir.path, name));
Devon Carew's avatar
Devon Carew committed
81 82 83 84 85 86
    if (!file.existsSync())
      return file;
    i++;
  }
}

87
String toPrettyJson(Object jsonable) {
88
  return const JsonEncoder.withIndent('  ').convert(jsonable) + '\n';
89 90
}

91 92 93 94 95
/// 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';
}

96 97 98 99 100 101 102 103 104 105 106
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';
}
107

108 109 110
/// Return a relative path if [fullPath] is contained by the cwd, else return an
/// absolute path.
String getDisplayPath(String fullPath) {
111
  final String cwd = fs.currentDirectory.path + fs.path.separator;
112 113 114
  return fullPath.startsWith(cwd) ?  fullPath.substring(cwd.length) : fullPath;
}

115 116 117 118 119 120 121 122 123 124 125 126 127 128
/// 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;

129 130
  final StreamController<T> _addedController = new StreamController<T>.broadcast();
  final StreamController<T> _removedController = new StreamController<T>.broadcast();
131 132 133 134 135 136 137

  Stream<T> get onAdded => _addedController.stream;
  Stream<T> get onRemoved => _removedController.stream;

  List<T> get items => _items.toList();

  void updateWithNewList(List<T> updatedList) {
138
    final Set<T> updatedSet = new Set<T>.from(updatedList);
139

140 141
    final Set<T> addedItems = updatedSet.difference(_items);
    final Set<T> removedItems = _items.difference(updatedSet);
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156

    _items = updatedSet;

    for (T item in addedItems)
      _addedController.add(item);
    for (T item in removedItems)
      _removedController.add(item);
  }

  /// Close the streams.
  void dispose() {
    _addedController.close();
    _removedController.close();
  }
}
157 158

class SettingsFile {
159 160
  SettingsFile();

161 162 163 164 165
  SettingsFile.parse(String contents) {
    for (String line in contents.split('\n')) {
      line = line.trim();
      if (line.startsWith('#') || line.isEmpty)
        continue;
166
      final int index = line.indexOf('=');
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
      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'));
  }
}
184 185 186 187 188 189 190 191 192 193

/// 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 {
194
  final Random _random = new Random();
195 196 197 198 199

  /// 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.
200
    final int special = 8 + _random.nextInt(4);
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217

    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');
}
218 219

Clock get clock => context.putIfAbsent(Clock, () => const Clock());
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

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;
  }
}
259 260 261 262 263 264 265 266 267 268 269

/// 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));
}