@@ -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.
@@ -190,30 +287,23 @@ abstract class DartdocOption<T> {
190
287
final String name;
191
288
192
289
/// Set to true if this option represents the name of a directory.
193
- /// Only one of [isDir] , [isFile] , or [isExecutable] must be set.
194
290
final bool isDir;
195
291
196
292
/// Set to true if this option represents the name of a file.
197
- /// Only one of [isDir] , [isFile] , or [isExecutable] must be set.
198
293
final bool isFile;
199
294
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
-
204
295
/// Set to true if DartdocOption subclasses should validate that the
205
296
/// directory or file exists. Does not imply validation of [defaultsTo] ,
206
297
/// and requires that one of [isDir] or [isFile] is set.
207
298
final bool mustExist;
208
299
209
300
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) {
211
302
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
+ }
217
307
}
218
308
219
309
/// Closure to convert yaml data into some other structure.
@@ -263,14 +353,17 @@ abstract class DartdocOption<T> {
263
353
/// Call [_onMissing] for every path that does not exist.
264
354
void _validatePaths (_OptionValueWithContext valueWithContext) {
265
355
if (! mustExist) return ;
266
- assert (isDir || isFile || isExecutable );
356
+ assert (isDir || isFile);
267
357
List <String > resolvedPaths;
268
358
if (valueWithContext.value is String ) {
269
359
resolvedPaths = [valueWithContext.resolvedValue];
270
360
} else if (valueWithContext.value is List <String >) {
271
361
resolvedPaths = valueWithContext.resolvedValue.toList ();
272
362
} else if (valueWithContext.value is Map <String , String >) {
273
363
resolvedPaths = valueWithContext.resolvedValue.values.toList ();
364
+ } else {
365
+ assert (false , "Trying to ensure existence of unsupported type "
366
+ "${valueWithContext .value .runtimeType }" );
274
367
}
275
368
for (String path in resolvedPaths) {
276
369
FileSystemEntity f = isDir ? new Directory (path) : new File (path);
@@ -280,10 +373,10 @@ abstract class DartdocOption<T> {
280
373
}
281
374
}
282
375
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.
285
378
T _handlePathsInContext (_OptionValueWithContext valueWithContext) {
286
- if (valueWithContext? .value == null || ! (isDir || isFile || isExecutable ))
379
+ if (valueWithContext? .value == null || ! (isDir || isFile))
287
380
return valueWithContext? .value;
288
381
_validatePaths (valueWithContext);
289
382
return valueWithContext.resolvedValue;
@@ -327,8 +420,8 @@ abstract class DartdocOption<T> {
327
420
328
421
/// Return the calculated value of this option, given the directory as context.
329
422
///
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
332
425
/// (for arguments) or the config file from which the value was derived.
333
426
///
334
427
/// May throw [DartdocOptionError] if a command line argument is of the wrong
@@ -385,19 +478,9 @@ class DartdocOptionFileSynth<T> extends DartdocOption<T>
385
478
String help = '' ,
386
479
bool isDir = false ,
387
480
bool isFile = false ,
388
- bool isExecutable = false ,
389
481
bool parentDirOverridesChild,
390
482
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) {
401
484
_parentDirOverridesChild = parentDirOverridesChild;
402
485
}
403
486
@@ -442,11 +525,9 @@ class DartdocOptionArgSynth<T> extends DartdocOption<T>
442
525
bool hide = false ,
443
526
bool isDir = false ,
444
527
bool isFile = false ,
445
- bool isExecutable = false ,
446
528
bool negatable,
447
529
bool splitCommas})
448
- : super ._(
449
- name, null , help, isDir, isFile, isExecutable, mustExist, null ) {
530
+ : super ._(name, null , help, isDir, isFile, mustExist, null ) {
450
531
_hide = hide;
451
532
_negatable = negatable;
452
533
_splitCommas = splitCommas;
@@ -481,8 +562,8 @@ class DartdocOptionArgSynth<T> extends DartdocOption<T>
481
562
/// A synthetic option takes a closure at construction time that computes
482
563
/// the value of the configuration option based on other configuration options.
483
564
/// 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.
486
567
class DartdocOptionSyntheticOnly <T > extends DartdocOption <T >
487
568
with DartdocSyntheticOption <T > {
488
569
@override
@@ -491,9 +572,8 @@ class DartdocOptionSyntheticOnly<T> extends DartdocOption<T>
491
572
{bool mustExist = false ,
492
573
String help = '' ,
493
574
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 );
497
577
}
498
578
499
579
abstract class DartdocSyntheticOption <T > implements DartdocOption <T > {
@@ -527,7 +607,7 @@ typedef Future<List<DartdocOption>> OptionGenerator();
527
607
/// A [DartdocOption] that only contains other [DartdocOption] s and is not an option itself.
528
608
class DartdocOptionSet extends DartdocOption <Null > {
529
609
DartdocOptionSet (String name)
530
- : super ._(name, null , null , false , false , false , false , null );
610
+ : super ._(name, null , null , false , false , false , null );
531
611
532
612
/// Asynchronous factory that is the main entry point to initialize Dartdoc
533
613
/// options for use.
@@ -576,19 +656,9 @@ class DartdocOptionArgOnly<T> extends DartdocOption<T>
576
656
bool hide = false ,
577
657
bool isDir = false ,
578
658
bool isFile = false ,
579
- bool isExecutable = false ,
580
659
bool negatable,
581
660
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 ) {
592
662
_hide = hide;
593
663
_negatable = negatable;
594
664
_splitCommas = splitCommas;
@@ -621,20 +691,10 @@ class DartdocOptionArgFile<T> extends DartdocOption<T>
621
691
bool hide = false ,
622
692
bool isDir = false ,
623
693
bool isFile = false ,
624
- bool isExecutable = false ,
625
694
bool negatable,
626
695
bool parentDirOverridesChild: false ,
627
696
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 ) {
638
698
_abbr = abbr;
639
699
_hide = hide;
640
700
_negatable = negatable;
@@ -682,19 +742,10 @@ class DartdocOptionFileOnly<T> extends DartdocOption<T>
682
742
String help: '' ,
683
743
bool isDir = false ,
684
744
bool isFile = false ,
685
- bool isExecutable = false ,
686
745
bool parentDirOverridesChild: false ,
687
746
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) {
698
749
_parentDirOverridesChild = parentDirOverridesChild;
699
750
}
700
751
@@ -1073,7 +1124,7 @@ class DartdocOptionContext {
1073
1124
List <String > get includeExternal =>
1074
1125
optionSet['includeExternal' ].valueAt (context);
1075
1126
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);
1077
1128
1078
1129
/// _input is only used to construct synthetic options.
1079
1130
// ignore: unused_element
@@ -1299,10 +1350,12 @@ Future<List<DartdocOption>> createDartdocOptions() async {
1299
1350
new DartdocOptionArgOnly <bool >('verboseWarnings' , true ,
1300
1351
help: 'Display extra debugging information and help with warnings.' ,
1301
1352
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 ,
1305
1356
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.' ),
1307
1360
];
1308
1361
}
0 commit comments