@@ -110,6 +110,103 @@ class CategoryConfiguration {
110
110
}
111
111
}
112
112
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
+
113
210
/// A container class to keep track of where our yaml data came from.
114
211
class _YamlFileData {
115
212
/// The map from the yaml file.
@@ -212,7 +309,10 @@ abstract class DartdocOption<T> {
212
309
assert (! (isDir && isExecutable));
213
310
assert (! (isFile && isExecutable));
214
311
if (isDir || isFile || isExecutable)
215
- assert (_isString || _isListString || _isMapString);
312
+ assert (_isString ||
313
+ _isListString ||
314
+ _isMapString ||
315
+ _convertYamlToType != null );
216
316
if (mustExist) assert (isDir || isFile || isExecutable);
217
317
}
218
318
@@ -271,6 +371,9 @@ abstract class DartdocOption<T> {
271
371
resolvedPaths = valueWithContext.resolvedValue.toList ();
272
372
} else if (valueWithContext.value is Map <String , String >) {
273
373
resolvedPaths = valueWithContext.resolvedValue.values.toList ();
374
+ } else {
375
+ assert (false , "Trying to ensure existence of wrong type "
376
+ "${valueWithContext .value .runtimeType }" );
274
377
}
275
378
for (String path in resolvedPaths) {
276
379
FileSystemEntity f = isDir ? new Directory (path) : new File (path);
@@ -1073,7 +1176,7 @@ class DartdocOptionContext {
1073
1176
List <String > get includeExternal =>
1074
1177
optionSet['includeExternal' ].valueAt (context);
1075
1178
bool get includeSource => optionSet['includeSource' ].valueAt (context);
1076
- Map < String , String > get tools => optionSet['tools' ].valueAt (context);
1179
+ ToolConfiguration get tools => optionSet['tools' ].valueAt (context);
1077
1180
1078
1181
/// _input is only used to construct synthetic options.
1079
1182
// ignore: unused_element
@@ -1299,10 +1402,12 @@ Future<List<DartdocOption>> createDartdocOptions() async {
1299
1402
new DartdocOptionArgOnly <bool >('verboseWarnings' , true ,
1300
1403
help: 'Display extra debugging information and help with warnings.' ,
1301
1404
negatable: true ),
1302
- new DartdocOptionArgFile < Map < String , String >>( 'tools' , < String , String > {},
1303
- isExecutable : true ,
1304
- mustExist : true ,
1405
+ new DartdocOptionFileOnly < ToolConfiguration >(
1406
+ 'tools' , ToolConfiguration .empty ,
1407
+ convertYamlToType : ToolConfiguration .fromYamlMap ,
1305
1408
help: 'A map of tool names to executable paths. Each executable must '
1306
- 'exist.' ),
1409
+ 'exist. Executables for different platforms are specified by '
1410
+ 'giving the platform name as a key, and a list of strings as the '
1411
+ 'command.' ),
1307
1412
];
1308
1413
}
0 commit comments