Skip to content

Move ToolConfiguration wholesale to it's own file #3911

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 2 additions & 127 deletions lib/src/dartdoc_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
/// line arguments.
library;

import 'dart:io' show Platform, exitCode, stderr, stdout;
import 'dart:io' show exitCode, stderr, stdout;

import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/file_system/file_system.dart';
Expand All @@ -26,8 +26,7 @@ import 'package:dartdoc/src/logging.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/package_meta.dart';
import 'package:dartdoc/src/source_linker.dart';
import 'package:dartdoc/src/tool_definition.dart';
import 'package:dartdoc/src/tool_runner.dart';
import 'package:dartdoc/src/tool_configuration.dart';
import 'package:dartdoc/src/warnings.dart';
import 'package:path/path.dart' as p show Context, canonicalize;
import 'package:yaml/yaml.dart';
Expand Down Expand Up @@ -152,130 +151,6 @@ class CategoryConfiguration {
}
}

/// A configuration class that can interpret [ToolDefinition]s from a YAML map.
class ToolConfiguration {
final Map<String, ToolDefinition> tools;

final ResourceProvider resourceProvider;

late ToolRunner runner = ToolRunner(this);

ToolConfiguration._(this.tools, this.resourceProvider);

static ToolConfiguration empty(ResourceProvider resourceProvider) {
return ToolConfiguration._(const {}, resourceProvider);
}

// TODO(jcollins-g): consider caching these.
static ToolConfiguration fromYamlMap(YamlMap yamlMap,
String canonicalYamlPath, ResourceProvider resourceProvider) {
var newToolDefinitions = <String, ToolDefinition>{};
var pathContext = resourceProvider.pathContext;
for (var MapEntry(:key, value: toolMap) in yamlMap.entries) {
var name = key.toString();
if (toolMap is! Map) {
throw DartdocOptionError(
'Tools must be defined as a map of tool names to definitions. Tool '
'$name is not a map.');
}

var description = toolMap['description'].toString();

List<String>? findCommand([String prefix = '']) {
// If the command key is given, then it applies to all platforms.
var commandFromKey = toolMap.containsKey('${prefix}command')
? '${prefix}command'
: '$prefix${Platform.operatingSystem}';
if (!toolMap.containsKey(commandFromKey)) {
return null;
}
var commandFrom = toolMap[commandFromKey] as YamlNode;
List<String> command;
if (commandFrom.value is String) {
command = [commandFrom.toString()];
} else if (commandFrom is YamlList) {
command = commandFrom.map((node) => node.toString()).toList();
} else {
throw DartdocOptionError(
'Tool commands must be a path to an executable, or a list of '
'strings that starts with a path to an executable. The tool '
"'$name' has a '$commandFromKey' entry that is a "
'${commandFrom.runtimeType}');
}
if (command.isEmpty || command[0].isEmpty) {
throw DartdocOptionError(
'Tool commands must not be empty. Tool $name command entry '
'"$commandFromKey" must contain at least one path.');
}
return command;
}

var command = findCommand();
if (command == null) {
throw DartdocOptionError(
'At least one of "command" or "${Platform.operatingSystem}" must '
'be defined for the tool $name.');
}
var setupCommand = findCommand('setup_');

var rawCompileArgs = toolMap[compileArgsTagName];
var compileArgs = switch (rawCompileArgs) {
null => const <String>[],
String() => [toolMap[compileArgsTagName].toString()],
YamlList() =>
rawCompileArgs.map((node) => node.toString()).toList(growable: false),
_ => throw DartdocOptionError(
'Tool compile arguments must be a list of strings. The tool '
"'$name' has a '$compileArgsTagName' entry that is a "
'${rawCompileArgs.runtimeType}',
),
};

/// Validates [executable] and returns whether it is a Dart script.
bool isDartScript(String executable) {
var executableFile = resourceProvider.getFile(executable);
if (resourceProvider.isNotFound(executableFile)) {
throw DartdocOptionError('Command executables must exist. '
'The file "$executable" does not exist for tool $name.');
}

var isDartCommand = ToolDefinition.isDartExecutable(executable);
// Dart scripts don't need to be executable, because they'll be
// executed with the Dart binary.
if (!isDartCommand && !resourceProvider.isExecutable(executableFile)) {
throw DartdocOptionError('Non-Dart commands must be '
'executable. The file "$executable" for tool $name does not have '
'execute permission.');
}
return isDartCommand;
}

var executableRelativePath = command.removeAt(0);
var executable = pathContext.canonicalize(
pathContext.join(canonicalYamlPath, executableRelativePath));
var isDartSetupScript = isDartScript(executable);
if (setupCommand != null) {
var setupExecutableRelativePath = setupCommand.removeAt(0);
var setupExecutable = pathContext.canonicalize(
pathContext.join(canonicalYamlPath, setupExecutableRelativePath));
// Setup commands aren't snapshotted, since they're only run once.
setupCommand = [
if (isDartSetupScript) Platform.resolvedExecutable,
setupExecutable,
...setupCommand,
];
}
newToolDefinitions[name] = ToolDefinition.fromCommand(
[executable, ...command],
setupCommand ?? const [],
description,
resourceProvider,
compileArgs: compileArgs);
}
return ToolConfiguration._(newToolDefinitions, resourceProvider);
}
}

/// A container class to keep track of where our yaml data came from.
class _YamlFileData {
/// The map from the yaml file.
Expand Down
136 changes: 136 additions & 0 deletions lib/src/tool_configuration.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright (c) 2024, 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:io' show Platform;

import 'package:analyzer/file_system/file_system.dart';
import 'package:dartdoc/src/dartdoc_options.dart';
import 'package:dartdoc/src/io_utils.dart';
import 'package:dartdoc/src/tool_definition.dart';
import 'package:dartdoc/src/tool_runner.dart';
import 'package:yaml/yaml.dart';

/// A configuration class that can interpret [ToolDefinition]s from a YAML map.
class ToolConfiguration {
final Map<String, ToolDefinition> tools;

final ResourceProvider resourceProvider;

late final ToolRunner runner = ToolRunner(this);

ToolConfiguration._(this.tools, this.resourceProvider);

static ToolConfiguration empty(ResourceProvider resourceProvider) {
return ToolConfiguration._(const {}, resourceProvider);
}

// TODO(jcollins-g): consider caching these.
static ToolConfiguration fromYamlMap(YamlMap yamlMap,
String canonicalYamlPath, ResourceProvider resourceProvider) {
var newToolDefinitions = <String, ToolDefinition>{};
var pathContext = resourceProvider.pathContext;
for (var MapEntry(:key, value: toolMap) in yamlMap.entries) {
var name = key.toString();
if (toolMap is! Map) {
throw DartdocOptionError(
'Tools must be defined as a map of tool names to definitions. Tool '
'$name is not a map.');
}

var description = toolMap['description'].toString();

List<String>? findCommand([String prefix = '']) {
// If the command key is given, then it applies to all platforms.
var commandFromKey = toolMap.containsKey('${prefix}command')
? '${prefix}command'
: '$prefix${Platform.operatingSystem}';
if (!toolMap.containsKey(commandFromKey)) {
return null;
}
var commandFrom = toolMap[commandFromKey] as YamlNode;
List<String> command;
if (commandFrom.value is String) {
command = [commandFrom.toString()];
} else if (commandFrom is YamlList) {
command = commandFrom.map((node) => node.toString()).toList();
} else {
throw DartdocOptionError(
'Tool commands must be a path to an executable, or a list of '
'strings that starts with a path to an executable. The tool '
"'$name' has a '$commandFromKey' entry that is a "
'${commandFrom.runtimeType}');
}
if (command.isEmpty || command[0].isEmpty) {
throw DartdocOptionError(
'Tool commands must not be empty. Tool $name command entry '
'"$commandFromKey" must contain at least one path.');
}
return command;
}

var command = findCommand();
if (command == null) {
throw DartdocOptionError(
'At least one of "command" or "${Platform.operatingSystem}" must '
'be defined for the tool $name.');
}
var setupCommand = findCommand('setup_');

var rawCompileArgs = toolMap[compileArgsTagName];
var compileArgs = switch (rawCompileArgs) {
null => const <String>[],
String() => [toolMap[compileArgsTagName].toString()],
YamlList() =>
rawCompileArgs.map((node) => node.toString()).toList(growable: false),
_ => throw DartdocOptionError(
'Tool compile arguments must be a list of strings. The tool '
"'$name' has a '$compileArgsTagName' entry that is a "
'${rawCompileArgs.runtimeType}',
),
};

/// Validates [executable] and returns whether it is a Dart script.
bool isDartScript(String executable) {
var executableFile = resourceProvider.getFile(executable);
if (resourceProvider.isNotFound(executableFile)) {
throw DartdocOptionError('Command executables must exist. '
'The file "$executable" does not exist for tool $name.');
}

var isDartCommand = ToolDefinition.isDartExecutable(executable);
// Dart scripts don't need to be executable, because they'll be
// executed with the Dart binary.
if (!isDartCommand && !resourceProvider.isExecutable(executableFile)) {
throw DartdocOptionError('Non-Dart commands must be '
'executable. The file "$executable" for tool $name does not have '
'execute permission.');
}
return isDartCommand;
}

var executableRelativePath = command.removeAt(0);
var executable = pathContext.canonicalize(
pathContext.join(canonicalYamlPath, executableRelativePath));
var isDartSetupScript = isDartScript(executable);
if (setupCommand != null) {
var setupExecutableRelativePath = setupCommand.removeAt(0);
var setupExecutable = pathContext.canonicalize(
pathContext.join(canonicalYamlPath, setupExecutableRelativePath));
// Setup commands aren't snapshotted, since they're only run once.
setupCommand = [
if (isDartSetupScript) Platform.resolvedExecutable,
setupExecutable,
...setupCommand,
];
}
newToolDefinitions[name] = ToolDefinition.fromCommand(
[executable, ...command],
setupCommand ?? const [],
description,
resourceProvider,
compileArgs: compileArgs);
}
return ToolConfiguration._(newToolDefinitions, resourceProvider);
}
}
2 changes: 1 addition & 1 deletion lib/src/tool_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import 'dart:io' show Process, ProcessException;

import 'package:analyzer/file_system/file_system.dart';
import 'package:dartdoc/src/dartdoc_options.dart';
import 'package:dartdoc/src/io_utils.dart';
import 'package:dartdoc/src/tool_configuration.dart';
import 'package:dartdoc/src/tool_definition.dart';
import 'package:path/path.dart' as path;

Expand Down
2 changes: 1 addition & 1 deletion test/tool_runner_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

import 'dart:io';

import 'package:dartdoc/src/dartdoc_options.dart';
import 'package:dartdoc/src/package_meta.dart';
import 'package:dartdoc/src/tool_configuration.dart';
import 'package:dartdoc/src/tool_definition.dart';
import 'package:dartdoc/src/tool_runner.dart';
import 'package:path/path.dart' as path;
Expand Down