| // Copyright (c) 2017, 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. |
| |
| /// |
| /// dartdoc's dartdoc_options.yaml configuration file follows similar loading |
| /// semantics to that of analysis_options.yaml, |
| /// [documented here](https://www.dartlang.org/guides/language/analysis-options). |
| /// It searches parent directories until it finds an analysis_options.yaml file, |
| /// and uses built-in defaults if one is not found. |
| /// |
| /// The classes here manage both the dartdoc_options.yaml loading and command |
| /// line arguments. |
| /// |
| library dartdoc.dartdoc_options; |
| |
| import 'dart:async'; |
| import 'dart:io'; |
| |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:args/args.dart'; |
| import 'package:dartdoc/dartdoc.dart'; |
| import 'package:dartdoc/src/experiment_options.dart'; |
| import 'package:dartdoc/src/io_utils.dart'; |
| import 'package:dartdoc/src/tool_runner.dart'; |
| import 'package:dartdoc/src/tuple.dart'; |
| import 'package:dartdoc/src/warnings.dart'; |
| import 'package:path/path.dart' as pathLib; |
| import 'package:yaml/yaml.dart'; |
| |
| /// Constants to help with type checking, because T is int and so forth |
| /// don't work in Dart. |
| const String _kStringVal = ''; |
| const List<String> _kListStringVal = const <String>[]; |
| const Map<String, String> _kMapStringVal = const <String, String>{}; |
| const int _kIntVal = 0; |
| const double _kDoubleVal = 0.0; |
| const bool _kBoolVal = true; |
| |
| /// Args are computed relative to the current directory at the time the |
| /// program starts. |
| final Directory directoryCurrent = Directory.current; |
| final String directoryCurrentPath = |
| pathLib.canonicalize(Directory.current.path); |
| |
| class DartdocOptionError extends DartdocFailure { |
| DartdocOptionError(String details) : super(details); |
| } |
| |
| class DartdocFileMissing extends DartdocOptionError { |
| DartdocFileMissing(String details) : super(details); |
| } |
| |
| /// Defines the attributes of a category in the options file, corresponding to |
| /// the 'categories' keyword in the options file, and populated by the |
| /// [CategoryConfiguration] class. |
| class CategoryDefinition { |
| /// Internal name of the category. |
| final String name; |
| |
| /// Displayed name of the category in docs, or null if there is none. |
| final String _displayName; |
| |
| /// Canonical path of the markdown file used to document this category |
| /// (or null if undocumented). |
| final String documentationMarkdown; |
| |
| CategoryDefinition(this.name, this._displayName, this.documentationMarkdown); |
| |
| /// Returns the [_displayName], if available, or else simply [name]. |
| String get displayName => _displayName ?? name; |
| } |
| |
| /// A configuration class that can interpret category definitions from a YAML |
| /// map. |
| class CategoryConfiguration { |
| /// A map of [CategoryDefinition.name] to [CategoryDefinition] objects. |
| final Map<String, CategoryDefinition> categoryDefinitions; |
| |
| CategoryConfiguration._(this.categoryDefinitions); |
| |
| static CategoryConfiguration get empty { |
| return new CategoryConfiguration._({}); |
| } |
| |
| static CategoryConfiguration fromYamlMap( |
| YamlMap yamlMap, pathLib.Context pathContext) { |
| Map<String, CategoryDefinition> newCategoryDefinitions = {}; |
| for (MapEntry entry in yamlMap.entries) { |
| String name = entry.key.toString(); |
| String displayName; |
| String documentationMarkdown; |
| var categoryMap = entry.value; |
| if (categoryMap is Map) { |
| displayName = categoryMap['displayName']?.toString(); |
| documentationMarkdown = categoryMap['markdown']?.toString(); |
| if (documentationMarkdown != null) { |
| documentationMarkdown = |
| pathContext.canonicalize(documentationMarkdown); |
| if (!new File(documentationMarkdown).existsSync()) { |
| throw new DartdocFileMissing( |
| 'In categories definition for ${name}, "markdown" resolves to the missing file $documentationMarkdown'); |
| } |
| } |
| newCategoryDefinitions[name] = |
| new CategoryDefinition(name, displayName, documentationMarkdown); |
| } |
| } |
| return new CategoryConfiguration._(newCategoryDefinitions); |
| } |
| } |
| |
| /// Defines the attributes of a tool in the options file, corresponding to |
| /// the 'tools' keyword in the options file, and populated by the |
| /// [ToolConfiguration] class. |
| class ToolDefinition { |
| /// A list containing the command and options to be run for this tool. The |
| /// first argument in the command is the tool executable, and will have its |
| /// path evaluated relative to the `dartdoc_options.yaml` location. Must not |
| /// be an empty list, or be null. |
| final List<String> command; |
| |
| /// A list containing the command and options to setup phase for this tool. |
| /// The first argument in the command is the tool executable, and will have |
| /// its path evaluated relative to the `dartdoc_options.yaml` location. May |
| /// be null or empty, in which case it will be ignored at setup time. |
| final List<String> setupCommand; |
| |
| /// A description of the defined tool. Must not be null. |
| final String description; |
| |
| /// If set, then the setup command has been run once for this tool definition. |
| bool setupComplete = false; |
| |
| /// Returns true if the given executable path has an extension recognized as a |
| /// Dart extension (e.g. '.dart' or '.snapshot'). |
| static bool isDartExecutable(String executable) { |
| var extension = pathLib.extension(executable); |
| return extension == '.dart' || extension == '.snapshot'; |
| } |
| |
| /// Creates a ToolDefinition or subclass that is appropriate for the command |
| /// given. |
| factory ToolDefinition.fromCommand( |
| List<String> command, List<String> setupCommand, String description) { |
| assert(command != null); |
| assert(command.isNotEmpty); |
| assert(description != null); |
| if (isDartExecutable(command[0])) { |
| return DartToolDefinition(command, setupCommand, description); |
| } else { |
| return ToolDefinition(command, setupCommand, description); |
| } |
| } |
| |
| ToolDefinition(this.command, this.setupCommand, this.description) |
| : assert(command != null), |
| assert(command.isNotEmpty), |
| assert(description != null); |
| |
| @override |
| String toString() { |
| final String commandString = |
| '${this is DartToolDefinition ? '(Dart) ' : ''}"${command.join(' ')}"'; |
| if (setupCommand == null) { |
| return '$runtimeType: $commandString ($description)'; |
| } else { |
| return '$runtimeType: $commandString, with setup command ' |
| '"${setupCommand.join(' ')}" ($description)'; |
| } |
| } |
| } |
| |
| /// Manages the creation of a single snapshot file in a context where multiple |
| /// async functions could be trying to use and/or create it. |
| /// |
| /// To use: |
| /// |
| /// var s = new Snapshot(...); |
| /// |
| /// if (s.needsSnapshot) { |
| /// // create s.snapshotFile, then call: |
| /// s.snapshotCompleted(); |
| /// } else { |
| /// await snapshotValid(); |
| /// // use existing s.snapshotFile; |
| /// } |
| /// |
| class Snapshot { |
| File _snapshotFile; |
| File get snapshotFile => _snapshotFile; |
| final Completer _snapshotCompleter = Completer(); |
| |
| Snapshot(Directory snapshotCache, String toolPath, int serial) { |
| if (toolPath.endsWith('.snapshot')) { |
| _needsSnapshot = false; |
| _snapshotFile = File(toolPath); |
| snapshotCompleted(); |
| } else { |
| _snapshotFile = |
| File(pathLib.join(snapshotCache.absolute.path, 'snapshot_$serial')); |
| } |
| } |
| |
| bool _needsSnapshot = true; |
| |
| /// Will return true precisely once, unless [toolPath] was already a snapshot. |
| /// In that case, will always return false. |
| bool get needsSnapshot { |
| if (_needsSnapshot == true) { |
| _needsSnapshot = false; |
| return true; |
| } |
| return _needsSnapshot; |
| } |
| |
| Future<void> snapshotValid() => _snapshotCompleter.future; |
| void snapshotCompleted() => _snapshotCompleter.complete(); |
| } |
| |
| /// A singleton that keeps track of cached snapshot files. The [dispose] |
| /// function must be called before process exit to clean up snapshots in the |
| /// cache. |
| class SnapshotCache { |
| static SnapshotCache _instance; |
| |
| Directory snapshotCache; |
| final Map<String, Snapshot> snapshots = {}; |
| int _serial = 0; |
| |
| SnapshotCache._() |
| : snapshotCache = |
| Directory.systemTemp.createTempSync('dartdoc_snapshot_cache_'); |
| |
| static SnapshotCache get instance { |
| _instance ??= SnapshotCache._(); |
| return _instance; |
| } |
| |
| Snapshot getSnapshot(String toolPath) { |
| if (snapshots.containsKey(toolPath)) { |
| return snapshots[toolPath]; |
| } |
| snapshots[toolPath] = new Snapshot(snapshotCache, toolPath, _serial); |
| _serial++; |
| return snapshots[toolPath]; |
| } |
| |
| Future<void> dispose() { |
| _instance = null; |
| if (snapshotCache != null && snapshotCache.existsSync()) { |
| return snapshotCache.delete(recursive: true); |
| } |
| return null; |
| } |
| } |
| |
| /// A special kind of tool definition for Dart commands. |
| class DartToolDefinition extends ToolDefinition { |
| /// Takes a list of args to modify, and returns the name of the executable |
| /// to run. If no snapshot file existed, then create one and modify the args |
| /// so that if they are executed with dart, will result in the snapshot being |
| /// built. |
| Future<Tuple2<String, Function()>> modifyArgsToCreateSnapshotIfNeeded( |
| List<String> args) async { |
| assert(args[0] == command.first); |
| // Set up flags to create a new snapshot, if needed, and use the first run as the training |
| // run. |
| Snapshot snapshot = SnapshotCache.instance.getSnapshot(command.first); |
| File snapshotFile = snapshot.snapshotFile; |
| bool needsSnapshot = snapshot.needsSnapshot; |
| if (needsSnapshot) { |
| args.insertAll(0, [ |
| '--snapshot=${snapshotFile.absolute.path}', |
| '--snapshot_kind=app-jit' |
| ]); |
| } else { |
| await snapshot.snapshotValid(); |
| // replace the first argument with the path to the snapshot. |
| args[0] = snapshotFile.absolute.path; |
| } |
| return new Tuple2(Platform.resolvedExecutable, |
| needsSnapshot ? snapshot.snapshotCompleted : null); |
| } |
| |
| DartToolDefinition( |
| List<String> command, List<String> setupCommand, String description) |
| : super(command, setupCommand, description); |
| } |
| |
| /// A configuration class that can interpret [ToolDefinition]s from a YAML map. |
| class ToolConfiguration { |
| final Map<String, ToolDefinition> tools; |
| |
| ToolRunner _runner; |
| ToolRunner get runner => _runner ??= ToolRunner(this); |
| |
| ToolConfiguration._(this.tools); |
| |
| static ToolConfiguration get empty { |
| return new ToolConfiguration._({}); |
| } |
| |
| // TODO(jcollins-g): consider caching these. |
| static ToolConfiguration fromYamlMap( |
| YamlMap yamlMap, pathLib.Context pathContext) { |
| var newToolDefinitions = <String, ToolDefinition>{}; |
| for (var entry in yamlMap.entries) { |
| var name = entry.key.toString(); |
| var toolMap = entry.value; |
| var description; |
| List<String> command; |
| List<String> setupCommand; |
| if (toolMap is Map) { |
| description = toolMap['description']?.toString(); |
| List<String> findCommand([String prefix = '']) { |
| List<String> command; |
| // If the command key is given, then it applies to all platforms. |
| var commandFrom = toolMap.containsKey('${prefix}command') |
| ? '${prefix}command' |
| : '$prefix${Platform.operatingSystem}'; |
| if (toolMap.containsKey(commandFrom)) { |
| if (toolMap[commandFrom].value is String) { |
| command = [toolMap[commandFrom].toString()]; |
| if (command[0].isEmpty) { |
| throw new DartdocOptionError( |
| 'Tool commands must not be empty. Tool $name command entry ' |
| '"$commandFrom" must contain at least one path.'); |
| } |
| } else if (toolMap[commandFrom] is YamlList) { |
| command = (toolMap[commandFrom] as YamlList) |
| .map<String>((node) => node.toString()) |
| .toList(); |
| if (command.isEmpty) { |
| throw new DartdocOptionError( |
| 'Tool commands must not be empty. Tool $name command entry ' |
| '"$commandFrom" must contain at least one path.'); |
| } |
| } else { |
| throw new 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 $commandFrom entry that is a ' |
| '${toolMap[commandFrom].runtimeType}'); |
| } |
| } |
| return command; |
| } |
| |
| command = findCommand(); |
| setupCommand = findCommand('setup_'); |
| } else { |
| throw new DartdocOptionError( |
| 'Tools must be defined as a map of tool names to definitions. Tool ' |
| '$name is not a map.'); |
| } |
| if (command == null) { |
| throw new DartdocOptionError( |
| 'At least one of "command" or "${Platform.operatingSystem}" must ' |
| 'be defined for the tool $name.'); |
| } |
| bool validateExecutable(String executable) { |
| bool isExecutable(int mode) { |
| return (0x1 & ((mode >> 6) | (mode >> 3) | mode)) != 0; |
| } |
| |
| var executableFile = new File(executable); |
| var exeStat = executableFile.statSync(); |
| if (exeStat.type == FileSystemEntityType.notFound) { |
| throw new 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 && !isExecutable(exeStat.mode)) { |
| throw new DartdocOptionError('Non-Dart commands must be ' |
| 'executable. The file "$executable" for tool $name does not have ' |
| 'execute permission.'); |
| } |
| return isDartCommand; |
| } |
| |
| var executable = pathContext.canonicalize(command.removeAt(0)); |
| validateExecutable(executable); |
| if (setupCommand != null) { |
| var setupExecutable = |
| pathContext.canonicalize(setupCommand.removeAt(0)); |
| var isDartSetupCommand = validateExecutable(executable); |
| // Setup commands aren't snapshotted, since they're only run once. |
| setupCommand = (isDartSetupCommand |
| ? [Platform.resolvedExecutable, setupExecutable] |
| : [setupExecutable]) + |
| setupCommand; |
| } |
| newToolDefinitions[name] = new ToolDefinition.fromCommand( |
| [executable] + command, setupCommand, description); |
| } |
| return new ToolConfiguration._(newToolDefinitions); |
| } |
| } |
| |
| /// A container class to keep track of where our yaml data came from. |
| class _YamlFileData { |
| /// The map from the yaml file. |
| final Map data; |
| |
| /// The path to the directory containing the yaml file. |
| final String canonicalDirectoryPath; |
| |
| _YamlFileData(this.data, this.canonicalDirectoryPath); |
| } |
| |
| /// Some DartdocOption subclasses need to keep track of where they |
| /// got the value from; this class contains those intermediate results |
| /// so that error messages can be more useful. |
| class _OptionValueWithContext<T> { |
| /// The value of the option at canonicalDirectoryPath. |
| final T value; |
| |
| /// A canonical path to the directory where this value came from. May |
| /// be different from [DartdocOption.valueAt]'s `dir` parameter. |
| String canonicalDirectoryPath; |
| |
| /// If non-null, the basename of the configuration file the value came from. |
| String definingFile; |
| |
| /// A [pathLib.Context] variable initialized with canonicalDirectoryPath. |
| pathLib.Context pathContext; |
| |
| /// Build a _OptionValueWithContext. |
| /// [path] is the path where this value came from (not required to be canonical) |
| _OptionValueWithContext(this.value, String path, {String definingFile}) { |
| this.definingFile = definingFile; |
| canonicalDirectoryPath = pathLib.canonicalize(path); |
| pathContext = new pathLib.Context(current: canonicalDirectoryPath); |
| } |
| |
| /// Assume value is a path, and attempt to resolve it. Throws [UnsupportedError] |
| /// if [T] isn't a [String] or [List<String>]. |
| T get resolvedValue { |
| if (value is List<String>) { |
| return (value as List<String>) |
| .map((v) => pathContext.canonicalize(resolveTildePath(v))) |
| .cast<String>() |
| .toList() as T; |
| } else if (value is String) { |
| return pathContext.canonicalize(resolveTildePath(value as String)) as T; |
| } else if (value is Map<String, String>) { |
| return (value as Map<String, String>) |
| .map<String, String>((String key, String value) { |
| return new MapEntry( |
| key, pathContext.canonicalize(resolveTildePath(value))); |
| }) as T; |
| } else { |
| throw new UnsupportedError('Type $T is not supported for resolvedValue'); |
| } |
| } |
| } |
| |
| /// An abstract class for interacting with dartdoc options. |
| /// |
| /// This class and its implementations allow Dartdoc to declare options |
| /// that are both defined in a configuration file and specified via the |
| /// command line, with searching the directory tree for a proper file |
| /// and overriding file options with the command line built-in. A number |
| /// of sanity checks are also built in to these classes so that file existence |
| /// can be verified, types constrained, and defaults provided. |
| /// |
| /// Use via implementations [DartdocOptionSet], [DartdocOptionArgFile], |
| /// [DartdocOptionArgOnly], and [DartdocOptionFileOnly]. |
| abstract class DartdocOption<T> { |
| /// This is the value returned if we couldn't find one otherwise. |
| final T defaultsTo; |
| |
| /// Text string for help passed on in command line options. |
| final String help; |
| |
| /// The name of this option, not including the names of any parents. |
| final String name; |
| |
| /// Set to true if this option represents the name of a directory. |
| final bool isDir; |
| |
| /// Set to true if this option represents the name of a file. |
| final bool isFile; |
| |
| /// Set to true if DartdocOption subclasses should validate that the |
| /// directory or file exists. Does not imply validation of [defaultsTo], |
| /// and requires that one of [isDir] or [isFile] is set. |
| final bool mustExist; |
| |
| DartdocOption(this.name, this.defaultsTo, this.help, this.isDir, this.isFile, |
| this.mustExist, this._convertYamlToType) { |
| assert(!(isDir && isFile)); |
| if (isDir || isFile) assert(_isString || _isListString || _isMapString); |
| if (mustExist) { |
| assert(isDir || isFile); |
| } |
| } |
| |
| /// Closure to convert yaml data into some other structure. |
| T Function(YamlMap, pathLib.Context) _convertYamlToType; |
| |
| // The choice not to use reflection means there's some ugly type checking, |
| // somewhat more ugly than we'd have to do anyway to automatically convert |
| // command line arguments and yaml data to real types. |
| // |
| // Condense the ugly all in one place, this set of getters. |
| bool get _isString => _kStringVal is T; |
| bool get _isListString => _kListStringVal is T; |
| bool get _isMapString => _kMapStringVal is T; |
| bool get _isBool => _kBoolVal is T; |
| bool get _isInt => _kIntVal is T; |
| bool get _isDouble => _kDoubleVal is T; |
| |
| DartdocOption _parent; |
| |
| /// The parent of this DartdocOption, or null if this is the root. |
| DartdocOption get parent => _parent; |
| |
| final Map<String, _YamlFileData> __yamlAtCanonicalPathCache = {}; |
| |
| /// Implementation detail for [DartdocOptionFileOnly]. Make sure we use |
| /// the root node's cache. |
| Map<String, _YamlFileData> get _yamlAtCanonicalPathCache => |
| root.__yamlAtCanonicalPathCache; |
| |
| final ArgParser __argParser = new ArgParser(); |
| ArgParser get argParser => root.__argParser; |
| |
| ArgResults __argResults; |
| |
| /// Parse these as string arguments (from argv) with the argument parser. |
| /// Call before calling [valueAt] for any [DartdocOptionArgOnly] or |
| /// [DartdocOptionArgFile] in this tree. |
| void _parseArguments(List<String> arguments) { |
| __argResults = argParser.parse(arguments); |
| } |
| |
| /// Throw [DartdocFileMissing] with a detailed error message indicating where |
| /// the error came from when a file or directory option is missing. |
| void _onMissing( |
| _OptionValueWithContext valueWithContext, String missingFilename); |
| |
| /// Call [_onMissing] for every path that does not exist. |
| void _validatePaths(_OptionValueWithContext valueWithContext) { |
| if (!mustExist) return; |
| assert(isDir || isFile); |
| List<String> resolvedPaths; |
| if (valueWithContext.value is String) { |
| resolvedPaths = [valueWithContext.resolvedValue]; |
| } else if (valueWithContext.value is List<String>) { |
| resolvedPaths = valueWithContext.resolvedValue.toList(); |
| } else if (valueWithContext.value is Map<String, String>) { |
| resolvedPaths = valueWithContext.resolvedValue.values.toList(); |
| } else { |
| assert( |
| false, |
| "Trying to ensure existence of unsupported type " |
| "${valueWithContext.value.runtimeType}"); |
| } |
| for (String path in resolvedPaths) { |
| FileSystemEntity f = isDir ? new Directory(path) : new File(path); |
| if (!f.existsSync()) { |
| _onMissing(valueWithContext, path); |
| } |
| } |
| } |
| |
| /// For a [List<String>] or [String] value, if [isDir] or [isFile] is set, |
| /// resolve paths in value relative to canonicalPath. |
| T _handlePathsInContext(_OptionValueWithContext valueWithContext) { |
| if (valueWithContext?.value == null || !(isDir || isFile)) |
| return valueWithContext?.value; |
| _validatePaths(valueWithContext); |
| return valueWithContext.resolvedValue; |
| } |
| |
| /// Call this with argv to set up the argument overrides. Applies to all |
| /// children. |
| void parseArguments(List<String> arguments) => |
| root._parseArguments(arguments); |
| ArgResults get _argResults => root.__argResults; |
| |
| /// Set the parent of this [DartdocOption]. Do not call more than once. |
| set parent(DartdocOption newParent) { |
| assert(_parent == null); |
| _parent = newParent; |
| } |
| |
| /// The root [DartdocOption] containing this object, or [this] if the object |
| /// has no parent. |
| DartdocOption get root { |
| DartdocOption p = this; |
| while (p.parent != null) { |
| p = p.parent; |
| } |
| return p; |
| } |
| |
| /// All object names starting at the root. |
| Iterable<String> get keys { |
| List<String> keyList = []; |
| DartdocOption option = this; |
| while (option?.name != null) { |
| keyList.add(option.name); |
| option = option.parent; |
| } |
| return keyList.reversed; |
| } |
| |
| /// Direct children of this node, mapped by name. |
| final Map<String, DartdocOption> _children = {}; |
| |
| /// Return the calculated value of this option, given the directory as context. |
| /// |
| /// If [isFile] or [isDir] is set, the returned value will be transformed |
| /// into a canonical path relative to the current working directory |
| /// (for arguments) or the config file from which the value was derived. |
| /// |
| /// May throw [DartdocOptionError] if a command line argument is of the wrong |
| /// type. If [mustExist] is true, will throw [DartdocFileMissing] for command |
| /// line parameters and file paths in config files that don't point to |
| /// corresponding files or directories. |
| T valueAt(Directory dir); |
| |
| /// Calls [valueAt] with the working directory at the start of the program. |
| T valueAtCurrent() => valueAt(directoryCurrent); |
| |
| /// Calls [valueAt] on the directory this element is defined in. |
| T valueAtElement(Element element) => valueAt(new Directory( |
| pathLib.canonicalize(pathLib.basename(element.source.fullName)))); |
| |
| /// Adds a DartdocOption to the children of this DartdocOption. |
| void add(DartdocOption option) { |
| if (_children.containsKey(option.name)) |
| throw new DartdocOptionError( |
| 'Tried to add two children with the same name: ${option.name}'); |
| _children[option.name] = option; |
| option.parent = this; |
| option.traverse((option) => option._onAdd()); |
| } |
| |
| /// This method is guaranteed to be called when [this] or any parent is added. |
| void _onAdd() {} |
| |
| /// Adds a list of dartdoc options to the children of this DartdocOption. |
| void addAll(Iterable<DartdocOption> options) => |
| options.forEach((o) => add(o)); |
| |
| /// Get the immediate child of this node named [name]. |
| DartdocOption operator [](String name) { |
| return _children[name]; |
| } |
| |
| /// Apply the function [visit] to [this] and all children. |
| void traverse(void visit(DartdocOption)) { |
| visit(this); |
| _children.values.forEach((d) => d.traverse(visit)); |
| } |
| } |
| |
| /// A class that defaults to a value computed from a closure, but can be |
| /// overridden by a file. |
| class DartdocOptionFileSynth<T> extends DartdocOption<T> |
| with DartdocSyntheticOption<T>, _DartdocFileOption<T> { |
| bool _parentDirOverridesChild; |
| @override |
| T Function(DartdocSyntheticOption<T>, Directory) _compute; |
| DartdocOptionFileSynth(String name, this._compute, |
| {bool mustExist = false, |
| String help = '', |
| bool isDir = false, |
| bool isFile = false, |
| bool parentDirOverridesChild, |
| T Function(YamlMap, pathLib.Context) convertYamlToType}) |
| : super(name, null, help, isDir, isFile, mustExist, convertYamlToType) { |
| _parentDirOverridesChild = parentDirOverridesChild; |
| } |
| |
| @override |
| T valueAt(Directory dir) { |
| _OptionValueWithContext result = _valueAtFromFile(dir); |
| if (result?.definingFile != null) { |
| return _handlePathsInContext(result); |
| } |
| return _valueAtFromSynthetic(dir); |
| } |
| |
| @override |
| void _onMissing( |
| _OptionValueWithContext valueWithContext, String missingPath) { |
| if (valueWithContext.definingFile != null) { |
| _onMissingFromFiles(valueWithContext, missingPath); |
| } else { |
| _onMissingFromSynthetic(valueWithContext, missingPath); |
| } |
| } |
| |
| @override |
| bool get parentDirOverridesChild => _parentDirOverridesChild; |
| } |
| |
| /// A class that defaults to a value computed from a closure, but can |
| /// be overridden on the command line. |
| class DartdocOptionArgSynth<T> extends DartdocOption<T> |
| with DartdocSyntheticOption<T>, _DartdocArgOption<T> { |
| String _abbr; |
| bool _hide; |
| bool _negatable; |
| bool _splitCommas; |
| |
| @override |
| T Function(DartdocSyntheticOption<T>, Directory) _compute; |
| DartdocOptionArgSynth(String name, this._compute, |
| {String abbr, |
| bool mustExist = false, |
| String help = '', |
| bool hide = false, |
| bool isDir = false, |
| bool isFile = false, |
| bool negatable = false, |
| bool splitCommas}) |
| : super(name, null, help, isDir, isFile, mustExist, null) { |
| _hide = hide; |
| _negatable = negatable; |
| _splitCommas = splitCommas; |
| _abbr = abbr; |
| } |
| |
| @override |
| void _onMissing( |
| _OptionValueWithContext valueWithContext, String missingPath) { |
| _onMissingFromArgs(valueWithContext, missingPath); |
| } |
| |
| @override |
| T valueAt(Directory dir) { |
| if (_argResults.wasParsed(argName)) { |
| return _valueAtFromArgs(); |
| } |
| return _valueAtFromSynthetic(dir); |
| } |
| |
| @override |
| String get abbr => _abbr; |
| @override |
| bool get hide => _hide; |
| @override |
| bool get negatable => _negatable; |
| |
| @override |
| bool get splitCommas => _splitCommas; |
| } |
| |
| /// A synthetic option takes a closure at construction time that computes |
| /// the value of the configuration option based on other configuration options. |
| /// Does not protect against closures that self-reference. If [mustExist] and |
| /// [isDir] or [isFile] is set, computed values will be resolved to canonical |
| /// paths. |
| class DartdocOptionSyntheticOnly<T> extends DartdocOption<T> |
| with DartdocSyntheticOption<T> { |
| @override |
| T Function(DartdocSyntheticOption<T>, Directory) _compute; |
| DartdocOptionSyntheticOnly(String name, this._compute, |
| {bool mustExist = false, |
| String help = '', |
| bool isDir = false, |
| bool isFile = false}) |
| : super(name, null, help, isDir, isFile, mustExist, null); |
| } |
| |
| abstract class DartdocSyntheticOption<T> implements DartdocOption<T> { |
| T Function(DartdocSyntheticOption<T>, Directory) get _compute; |
| |
| @override |
| T valueAt(Directory dir) => _valueAtFromSynthetic(dir); |
| |
| T _valueAtFromSynthetic(Directory dir) { |
| _OptionValueWithContext context = |
| new _OptionValueWithContext<T>(_compute(this, dir), dir.path); |
| return _handlePathsInContext(context); |
| } |
| |
| @override |
| void _onMissing( |
| _OptionValueWithContext valueWithContext, String missingPath) => |
| _onMissingFromSynthetic(valueWithContext, missingPath); |
| |
| void _onMissingFromSynthetic( |
| _OptionValueWithContext valueWithContext, String missingPath) { |
| String description = |
| 'Synthetic configuration option ${name} from <internal>'; |
| throw new DartdocFileMissing( |
| '$description, computed as ${valueWithContext.value}, resolves to missing path: "${missingPath}"'); |
| } |
| } |
| |
| typedef Future<List<DartdocOption>> OptionGenerator(); |
| |
| /// A [DartdocOption] that only contains other [DartdocOption]s and is not an option itself. |
| class DartdocOptionSet extends DartdocOption<Null> { |
| DartdocOptionSet(String name) |
| : super(name, null, null, false, false, false, null); |
| |
| /// Asynchronous factory that is the main entry point to initialize Dartdoc |
| /// options for use. |
| /// |
| /// [name] is the top level key for the option set. |
| /// [optionGenerators] is a sequence of asynchronous functions that return |
| /// [DartdocOption]s that will be added to the new option set. |
| static Future<DartdocOptionSet> fromOptionGenerators( |
| String name, Iterable<OptionGenerator> optionGenerators) async { |
| DartdocOptionSet optionSet = new DartdocOptionSet(name); |
| for (OptionGenerator generator in optionGenerators) { |
| optionSet.addAll(await generator()); |
| } |
| return optionSet; |
| } |
| |
| /// [DartdocOptionSet] always has the null value. |
| @override |
| Null valueAt(Directory dir) => null; |
| |
| /// Since we have no value, [_onMissing] does nothing. |
| @override |
| void _onMissing( |
| _OptionValueWithContext valueWithContext, String missingFilename) {} |
| |
| /// Traverse skips this node, because it doesn't represent a real configuration object. |
| @override |
| void traverse(void visitor(DartdocOption)) { |
| _children.values.forEach((d) => d.traverse(visitor)); |
| } |
| } |
| |
| /// A [DartdocOption] that only exists as a command line argument. --help would |
| /// be a good example. |
| class DartdocOptionArgOnly<T> extends DartdocOption<T> |
| with _DartdocArgOption<T> { |
| String _abbr; |
| bool _hide; |
| bool _negatable; |
| bool _splitCommas; |
| |
| DartdocOptionArgOnly(String name, T defaultsTo, |
| {String abbr, |
| bool mustExist = false, |
| String help = '', |
| bool hide = false, |
| bool isDir = false, |
| bool isFile = false, |
| bool negatable = false, |
| bool splitCommas}) |
| : super(name, defaultsTo, help, isDir, isFile, mustExist, null) { |
| _hide = hide; |
| _negatable = negatable; |
| _splitCommas = splitCommas; |
| _abbr = abbr; |
| } |
| |
| @override |
| String get abbr => _abbr; |
| @override |
| bool get hide => _hide; |
| @override |
| bool get negatable => _negatable; |
| @override |
| bool get splitCommas => _splitCommas; |
| } |
| |
| /// A [DartdocOption] that works with command line arguments and dartdoc_options files. |
| class DartdocOptionArgFile<T> extends DartdocOption<T> |
| with _DartdocArgOption<T>, _DartdocFileOption<T> { |
| String _abbr; |
| bool _hide; |
| bool _negatable; |
| bool _parentDirOverridesChild; |
| bool _splitCommas; |
| |
| DartdocOptionArgFile(String name, T defaultsTo, |
| {String abbr, |
| bool mustExist = false, |
| String help: '', |
| bool hide = false, |
| bool isDir = false, |
| bool isFile = false, |
| bool negatable = false, |
| bool parentDirOverridesChild: false, |
| bool splitCommas}) |
| : super(name, defaultsTo, help, isDir, isFile, mustExist, null) { |
| _abbr = abbr; |
| _hide = hide; |
| _negatable = negatable; |
| _parentDirOverridesChild = parentDirOverridesChild; |
| _splitCommas = splitCommas; |
| } |
| |
| @override |
| void _onMissing( |
| _OptionValueWithContext valueWithContext, String missingPath) { |
| if (valueWithContext.definingFile != null) { |
| _onMissingFromFiles(valueWithContext, missingPath); |
| } else { |
| _onMissingFromArgs(valueWithContext, missingPath); |
| } |
| } |
| |
| /// Try to find an explicit argument setting this value, but if not, fall back to files |
| /// finally, the default. |
| @override |
| T valueAt(Directory dir) { |
| T value = _valueAtFromArgs(); |
| if (value == null) value = _valueAtFromFiles(dir); |
| if (value == null) value = defaultsTo; |
| return value; |
| } |
| |
| @override |
| String get abbr => _abbr; |
| @override |
| bool get hide => _hide; |
| @override |
| bool get negatable => _negatable; |
| @override |
| bool get parentDirOverridesChild => _parentDirOverridesChild; |
| @override |
| bool get splitCommas => _splitCommas; |
| } |
| |
| class DartdocOptionFileOnly<T> extends DartdocOption<T> |
| with _DartdocFileOption<T> { |
| bool _parentDirOverridesChild; |
| DartdocOptionFileOnly(String name, T defaultsTo, |
| {bool mustExist = false, |
| String help: '', |
| bool isDir = false, |
| bool isFile = false, |
| bool parentDirOverridesChild: false, |
| T Function(YamlMap, pathLib.Context) convertYamlToType}) |
| : super(name, defaultsTo, help, isDir, isFile, mustExist, |
| convertYamlToType) { |
| _parentDirOverridesChild = parentDirOverridesChild; |
| } |
| |
| @override |
| bool get parentDirOverridesChild => _parentDirOverridesChild; |
| } |
| |
| /// Implements checking for options contained in dartdoc.yaml. |
| abstract class _DartdocFileOption<T> implements DartdocOption<T> { |
| /// If true, the parent directory's value overrides the child's. Otherwise, the child's |
| /// value overrides values in parents. |
| bool get parentDirOverridesChild; |
| |
| /// The name of the option, with nested options joined by [.]. For example: |
| /// |
| /// ```yaml |
| /// dartdoc: |
| /// stuff: |
| /// things: |
| /// ``` |
| /// would have the name `things` and the fieldName `dartdoc.stuff.things`. |
| String get fieldName => keys.join('.'); |
| |
| @override |
| void _onMissing( |
| _OptionValueWithContext valueWithContext, String missingPath) => |
| _onMissingFromFiles(valueWithContext, missingPath); |
| |
| void _onMissingFromFiles( |
| _OptionValueWithContext valueWithContext, String missingPath) { |
| String dartdocYaml = pathLib.join( |
| valueWithContext.canonicalDirectoryPath, valueWithContext.definingFile); |
| throw new DartdocFileMissing( |
| 'Field ${fieldName} from ${dartdocYaml}, set to ${valueWithContext.value}, resolves to missing path: "${missingPath}"'); |
| } |
| |
| @override |
| |
| /// Searches for a value in configuration files relative to [dir], and if not |
| /// found, returns [defaultsTo]. |
| T valueAt(Directory dir) { |
| return _valueAtFromFiles(dir) ?? defaultsTo; |
| } |
| |
| final Map<String, T> __valueAtFromFiles = new Map(); |
| // The value of this option from files will not change unless files are |
| // modified during execution (not allowed in Dartdoc). |
| T _valueAtFromFiles(Directory dir) { |
| String key = pathLib.canonicalize(dir.path); |
| if (!__valueAtFromFiles.containsKey(key)) { |
| _OptionValueWithContext valueWithContext; |
| if (parentDirOverridesChild) { |
| valueWithContext = _valueAtFromFilesLastFound(dir); |
| } else { |
| valueWithContext = _valueAtFromFilesFirstFound(dir); |
| } |
| __valueAtFromFiles[key] = _handlePathsInContext(valueWithContext); |
| } |
| return __valueAtFromFiles[key]; |
| } |
| |
| /// Searches all dartdoc_options files through parent directories, |
| /// starting at [dir], for the option and returns one once |
| /// found. |
| _OptionValueWithContext _valueAtFromFilesFirstFound(Directory dir) { |
| _OptionValueWithContext value; |
| while (true) { |
| value = _valueAtFromFile(dir); |
| if (value != null || pathLib.equals(dir.parent.path, dir.path)) break; |
| dir = dir.parent; |
| } |
| return value; |
| } |
| |
| /// Searches all dartdoc_options files for the option, and returns the |
| /// value in the top-most parent directory dartdoc_options.yaml file it is |
| /// mentioned in. |
| _OptionValueWithContext _valueAtFromFilesLastFound(Directory dir) { |
| _OptionValueWithContext value; |
| while (true) { |
| _OptionValueWithContext tmpValue = _valueAtFromFile(dir); |
| if (tmpValue != null) value = tmpValue; |
| if (pathLib.equals(dir.parent.path, dir.path)) break; |
| dir = dir.parent; |
| } |
| return value; |
| } |
| |
| /// Returns null if not set in the yaml file in this directory (or its |
| /// parents). |
| _OptionValueWithContext _valueAtFromFile(Directory dir) { |
| _YamlFileData yamlFileData = _yamlAtDirectory(dir); |
| String contextPath = yamlFileData.canonicalDirectoryPath; |
| dynamic yamlData = yamlFileData.data; |
| for (String key in keys) { |
| if (!yamlData.containsKey(key)) return null; |
| yamlData = yamlData[key]; |
| } |
| |
| var returnData; |
| if (_isListString) { |
| if (yamlData is YamlList) { |
| returnData = <String>[]; |
| for (var item in yamlData) { |
| returnData.add(item.toString()); |
| } |
| } |
| } else if (yamlData is YamlMap) { |
| // TODO(jcollins-g): This special casing is unfortunate. Consider |
| // a refactor to extract yaml data conversion into closures 100% of the |
| // time or find a cleaner way to do this. |
| // |
| // A refactor probably would integrate resolvedValue for |
| // _OptionValueWithContext into the return data here, and would not have |
| // that be separate. |
| if (_isMapString && _convertYamlToType == null) { |
| _convertYamlToType = (YamlMap yamlMap, pathLib.Context pathContext) { |
| var returnData = <String, String>{}; |
| for (MapEntry entry in yamlMap.entries) { |
| returnData[entry.key.toString()] = entry.value.toString(); |
| } |
| return returnData as T; |
| }; |
| } |
| if (_convertYamlToType == null) { |
| throw new DartdocOptionError( |
| 'Unable to convert yaml to type for option: $fieldName, method not defined'); |
| } |
| String canonicalDirectoryPath = pathLib.canonicalize(contextPath); |
| returnData = _convertYamlToType( |
| yamlData, new pathLib.Context(current: canonicalDirectoryPath)); |
| } else if (_isDouble) { |
| if (yamlData is num) { |
| returnData = yamlData.toDouble(); |
| } |
| } else if (_isInt || _isString || _isBool) { |
| if (yamlData is T) { |
| returnData = yamlData; |
| } |
| } else { |
| throw new UnsupportedError('Type ${T} is not supported'); |
| } |
| return new _OptionValueWithContext(returnData as T, contextPath, |
| definingFile: 'dartdoc_options.yaml'); |
| } |
| |
| _YamlFileData _yamlAtDirectory(Directory dir) { |
| List<String> canonicalPaths = [pathLib.canonicalize(dir.path)]; |
| if (!_yamlAtCanonicalPathCache.containsKey(canonicalPaths.first)) { |
| _YamlFileData yamlData = |
| new _YamlFileData(new Map(), directoryCurrentPath); |
| if (dir.existsSync()) { |
| File dartdocOptionsFile; |
| |
| while (true) { |
| dartdocOptionsFile = |
| new File(pathLib.join(dir.path, 'dartdoc_options.yaml')); |
| if (dartdocOptionsFile.existsSync() || |
| pathLib.equals(dir.parent.path, dir.path)) break; |
| dir = dir.parent; |
| canonicalPaths.add(pathLib.canonicalize(dir.path)); |
| } |
| if (dartdocOptionsFile.existsSync()) { |
| yamlData = new _YamlFileData( |
| loadYaml(dartdocOptionsFile.readAsStringSync()), |
| pathLib.canonicalize(dir.path)); |
| } |
| } |
| canonicalPaths.forEach((p) => _yamlAtCanonicalPathCache[p] = yamlData); |
| } |
| return _yamlAtCanonicalPathCache[canonicalPaths.first]; |
| } |
| } |
| |
| /// Mixin class implementing command-line arguments for [DartdocOption]. |
| abstract class _DartdocArgOption<T> implements DartdocOption<T> { |
| /// For [ArgParser], set to true if the argument can be negated with --no on the command line. |
| bool get negatable; |
| |
| /// For [ArgParser], set to true if a single string argument will be broken into a list on commas. |
| bool get splitCommas; |
| |
| /// For [ArgParser], set to true to hide this from the help menu. |
| bool get hide; |
| |
| /// For [ArgParser], set to a single character to have a short version of the command line argument. |
| String get abbr; |
| |
| /// valueAt for arguments ignores the [dir] parameter and only uses command |
| /// line arguments and the current working directory to resolve the result. |
| @override |
| T valueAt(Directory dir) => _valueAtFromArgs() ?? defaultsTo; |
| |
| /// For passing in to [int.parse] and [double.parse] `onError'. |
| void _throwErrorForTypes(String value) { |
| String example; |
| if (defaultsTo is Map) { |
| example = 'key::value'; |
| } else if (_isInt) { |
| example = '32'; |
| } else if (_isDouble) { |
| example = '0.76'; |
| } |
| throw new DartdocOptionError( |
| 'Invalid argument value: --${argName}, set to "${value}", must be a ${T}. Example: --${argName} ${example}'); |
| } |
| |
| /// Returns null if no argument was given on the command line. |
| T _valueAtFromArgs() { |
| _OptionValueWithContext valueWithContext = _valueAtFromArgsWithContext(); |
| return _handlePathsInContext(valueWithContext); |
| } |
| |
| @override |
| void _onMissing( |
| _OptionValueWithContext valueWithContext, String missingPath) => |
| _onMissingFromArgs(valueWithContext, missingPath); |
| |
| void _onMissingFromArgs( |
| _OptionValueWithContext valueWithContext, String missingPath) { |
| throw new DartdocFileMissing( |
| 'Argument --${argName}, set to ${valueWithContext.value}, resolves to missing path: "${missingPath}"'); |
| } |
| |
| /// Generates an _OptionValueWithContext using the value of the argument from |
| /// the [argParser] and the working directory from [directoryCurrent]. |
| /// |
| /// Throws [UnsupportedError] if [T] is not a supported type. |
| _OptionValueWithContext _valueAtFromArgsWithContext() { |
| if (!_argResults.wasParsed(argName)) return null; |
| |
| T retval; |
| // Unlike in _DartdocFileOption, we throw here on inputs being invalid rather |
| // than silently proceeding. TODO(jcollins-g): throw on input formatting for |
| // files too? |
| if (_isBool || _isListString || _isString) { |
| retval = _argResults[argName]; |
| } else if (_isInt) { |
| retval = int.tryParse(_argResults[argName]) as T; |
| if (retval == null) _throwErrorForTypes(_argResults[argName]); |
| } else if (_isDouble) { |
| retval = double.tryParse(_argResults[argName]) as T; |
| if (retval == null) _throwErrorForTypes(_argResults[argName]); |
| } else if (_isMapString) { |
| retval = <String, String>{} as T; |
| for (String pair in _argResults[argName]) { |
| List<String> pairList = pair.split('::'); |
| if (pairList.length != 2) { |
| _throwErrorForTypes(pair); |
| } |
| assert(pairList.length == 2); |
| (retval as Map<String, String>)[pairList.first] = pairList.last; |
| } |
| } else { |
| throw UnsupportedError('Type ${T} is not supported'); |
| } |
| return new _OptionValueWithContext(retval, directoryCurrentPath); |
| } |
| |
| /// The name of this option as a command line argument. |
| String get argName => _keysToArgName(keys); |
| |
| /// Turns ['foo', 'somethingBar', 'over_the_hill'] into |
| /// 'something-bar-over-the-hill' (with default skip). |
| /// This allows argument names to reflect nested structure. |
| static String _keysToArgName(Iterable<String> keys, [int skip = 1]) { |
| String argName = "${keys.skip(skip).join('-')}"; |
| argName = argName.replaceAll('_', '-'); |
| // Do not consume the lowercase character after the uppercase one, to handle |
| // two character words. |
| final camelCaseRegexp = new RegExp(r'([a-z])([A-Z])(?=([a-z]))'); |
| argName = argName.replaceAllMapped(camelCaseRegexp, (Match m) { |
| String before = m.group(1); |
| String after = m.group(2).toLowerCase(); |
| return '${before}-${after}'; |
| }); |
| return argName; |
| } |
| |
| /// If this argument is added to a larger tree of DartdocOptions, call |
| /// [ArgParser.addFlag], [ArgParser.addOption], or [ArgParser.addMultiOption] |
| /// as appropriate for [T]. |
| @override |
| void _onAdd() { |
| if (_isBool) { |
| argParser.addFlag(argName, |
| abbr: abbr, |
| defaultsTo: defaultsTo as bool, |
| help: help, |
| hide: hide, |
| negatable: negatable ?? false); |
| } else if (_isInt || _isDouble || _isString) { |
| argParser.addOption(argName, |
| abbr: abbr, |
| defaultsTo: defaultsTo?.toString() ?? null, |
| help: help, |
| hide: hide); |
| } else if (_isListString || _isMapString) { |
| List<String> defaultsToList = []; |
| if (_isListString) { |
| defaultsToList = defaultsTo as List<String>; |
| } else { |
| defaultsToList.addAll((defaultsTo as Map<String, String>) |
| .entries |
| .map((m) => '${m.key}::${m.value}')); |
| } |
| argParser.addMultiOption(argName, |
| abbr: abbr, |
| defaultsTo: defaultsToList, |
| help: help, |
| hide: hide, |
| splitCommas: splitCommas); |
| } else { |
| throw new UnsupportedError('Type ${T} is not supported'); |
| } |
| } |
| } |
| |
| /// All DartdocOptionContext mixins should implement this, as well as any other |
| /// DartdocOptionContext mixins they use for calculating synthetic options. |
| abstract class DartdocOptionContextBase { |
| DartdocOptionSet get optionSet; |
| Directory get context; |
| } |
| |
| /// An [DartdocOptionSet] wrapped in nice accessors specific to Dartdoc, which |
| /// automatically passes in the right directory for a given context. Usually, |
| /// a single [ModelElement], [Package], [Category] and so forth has a single context |
| /// and so this can be made a member variable of those structures. |
| class DartdocOptionContext extends DartdocOptionContextBase |
| with DartdocExperimentOptionContext, PackageWarningOptionContext { |
| @override |
| final DartdocOptionSet optionSet; |
| @override |
| Directory context; |
| |
| // TODO(jcollins-g): Allow passing in structured data to initialize a |
| // [DartdocOptionContext]'s arguments instead of having to parse strings |
| // via optionSet. |
| /// If [entity] is null, assume this is the initialization case and use |
| /// the inputDir flag to determine the context. |
| DartdocOptionContext(this.optionSet, FileSystemEntity entity) { |
| if (entity == null) { |
| String inputDir = optionSet['inputDir'].valueAt(directoryCurrent) ?? |
| directoryCurrentPath; |
| context = new Directory(inputDir); |
| } else { |
| context = new Directory(pathLib |
| .canonicalize(entity is File ? entity.parent.path : entity.path)); |
| } |
| } |
| |
| /// Build a DartdocOptionContext from an analyzer element (using its source |
| /// location). |
| factory DartdocOptionContext.fromElement( |
| DartdocOptionSet optionSet, Element element) { |
| return new DartdocOptionContext( |
| optionSet, new File(element.source.fullName)); |
| } |
| |
| /// Build a DartdocOptionContext from an existing [DartdocOptionContext] and a new analyzer [Element]. |
| factory DartdocOptionContext.fromContextElement( |
| DartdocOptionContext optionContext, Element element) { |
| return new DartdocOptionContext.fromElement( |
| optionContext.optionSet, element); |
| } |
| |
| /// Build a DartdocOptionContext from an existing [DartdocOptionContext]. |
| factory DartdocOptionContext.fromContext( |
| DartdocOptionContext optionContext, FileSystemEntity entity) { |
| return new DartdocOptionContext(optionContext.optionSet, entity); |
| } |
| |
| // All values defined in createDartdocOptions should be exposed here. |
| bool get allowTools => optionSet['allowTools'].valueAt(context); |
| double get ambiguousReexportScorerMinConfidence => |
| optionSet['ambiguousReexportScorerMinConfidence'].valueAt(context); |
| bool get autoIncludeDependencies => |
| optionSet['autoIncludeDependencies'].valueAt(context); |
| List<String> get categoryOrder => optionSet['categoryOrder'].valueAt(context); |
| CategoryConfiguration get categories => |
| optionSet['categories'].valueAt(context); |
| List<String> get dropTextFrom => optionSet['dropTextFrom'].valueAt(context); |
| String get examplePathPrefix => |
| optionSet['examplePathPrefix'].valueAt(context); |
| List<String> get exclude => optionSet['exclude'].valueAt(context); |
| List<String> get excludePackages => |
| optionSet['excludePackages'].valueAt(context); |
| |
| String get flutterRoot => optionSet['flutterRoot'].valueAt(context); |
| bool get hideSdkText => optionSet['hideSdkText'].valueAt(context); |
| List<String> get include => optionSet['include'].valueAt(context); |
| List<String> get includeExternal => |
| optionSet['includeExternal'].valueAt(context); |
| bool get includeSource => optionSet['includeSource'].valueAt(context); |
| bool get injectHtml => optionSet['injectHtml'].valueAt(context); |
| ToolConfiguration get tools => optionSet['tools'].valueAt(context); |
| |
| /// _input is only used to construct synthetic options. |
| // ignore: unused_element |
| String get _input => optionSet['input'].valueAt(context); |
| String get inputDir => optionSet['inputDir'].valueAt(context); |
| bool get linkToRemote => optionSet['linkTo']['remote'].valueAt(context); |
| String get linkToUrl => optionSet['linkTo']['url'].valueAt(context); |
| |
| /// _linkToHosted is only used to construct synthetic options. |
| // ignore: unused_element |
| String get _linkToHosted => optionSet['linkTo']['hosted'].valueAt(context); |
| |
| String get output => optionSet['output'].valueAt(context); |
| PackageMeta get packageMeta => optionSet['packageMeta'].valueAt(context); |
| List<String> get packageOrder => optionSet['packageOrder'].valueAt(context); |
| bool get sdkDocs => optionSet['sdkDocs'].valueAt(context); |
| String get sdkDir => optionSet['sdkDir'].valueAt(context); |
| bool get showUndocumentedCategories => |
| optionSet['showUndocumentedCategories'].valueAt(context); |
| PackageMeta get topLevelPackageMeta => |
| optionSet['topLevelPackageMeta'].valueAt(context); |
| bool get useCategories => optionSet['useCategories'].valueAt(context); |
| bool get validateLinks => optionSet['validateLinks'].valueAt(context); |
| |
| bool isLibraryExcluded(String name) => |
| exclude.any((pattern) => name == pattern); |
| bool isPackageExcluded(String name) => |
| excludePackages.any((pattern) => name == pattern); |
| } |
| |
| /// Instantiate dartdoc's configuration file and options parser with the |
| /// given command line arguments. |
| Future<List<DartdocOption>> createDartdocOptions() async { |
| return <DartdocOption>[ |
| new DartdocOptionArgOnly<bool>('allowTools', true, |
| help: 'Execute user-defined tools to fill in @tool directives.', |
| negatable: true), |
| new DartdocOptionArgFile<double>( |
| 'ambiguousReexportScorerMinConfidence', 0.1, |
| help: 'Minimum scorer confidence to suppress warning on ambiguous ' |
| 'reexport.'), |
| new DartdocOptionArgOnly<bool>('autoIncludeDependencies', false, |
| help: 'Include all the used libraries into the docs, even the ones not ' |
| 'in the current package or "include-external"', |
| negatable: true), |
| new DartdocOptionArgFile<List<String>>('categoryOrder', [], |
| help: |
| "A list of categories (not package names) to place first when grouping symbols on dartdoc's sidebar. " |
| 'Unmentioned categories are sorted after these.'), |
| new DartdocOptionFileOnly<CategoryConfiguration>( |
| 'categories', CategoryConfiguration.empty, |
| convertYamlToType: CategoryConfiguration.fromYamlMap, |
| help: 'A list of all categories, their display names, and markdown ' |
| 'documentation in the order they are to be displayed.'), |
| new DartdocOptionSyntheticOnly<List<String>>('dropTextFrom', |
| (DartdocSyntheticOption<List<String>> option, Directory dir) { |
| if (option.parent['hideSdkText'].valueAt(dir)) { |
| return [ |
| 'dart.async', |
| 'dart.collection', |
| 'dart.convert', |
| 'dart.core', |
| 'dart.developer', |
| 'dart.html', |
| 'dart.indexed_db', |
| 'dart.io', |
| 'dart.lisolate', |
| 'dart.js', |
| 'dart.js_util', |
| 'dart.math', |
| 'dart.mirrors', |
| 'dart.svg', |
| 'dart.typed_data', |
| 'dart.web_audio' |
| ]; |
| } |
| return []; |
| }, help: 'Remove text from libraries with the following names.'), |
| new DartdocOptionArgFile<String>('examplePathPrefix', null, |
| isDir: true, |
| help: 'Prefix for @example paths.\n(defaults to the project root)', |
| mustExist: true), |
| new DartdocOptionArgFile<List<String>>('exclude', [], |
| help: 'Library names to ignore.', splitCommas: true), |
| new DartdocOptionArgOnly<List<String>>('excludePackages', [], |
| help: 'Package names to ignore.', splitCommas: true), |
| // This could be a ArgOnly, but trying to not provide too many ways |
| // to set the flutter root. |
| new DartdocOptionSyntheticOnly<String>( |
| 'flutterRoot', |
| (DartdocSyntheticOption<String> option, Directory dir) => |
| resolveTildePath(Platform.environment['FLUTTER_ROOT']), |
| isDir: true, |
| help: 'Root of the Flutter SDK, specified from environment.', |
| mustExist: true), |
| new DartdocOptionArgOnly<bool>('hideSdkText', false, |
| hide: true, |
| help: 'Drop all text for SDK components. Helpful for integration ' |
| 'tests for dartdoc, probably not useful for anything else.', |
| negatable: true), |
| new DartdocOptionArgFile<List<String>>('include', [], |
| help: 'Library names to generate docs for.', splitCommas: true), |
| new DartdocOptionArgFile<List<String>>('includeExternal', null, |
| isFile: true, |
| help: |
| 'Additional (external) dart files to include; use "dir/fileName", ' |
| 'as in lib/material.dart.', |
| mustExist: true, |
| splitCommas: true), |
| new DartdocOptionArgOnly<bool>('includeSource', true, |
| help: 'Show source code blocks.', negatable: true), |
| new DartdocOptionArgOnly<bool>('injectHtml', false, |
| help: 'Allow the use of the {@inject-html} directive to inject raw ' |
| 'HTML into dartdoc output.'), |
| new DartdocOptionArgOnly<String>('input', directoryCurrentPath, |
| isDir: true, help: 'Path to source directory', mustExist: true), |
| new DartdocOptionSyntheticOnly<String>('inputDir', |
| (DartdocSyntheticOption<String> option, Directory dir) { |
| if (option.parent['sdkDocs'].valueAt(dir)) { |
| return option.parent['sdkDir'].valueAt(dir); |
| } |
| return option.parent['input'].valueAt(dir); |
| }, |
| help: 'Path to source directory (with override if --sdk-docs)', |
| isDir: true, |
| mustExist: true), |
| new DartdocOptionSet('linkTo') |
| ..addAll([ |
| new DartdocOptionArgOnly<Map<String, String>>( |
| 'hosted', |
| { |
| 'pub.dartlang.org': |
| 'https://pub.dartlang.org/documentation/%n%/%v%' |
| }, |
| help: 'Specify URLs for hosted pub packages'), |
| new DartdocOptionArgOnly<Map<String, String>>( |
| 'sdks', |
| { |
| 'Dart': 'https://api.dartlang.org/%b%/%v%', |
| 'Flutter': 'https://docs.flutter.io/flutter', |
| }, |
| help: 'Specify URLs for SDKs.', |
| ), |
| new DartdocOptionFileSynth<String>('url', |
| (DartdocSyntheticOption<String> option, Directory dir) { |
| PackageMeta packageMeta = |
| option.parent.parent['packageMeta'].valueAt(dir); |
| // Prefer SDK check first, then pub cache check. |
| String inSdk = packageMeta |
| .sdkType(option.parent.parent['flutterRoot'].valueAt(dir)); |
| if (inSdk != null) { |
| Map<String, String> sdks = option.parent['sdks'].valueAt(dir); |
| if (sdks.containsKey(inSdk)) return sdks[inSdk]; |
| } |
| String hostedAt = packageMeta.hostedAt; |
| if (hostedAt != null) { |
| Map<String, String> hostMap = option.parent['hosted'].valueAt(dir); |
| if (hostMap.containsKey(hostedAt)) return hostMap[hostedAt]; |
| } |
| return ''; |
| }, help: 'Url to use for this particular package.'), |
| new DartdocOptionArgOnly<bool>('remote', false, |
| help: 'Allow links to be generated for packages outside this one.', |
| negatable: true), |
| ]), |
| new DartdocOptionArgOnly<String>('output', pathLib.join('doc', 'api'), |
| isDir: true, help: 'Path to output directory.'), |
| new DartdocOptionSyntheticOnly<PackageMeta>( |
| 'packageMeta', |
| (DartdocSyntheticOption<PackageMeta> option, Directory dir) { |
| PackageMeta packageMeta = new PackageMeta.fromDir(dir); |
| if (packageMeta == null) { |
| throw new DartdocOptionError( |
| 'Unable to determine package for directory: ${dir.path}'); |
| } |
| return packageMeta; |
| }, |
| ), |
| new DartdocOptionArgOnly<List<String>>('packageOrder', [], |
| help: |
| 'A list of package names to place first when grouping libraries in packages. ' |
| 'Unmentioned packages are sorted after these.'), |
| new DartdocOptionArgOnly<bool>('sdkDocs', false, |
| help: 'Generate ONLY the docs for the Dart SDK.'), |
| new DartdocOptionArgSynth<String>('sdkDir', |
| (DartdocSyntheticOption<String> option, Directory dir) { |
| if (!option.parent['sdkDocs'].valueAt(dir) && |
| (option.root['topLevelPackageMeta'].valueAt(dir) as PackageMeta) |
| .requiresFlutter) { |
| String flutterRoot = option.root['flutterRoot'].valueAt(dir); |
| if (flutterRoot == null) { |
| throw new DartdocOptionError( |
| 'Top level package requires Flutter but FLUTTER_ROOT environment variable not set'); |
| } |
| return pathLib.join(flutterRoot, 'bin', 'cache', 'dart-sdk'); |
| } |
| return defaultSdkDir.absolute.path; |
| }, help: 'Path to the SDK directory.', isDir: true, mustExist: true), |
| new DartdocOptionArgFile<bool>('showUndocumentedCategories', false, |
| help: "Label categories that aren't documented", negatable: true), |
| new DartdocOptionSyntheticOnly<PackageMeta>('topLevelPackageMeta', |
| (DartdocSyntheticOption<PackageMeta> option, Directory dir) { |
| PackageMeta packageMeta = new PackageMeta.fromDir( |
| new Directory(option.parent['inputDir'].valueAt(dir))); |
| if (packageMeta == null) { |
| throw new DartdocOptionError( |
| 'Unable to generate documentation: no package found'); |
| } |
| if (!packageMeta.isValid) { |
| final String firstError = packageMeta.getInvalidReasons().first; |
| throw new DartdocOptionError('Package is invalid: $firstError'); |
| } |
| return packageMeta; |
| }, help: 'PackageMeta object for the default package.'), |
| new DartdocOptionArgOnly<bool>('useCategories', true, |
| help: 'Display categories in the sidebar of packages'), |
| new DartdocOptionArgOnly<bool>('validateLinks', true, |
| help: |
| 'Runs the built-in link checker to display Dart context aware warnings for broken links (slow)', |
| negatable: true), |
| new DartdocOptionArgOnly<bool>('verboseWarnings', true, |
| help: 'Display extra debugging information and help with warnings.', |
| negatable: true), |
| new DartdocOptionFileOnly<ToolConfiguration>( |
| 'tools', ToolConfiguration.empty, |
| convertYamlToType: ToolConfiguration.fromYamlMap, |
| help: 'A map of tool names to executable paths. Each executable must ' |
| 'exist. Executables for different platforms are specified by ' |
| 'giving the platform name as a key, and a list of strings as the ' |
| 'command.'), |
| // TODO(jcollins-g): refactor so there is a single static "create" for |
| // each DartdocOptionContext that traverses the inheritance tree itself. |
| ] |
| ..addAll(await createExperimentOptions()) |
| ..addAll(await createPackageWarningOptions()); |
| } |