Skip to content

Commit 3a5b586

Browse files
committed
Adding more complete tool options.
1 parent 2056fe1 commit 3a5b586

File tree

4 files changed

+161
-98
lines changed

4 files changed

+161
-98
lines changed

lib/src/dartdoc_options.dart

Lines changed: 129 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,103 @@ class CategoryConfiguration {
110110
}
111111
}
112112

113+
class ToolDefinition {
114+
final List<String> command;
115+
final String description;
116+
117+
ToolDefinition(this.command, this.description)
118+
: assert(command != null),
119+
assert(command.isNotEmpty),
120+
assert(description != null);
121+
122+
@override
123+
String toString() => '$runtimeType: "${command.join(' ')}" ($description)';
124+
}
125+
126+
class ToolConfiguration {
127+
final Map<String, ToolDefinition> tools;
128+
129+
ToolConfiguration._(this.tools);
130+
131+
static ToolConfiguration get empty {
132+
return new ToolConfiguration._({});
133+
}
134+
135+
static ToolConfiguration fromYamlMap(
136+
YamlMap yamlMap, pathLib.Context pathContext) {
137+
Map<String, ToolDefinition> newToolDefinitions = {};
138+
for (MapEntry entry in yamlMap.entries) {
139+
String name = entry.key.toString();
140+
var toolMap = entry.value;
141+
String description;
142+
List<String> command;
143+
if (toolMap is Map) {
144+
description = toolMap['description']?.toString();
145+
// If the command key is given, then it applies to all platforms.
146+
String commandFrom = toolMap.containsKey('command')
147+
? 'command'
148+
: Platform.operatingSystem;
149+
if (toolMap.containsKey(commandFrom)) {
150+
if (toolMap[commandFrom].value is String) {
151+
command = [toolMap[commandFrom].toString()];
152+
if (command[0].isEmpty) {
153+
throw new DartdocOptionError(
154+
'Tool commands must not be empty. Tool $name command entry '
155+
'"$commandFrom" must contain at least one path.');
156+
}
157+
} else if (toolMap[commandFrom] is YamlList) {
158+
command = (toolMap[commandFrom] as YamlList)
159+
.map<String>((node) => node.toString())
160+
.toList();
161+
if (command.isEmpty) {
162+
throw new DartdocOptionError(
163+
'Tool commands must not be empty. Tool $name command entry '
164+
'"$commandFrom" must contain at least one path.');
165+
}
166+
} else {
167+
throw new DartdocOptionError(
168+
'Tool commands must be a path to an executable, or a list of '
169+
'strings that starts with a path to an executable. '
170+
'The tool $name has a $commandFrom entry that is a '
171+
'${toolMap[commandFrom].runtimeType}');
172+
}
173+
}
174+
} else {
175+
throw new DartdocOptionError(
176+
'Tools must be defined as a map of tool names to definitions. Tool '
177+
'$name is not a map.');
178+
}
179+
if (command == null) {
180+
throw new DartdocOptionError(
181+
'At least one of "command" or "${Platform.operatingSystem}" must '
182+
'be defined for the tool $name.');
183+
}
184+
String executable = command.removeAt(0);
185+
executable = pathContext.canonicalize(executable);
186+
File executableFile = new File(executable);
187+
FileStat exeStat = executableFile.statSync();
188+
if (exeStat.type == FileSystemEntityType.notFound) {
189+
throw new DartdocOptionError('Command executables must exist. '
190+
'The file "$executable" does not exist for tool $name.');
191+
}
192+
// Dart scripts don't need to be executable, because they'll be
193+
// executed with the Dart binary.
194+
bool isExecutable(int mode) {
195+
return (0x1 & ((mode >> 6) | (mode >> 3) | mode)) != 0;
196+
}
197+
198+
if (!executable.endsWith('.dart') && !isExecutable(exeStat.mode)) {
199+
throw new DartdocOptionError('Non-Dart commands must be '
200+
'executable. The file "$executable" for tool $name does not have '
201+
'executable permission.');
202+
}
203+
newToolDefinitions[name] =
204+
new ToolDefinition([executable] + command, description);
205+
}
206+
return new ToolConfiguration._(newToolDefinitions);
207+
}
208+
}
209+
113210
/// A container class to keep track of where our yaml data came from.
114211
class _YamlFileData {
115212
/// The map from the yaml file.
@@ -190,30 +287,23 @@ abstract class DartdocOption<T> {
190287
final String name;
191288

192289
/// Set to true if this option represents the name of a directory.
193-
/// Only one of [isDir], [isFile], or [isExecutable] must be set.
194290
final bool isDir;
195291

196292
/// Set to true if this option represents the name of a file.
197-
/// Only one of [isDir], [isFile], or [isExecutable] must be set.
198293
final bool isFile;
199294

200-
/// Set to true if this option represents the name of an executable file.
201-
/// Only one of [isDir], [isFile], or [isExecutable] must be set.
202-
final bool isExecutable;
203-
204295
/// Set to true if DartdocOption subclasses should validate that the
205296
/// directory or file exists. Does not imply validation of [defaultsTo],
206297
/// and requires that one of [isDir] or [isFile] is set.
207298
final bool mustExist;
208299

209300
DartdocOption._(this.name, this.defaultsTo, this.help, this.isDir,
210-
this.isFile, this.isExecutable, this.mustExist, this._convertYamlToType) {
301+
this.isFile, this.mustExist, this._convertYamlToType) {
211302
assert(!(isDir && isFile));
212-
assert(!(isDir && isExecutable));
213-
assert(!(isFile && isExecutable));
214-
if (isDir || isFile || isExecutable)
215-
assert(_isString || _isListString || _isMapString);
216-
if (mustExist) assert(isDir || isFile || isExecutable);
303+
if (isDir || isFile) assert(_isString || _isListString || _isMapString);
304+
if (mustExist) {
305+
assert(isDir || isFile);
306+
}
217307
}
218308

219309
/// Closure to convert yaml data into some other structure.
@@ -263,14 +353,17 @@ abstract class DartdocOption<T> {
263353
/// Call [_onMissing] for every path that does not exist.
264354
void _validatePaths(_OptionValueWithContext valueWithContext) {
265355
if (!mustExist) return;
266-
assert(isDir || isFile || isExecutable);
356+
assert(isDir || isFile);
267357
List<String> resolvedPaths;
268358
if (valueWithContext.value is String) {
269359
resolvedPaths = [valueWithContext.resolvedValue];
270360
} else if (valueWithContext.value is List<String>) {
271361
resolvedPaths = valueWithContext.resolvedValue.toList();
272362
} else if (valueWithContext.value is Map<String, String>) {
273363
resolvedPaths = valueWithContext.resolvedValue.values.toList();
364+
} else {
365+
assert(false, "Trying to ensure existence of unsupported type "
366+
"${valueWithContext.value.runtimeType}");
274367
}
275368
for (String path in resolvedPaths) {
276369
FileSystemEntity f = isDir ? new Directory(path) : new File(path);
@@ -280,10 +373,10 @@ abstract class DartdocOption<T> {
280373
}
281374
}
282375

283-
/// For a [List<String>] or [String] value, if [isDir], [isFile] or
284-
/// [isExecutable] is set, resolve paths in value relative to canonicalPath.
376+
/// For a [List<String>] or [String] value, if [isDir] or [isFile] is set,
377+
/// resolve paths in value relative to canonicalPath.
285378
T _handlePathsInContext(_OptionValueWithContext valueWithContext) {
286-
if (valueWithContext?.value == null || !(isDir || isFile || isExecutable))
379+
if (valueWithContext?.value == null || !(isDir || isFile))
287380
return valueWithContext?.value;
288381
_validatePaths(valueWithContext);
289382
return valueWithContext.resolvedValue;
@@ -327,8 +420,8 @@ abstract class DartdocOption<T> {
327420

328421
/// Return the calculated value of this option, given the directory as context.
329422
///
330-
/// If [isFile], [isExecutable] or [isDir] is set, the returned value will be
331-
/// transformed into a canonical path relative to the current working directory
423+
/// If [isFile] or [isDir] is set, the returned value will be transformed
424+
/// into a canonical path relative to the current working directory
332425
/// (for arguments) or the config file from which the value was derived.
333426
///
334427
/// May throw [DartdocOptionError] if a command line argument is of the wrong
@@ -385,19 +478,9 @@ class DartdocOptionFileSynth<T> extends DartdocOption<T>
385478
String help = '',
386479
bool isDir = false,
387480
bool isFile = false,
388-
bool isExecutable = false,
389481
bool parentDirOverridesChild,
390482
T Function(YamlMap, pathLib.Context) convertYamlToType})
391-
: super._(
392-
name,
393-
null,
394-
help,
395-
isDir,
396-
isFile,
397-
isExecutable,
398-
mustExist,
399-
convertYamlToType,
400-
) {
483+
: super._(name, null, help, isDir, isFile, mustExist, convertYamlToType) {
401484
_parentDirOverridesChild = parentDirOverridesChild;
402485
}
403486

@@ -442,11 +525,9 @@ class DartdocOptionArgSynth<T> extends DartdocOption<T>
442525
bool hide = false,
443526
bool isDir = false,
444527
bool isFile = false,
445-
bool isExecutable = false,
446528
bool negatable,
447529
bool splitCommas})
448-
: super._(
449-
name, null, help, isDir, isFile, isExecutable, mustExist, null) {
530+
: super._(name, null, help, isDir, isFile, mustExist, null) {
450531
_hide = hide;
451532
_negatable = negatable;
452533
_splitCommas = splitCommas;
@@ -481,8 +562,8 @@ class DartdocOptionArgSynth<T> extends DartdocOption<T>
481562
/// A synthetic option takes a closure at construction time that computes
482563
/// the value of the configuration option based on other configuration options.
483564
/// Does not protect against closures that self-reference. If [mustExist] and
484-
/// [isDir], [isExecutable] or [isFile] is set, computed values will be resolved
485-
/// to canonical paths.
565+
/// [isDir] or [isFile] is set, computed values will be resolved to canonical
566+
/// paths.
486567
class DartdocOptionSyntheticOnly<T> extends DartdocOption<T>
487568
with DartdocSyntheticOption<T> {
488569
@override
@@ -491,9 +572,8 @@ class DartdocOptionSyntheticOnly<T> extends DartdocOption<T>
491572
{bool mustExist = false,
492573
String help = '',
493574
bool isDir = false,
494-
bool isFile = false,
495-
bool isExecutable = false})
496-
: super._(name, null, help, isDir, isFile, isExecutable, mustExist, null);
575+
bool isFile = false})
576+
: super._(name, null, help, isDir, isFile, mustExist, null);
497577
}
498578

499579
abstract class DartdocSyntheticOption<T> implements DartdocOption<T> {
@@ -527,7 +607,7 @@ typedef Future<List<DartdocOption>> OptionGenerator();
527607
/// A [DartdocOption] that only contains other [DartdocOption]s and is not an option itself.
528608
class DartdocOptionSet extends DartdocOption<Null> {
529609
DartdocOptionSet(String name)
530-
: super._(name, null, null, false, false, false, false, null);
610+
: super._(name, null, null, false, false, false, null);
531611

532612
/// Asynchronous factory that is the main entry point to initialize Dartdoc
533613
/// options for use.
@@ -576,19 +656,9 @@ class DartdocOptionArgOnly<T> extends DartdocOption<T>
576656
bool hide = false,
577657
bool isDir = false,
578658
bool isFile = false,
579-
bool isExecutable = false,
580659
bool negatable,
581660
bool splitCommas})
582-
: super._(
583-
name,
584-
defaultsTo,
585-
help,
586-
isDir,
587-
isFile,
588-
isExecutable,
589-
mustExist,
590-
null,
591-
) {
661+
: super._(name, defaultsTo, help, isDir, isFile, mustExist, null) {
592662
_hide = hide;
593663
_negatable = negatable;
594664
_splitCommas = splitCommas;
@@ -621,20 +691,10 @@ class DartdocOptionArgFile<T> extends DartdocOption<T>
621691
bool hide = false,
622692
bool isDir = false,
623693
bool isFile = false,
624-
bool isExecutable = false,
625694
bool negatable,
626695
bool parentDirOverridesChild: false,
627696
bool splitCommas})
628-
: super._(
629-
name,
630-
defaultsTo,
631-
help,
632-
isDir,
633-
isFile,
634-
isExecutable,
635-
mustExist,
636-
null,
637-
) {
697+
: super._(name, defaultsTo, help, isDir, isFile, mustExist, null) {
638698
_abbr = abbr;
639699
_hide = hide;
640700
_negatable = negatable;
@@ -682,19 +742,10 @@ class DartdocOptionFileOnly<T> extends DartdocOption<T>
682742
String help: '',
683743
bool isDir = false,
684744
bool isFile = false,
685-
bool isExecutable = false,
686745
bool parentDirOverridesChild: false,
687746
T Function(YamlMap, pathLib.Context) convertYamlToType})
688-
: super._(
689-
name,
690-
defaultsTo,
691-
help,
692-
isDir,
693-
isFile,
694-
isExecutable,
695-
mustExist,
696-
convertYamlToType,
697-
) {
747+
: super._(name, defaultsTo, help, isDir, isFile, mustExist,
748+
convertYamlToType) {
698749
_parentDirOverridesChild = parentDirOverridesChild;
699750
}
700751

@@ -1073,7 +1124,7 @@ class DartdocOptionContext {
10731124
List<String> get includeExternal =>
10741125
optionSet['includeExternal'].valueAt(context);
10751126
bool get includeSource => optionSet['includeSource'].valueAt(context);
1076-
Map<String, String> get tools => optionSet['tools'].valueAt(context);
1127+
ToolConfiguration get tools => optionSet['tools'].valueAt(context);
10771128

10781129
/// _input is only used to construct synthetic options.
10791130
// ignore: unused_element
@@ -1299,10 +1350,12 @@ Future<List<DartdocOption>> createDartdocOptions() async {
12991350
new DartdocOptionArgOnly<bool>('verboseWarnings', true,
13001351
help: 'Display extra debugging information and help with warnings.',
13011352
negatable: true),
1302-
new DartdocOptionArgFile<Map<String, String>>('tools', <String, String>{},
1303-
isExecutable: true,
1304-
mustExist: true,
1353+
new DartdocOptionFileOnly<ToolConfiguration>(
1354+
'tools', ToolConfiguration.empty,
1355+
convertYamlToType: ToolConfiguration.fromYamlMap,
13051356
help: 'A map of tool names to executable paths. Each executable must '
1306-
'exist.'),
1357+
'exist. Executables for different platforms are specified by '
1358+
'giving the platform name as a key, and a list of strings as the '
1359+
'command.'),
13071360
];
13081361
}

0 commit comments

Comments
 (0)