blob: d9f40771d74747c0dac02b9820b037af0096c5d3 [file] [log] [blame] [edit]
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. 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:io';
import 'package:args/args.dart';
import 'package:args/command_runner.dart';
import 'package:cli_util/cli_logging.dart';
import 'package:path/path.dart' as path;
import 'experiments.dart';
import 'utils.dart';
// Initialize a default logger. We'll replace this with a verbose logger if
// necessary once we start parsing.
final Ansi ansi = Ansi(Ansi.terminalSupportsAnsi);
Logger log = Logger.standard(ansi: ansi);
bool isDiagnostics = false;
/// When set, this function is executed from the [DartdevCommand] constructor to
/// contribute additional flags.
void Function(ArgParser argParser, String cmdName)? flagContributor;
abstract class DartdevCommand extends Command<int> {
static const errorExitCode = 65;
final String _name;
final String _description;
final bool _verbose;
final Project project = Project();
@override
final bool hidden;
DartdevCommand(this._name, this._description, this._verbose,
{this.hidden = false}) {
flagContributor?.call(argParser, _name);
}
@override
String get name => _name;
@override
String get description => _description;
ArgParser? _argParser;
@override
ArgParser get argParser => _argParser ??= createArgParser();
@override
String get category {
if (parent != null) {
// Subcommands should not have a top level command category.
assert(commandCategory == null);
return '';
}
return commandCategory!.name;
}
CommandCategory? get commandCategory => null;
@override
String get invocation {
String result = super.invocation;
if (_verbose) {
var firstSpace = result.indexOf(' ');
if (firstSpace < 0) firstSpace = result.length;
result = result.replaceRange(firstSpace, firstSpace, ' [vm-options]');
}
return result;
}
/// Create the ArgParser instance for this command.
///
/// Subclasses can override this in order to create a customized ArgParser.
ArgParser createArgParser() =>
ArgParser(usageLineLength: dartdevUsageLineLength);
}
enum CommandCategory {
project('Project'),
sourceCode('Source code'),
tools('Tools');
final String name;
const CommandCategory(this.name);
}
extension DartDevCommand<T> on Command<T> {
/// Return whether commands should emit verbose output.
bool get verbose => globalResults!.flag('verbose');
/// Return whether the tool should emit diagnostic output.
bool get diagnosticsEnabled => globalResults!.flag('diagnostics');
/// Return whether any Dart experiments were specified by the user.
bool get wereExperimentsSpecified =>
globalResults?.wasParsed(experimentFlagName) ?? false;
List<String> get specifiedExperiments =>
globalResults!.multiOption(experimentFlagName);
}
Future<int> runProcess(
List<String> command, {
bool logToTrace = false,
void Function(String str)? listener,
String? cwd,
}) async {
Future<void> forward(Stream<List<int>> output, bool isStderr) {
return _streamLineTransform(output, (line) {
final trimmed = line.trimRight();
logToTrace
? log.trace(trimmed)
: (isStderr ? log.stderr(trimmed) : log.stdout(trimmed));
if (listener != null) listener(line);
});
}
log.trace(command.join(' '));
final process = await Process.start(
command.first,
command.skip(1).toList(),
workingDirectory: cwd,
);
final (_, _, exitCode) = await (
forward(process.stdout, false),
forward(process.stderr, true),
process.exitCode
).wait;
return exitCode;
}
Future<void> _streamLineTransform(
Stream<List<int>> stream,
Function(String line) handler,
) {
return stream
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(handler)
.asFuture();
}
/// A representation of a project on disk.
class Project {
final Directory dir;
Project() : dir = Directory.current;
Project.fromDirectory(this.dir);
bool get hasPubspecFile =>
FileSystemEntity.isFileSync(path.join(dir.path, 'pubspec.yaml'));
File get pubspecFile => File(path.join(dir.path, 'pubspec.yaml'));
}