|
5 | 5 | /// This is a helper library to make working with io easier.
|
6 | 6 | library dartdoc.io_utils;
|
7 | 7 |
|
| 8 | +import 'dart:async'; |
| 9 | +import 'dart:convert'; |
8 | 10 | import 'dart:io';
|
9 | 11 |
|
10 | 12 | import 'package:path/path.dart' as path;
|
@@ -60,3 +62,158 @@ String getFileNameFor(String name) =>
|
60 | 62 | final libraryNameRegexp = new RegExp('[.:]');
|
61 | 63 | final partOfRegexp = new RegExp('part of ');
|
62 | 64 | final newLinePartOfRegexp = new RegExp('\npart of ');
|
| 65 | + |
| 66 | +final RegExp quotables = new RegExp(r'[ "\r\n\$]'); |
| 67 | + |
| 68 | +class SubprocessLauncher { |
| 69 | + final String context; |
| 70 | + Map<String, String> _environment; |
| 71 | + |
| 72 | + Map<String, String> get environment => _environment; |
| 73 | + |
| 74 | + String get prefix => context.isNotEmpty ? '$context: ' : ''; |
| 75 | + |
| 76 | + // from flutter:dev/tools/dartdoc.dart, modified |
| 77 | + static void _printStream(Stream<List<int>> stream, Stdout output, |
| 78 | + {String prefix: '', Iterable<String> Function(String line) filter}) { |
| 79 | + assert(prefix != null); |
| 80 | + if (filter == null) filter = (line) => [line]; |
| 81 | + stream |
| 82 | + .transform(UTF8.decoder) |
| 83 | + .transform(const LineSplitter()) |
| 84 | + .expand(filter) |
| 85 | + .listen((String line) { |
| 86 | + if (line != null) { |
| 87 | + output.write('$prefix$line'.trim()); |
| 88 | + output.write('\n'); |
| 89 | + } |
| 90 | + }); |
| 91 | + } |
| 92 | + |
| 93 | + SubprocessLauncher(this.context, [Map<String, String> environment]) { |
| 94 | + if (environment == null) this._environment = new Map(); |
| 95 | + } |
| 96 | + |
| 97 | + /// A wrapper around start/await process.exitCode that will display the |
| 98 | + /// output of the executable continuously and fail on non-zero exit codes. |
| 99 | + /// It will also parse any valid JSON objects (one per line) it encounters |
| 100 | + /// on stdout/stderr, and return them. Returns null if no JSON objects |
| 101 | + /// were encountered. |
| 102 | + /// |
| 103 | + /// Makes running programs in grinder similar to set -ex for bash, even on |
| 104 | + /// Windows (though some of the bashisms will no longer make sense). |
| 105 | + /// TODO(jcollins-g): move this to grinder? |
| 106 | + Future<Iterable<Map>> runStreamed(String executable, List<String> arguments, |
| 107 | + {String workingDirectory}) async { |
| 108 | + List<Map> jsonObjects; |
| 109 | + |
| 110 | + /// Allow us to pretend we didn't pass the JSON flag in to dartdoc by |
| 111 | + /// printing what dartdoc would have printed without it, yet storing |
| 112 | + /// json objects into [jsonObjects]. |
| 113 | + Iterable<String> jsonCallback(String line) { |
| 114 | + Map result; |
| 115 | + try { |
| 116 | + result = json.decoder.convert(line); |
| 117 | + } catch (FormatException) {} |
| 118 | + if (result != null) { |
| 119 | + if (jsonObjects == null) { |
| 120 | + jsonObjects = new List(); |
| 121 | + } |
| 122 | + jsonObjects.add(result); |
| 123 | + if (result.containsKey('message')) { |
| 124 | + line = result['message']; |
| 125 | + } else if (result.containsKey('data')) { |
| 126 | + line = result['data']['text']; |
| 127 | + } |
| 128 | + } |
| 129 | + return line.split('\n'); |
| 130 | + } |
| 131 | + |
| 132 | + stderr.write('$prefix+ '); |
| 133 | + if (workingDirectory != null) stderr.write('(cd "$workingDirectory" && '); |
| 134 | + if (environment != null) { |
| 135 | + stderr.write(environment.keys.map((String key) { |
| 136 | + if (environment[key].contains(quotables)) { |
| 137 | + return "$key='${environment[key]}'"; |
| 138 | + } else { |
| 139 | + return "$key=${environment[key]}"; |
| 140 | + } |
| 141 | + }).join(' ')); |
| 142 | + stderr.write(' '); |
| 143 | + } |
| 144 | + stderr.write('$executable'); |
| 145 | + if (arguments.isNotEmpty) { |
| 146 | + for (String arg in arguments) { |
| 147 | + if (arg.contains(quotables)) { |
| 148 | + stderr.write(" '$arg'"); |
| 149 | + } else { |
| 150 | + stderr.write(" $arg"); |
| 151 | + } |
| 152 | + } |
| 153 | + } |
| 154 | + if (workingDirectory != null) stderr.write(')'); |
| 155 | + stderr.write('\n'); |
| 156 | + Process process = await Process.start(executable, arguments, |
| 157 | + workingDirectory: workingDirectory, environment: environment); |
| 158 | + |
| 159 | + _printStream(process.stdout, stdout, prefix: prefix, filter: jsonCallback); |
| 160 | + _printStream(process.stderr, stderr, prefix: prefix, filter: jsonCallback); |
| 161 | + await process.exitCode; |
| 162 | + |
| 163 | + int exitCode = await process.exitCode; |
| 164 | + if (exitCode != 0) { |
| 165 | + throw new ProcessException(executable, arguments, |
| 166 | + "SubprocessLauncher got non-zero exitCode", exitCode); |
| 167 | + } |
| 168 | + return jsonObjects; |
| 169 | + } |
| 170 | +} |
| 171 | + |
| 172 | +/// Output formatter for comparing warnings. |
| 173 | +String printWarningDelta(String title, String dartdocOriginalBranch, |
| 174 | + Map<String, int> original, Map<String, int> current) { |
| 175 | + StringBuffer printBuffer = new StringBuffer(); |
| 176 | + Set<String> quantityChangedOuts = new Set(); |
| 177 | + Set<String> onlyOriginal = new Set(); |
| 178 | + Set<String> onlyCurrent = new Set(); |
| 179 | + Set<String> allKeys = |
| 180 | + new Set.from([]..addAll(original.keys)..addAll(current.keys)); |
| 181 | + |
| 182 | + for (String key in allKeys) { |
| 183 | + if (original.containsKey(key) && !current.containsKey(key)) { |
| 184 | + onlyOriginal.add(key); |
| 185 | + } else if (!original.containsKey(key) && current.containsKey(key)) { |
| 186 | + onlyCurrent.add(key); |
| 187 | + } else if (original.containsKey(key) && |
| 188 | + current.containsKey(key) && |
| 189 | + original[key] != current[key]) { |
| 190 | + quantityChangedOuts.add(key); |
| 191 | + } |
| 192 | + } |
| 193 | + |
| 194 | + if (onlyOriginal.isNotEmpty) { |
| 195 | + printBuffer.writeln( |
| 196 | + '*** $title : ${onlyOriginal.length} warnings from original ($dartdocOriginalBranch) missing in current:'); |
| 197 | + onlyOriginal.forEach((warning) => printBuffer.writeln(warning)); |
| 198 | + } |
| 199 | + if (onlyCurrent.isNotEmpty) { |
| 200 | + printBuffer.writeln( |
| 201 | + '*** $title : ${onlyCurrent.length} new warnings not in original ($dartdocOriginalBranch)'); |
| 202 | + onlyCurrent.forEach((warning) => printBuffer.writeln(warning)); |
| 203 | + } |
| 204 | + if (quantityChangedOuts.isNotEmpty) { |
| 205 | + printBuffer.writeln('*** $title : Identical warning quantity changed'); |
| 206 | + for (String key in quantityChangedOuts) { |
| 207 | + printBuffer.writeln( |
| 208 | + "* Appeared ${original[key]} times in original ($dartdocOriginalBranch), now ${current[key]}:"); |
| 209 | + printBuffer.writeln(key); |
| 210 | + } |
| 211 | + } |
| 212 | + if (onlyOriginal.isEmpty && |
| 213 | + onlyCurrent.isEmpty && |
| 214 | + quantityChangedOuts.isEmpty) { |
| 215 | + printBuffer.writeln( |
| 216 | + '*** $title : No difference in warning output from original ($dartdocOriginalBranch)${allKeys.isEmpty ? "" : " (${allKeys.length} warnings found)"}'); |
| 217 | + } |
| 218 | + return printBuffer.toString(); |
| 219 | +} |
0 commit comments