diff --git a/lib/src/dartdoc_options.dart b/lib/src/dartdoc_options.dart index 9ed8e032b9..b60c8f0c74 100644 --- a/lib/src/dartdoc_options.dart +++ b/lib/src/dartdoc_options.dart @@ -433,6 +433,17 @@ class _YamlFileData { _YamlFileData(this.data, this.canonicalDirectoryPath); } +/// An enum to specify the multiple different kinds of data an option might +/// represent. +enum OptionKind { + other, // Make no assumptions about the option data; it may be of any type + // or semantic. + file, // Option data references a filename or filenames with strings. + dir, // Option data references a directory name or names with strings. + glob, // Option data references globs with strings that may cover many + // filenames and/or directories. +} + /// 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. @@ -503,10 +514,15 @@ abstract class DartdocOption { final String name; /// Set to true if this option represents the name of a directory. - final bool isDir; + bool get isDir => optionIs == OptionKind.dir; /// Set to true if this option represents the name of a file. - final bool isFile; + bool get isFile => optionIs == OptionKind.file; + + /// Set to true if this option represents a glob. + bool get isGlob => optionIs == OptionKind.glob; + + final OptionKind optionIs; /// Set to true if DartdocOption subclasses should validate that the /// directory or file exists. Does not imply validation of [defaultsTo], @@ -515,11 +531,13 @@ abstract class DartdocOption { final ResourceProvider resourceProvider; - DartdocOption(this.name, this.defaultsTo, this.help, this.isDir, this.isFile, + DartdocOption(this.name, this.defaultsTo, this.help, this.optionIs, this.mustExist, this._convertYamlToType, this.resourceProvider) { - assert(!(isDir && isFile)); - if (isDir || isFile) assert(_isString || _isListString || _isMapString); + if (isDir || isFile || isGlob) { + assert(_isString || _isListString || _isMapString); + } if (mustExist) { + // Globs by definition don't have to exist. assert(isDir || isFile); } } @@ -604,7 +622,7 @@ abstract class DartdocOption { /// For a [List] 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)) { + if (valueWithContext?.value == null || !(isDir || isFile || isGlob)) { return valueWithContext?.value; } _validatePaths(valueWithContext); @@ -715,11 +733,10 @@ class DartdocOptionFileSynth extends DartdocOption String name, this._compute, ResourceProvider resourceprovider, {bool mustExist = false, String help = '', - bool isDir = false, - bool isFile = false, + OptionKind optionIs = OptionKind.other, bool parentDirOverridesChild, ConvertYamlToType convertYamlToType}) - : super(name, null, help, isDir, isFile, mustExist, convertYamlToType, + : super(name, null, help, optionIs, mustExist, convertYamlToType, resourceprovider) { _parentDirOverridesChild = parentDirOverridesChild; } @@ -765,12 +782,10 @@ class DartdocOptionArgSynth extends DartdocOption bool mustExist = false, String help = '', bool hide = false, - bool isDir = false, - bool isFile = false, + OptionKind optionIs = OptionKind.other, bool negatable = false, bool splitCommas}) - : super(name, null, help, isDir, isFile, mustExist, null, - resourceProvider) { + : super(name, null, help, optionIs, mustExist, null, resourceProvider) { _hide = hide; _negatable = negatable; _splitCommas = splitCommas; @@ -818,10 +833,8 @@ class DartdocOptionSyntheticOnly extends DartdocOption String name, this._compute, ResourceProvider resourceProvider, {bool mustExist = false, String help = '', - bool isDir = false, - bool isFile = false}) - : super( - name, null, help, isDir, isFile, mustExist, null, resourceProvider); + OptionKind optionIs = OptionKind.other}) + : super(name, null, help, optionIs, mustExist, null, resourceProvider); } abstract class DartdocSyntheticOption implements DartdocOption { @@ -856,7 +869,8 @@ typedef OptionGenerator = Future>> Function( /// option itself. class DartdocOptionSet extends DartdocOption { DartdocOptionSet(String name, ResourceProvider resourceProvider) - : super(name, null, null, false, false, false, null, resourceProvider); + : super( + name, null, null, OptionKind.other, false, null, resourceProvider); /// Asynchronous factory that is the main entry point to initialize Dartdoc /// options for use. @@ -908,11 +922,10 @@ class DartdocOptionArgOnly extends DartdocOption bool mustExist = false, String help = '', bool hide = false, - bool isDir = false, - bool isFile = false, + OptionKind optionIs = OptionKind.other, bool negatable = false, bool splitCommas}) - : super(name, defaultsTo, help, isDir, isFile, mustExist, null, + : super(name, defaultsTo, help, optionIs, mustExist, null, resourceProvider) { _hide = hide; _negatable = negatable; @@ -949,12 +962,11 @@ class DartdocOptionArgFile extends DartdocOption bool mustExist = false, String help = '', bool hide = false, - bool isDir = false, - bool isFile = false, + OptionKind optionIs = OptionKind.other, bool negatable = false, bool parentDirOverridesChild = false, bool splitCommas}) - : super(name, defaultsTo, help, isDir, isFile, mustExist, null, + : super(name, defaultsTo, help, optionIs, mustExist, null, resourceProvider) { _abbr = abbr; _hide = hide; @@ -1007,12 +1019,11 @@ class DartdocOptionFileOnly extends DartdocOption String name, T defaultsTo, ResourceProvider resourceProvider, {bool mustExist = false, String help = '', - bool isDir = false, - bool isFile = false, + OptionKind optionIs = OptionKind.other, bool parentDirOverridesChild = false, ConvertYamlToType convertYamlToType}) - : super(name, defaultsTo, help, isDir, isFile, mustExist, - convertYamlToType, resourceProvider) { + : super(name, defaultsTo, help, optionIs, mustExist, convertYamlToType, + resourceProvider) { _parentDirOverridesChild = parentDirOverridesChild; } @@ -1260,8 +1271,10 @@ abstract class _DartdocArgOption implements DartdocOption { 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? + // rather than silently proceeding. This is because the user presumably + // typed something wrong on the command line and can therefore fix it. + // dartdoc_option.yaml files from other packages may not be fully in the + // user's control. if (_isBool || _isListString || _isString) { retval = _argResults[argName]; } else if (_isInt) { @@ -1566,7 +1579,7 @@ Future>> createDartdocOptions( }, resourceProvider, help: 'Remove text from libraries with the following names.'), DartdocOptionArgFile('examplePathPrefix', null, resourceProvider, - isDir: true, + optionIs: OptionKind.dir, help: 'Prefix for @example paths.\n(defaults to the project root)', mustExist: true), DartdocOptionArgFile>('exclude', [], resourceProvider, @@ -1580,7 +1593,7 @@ Future>> createDartdocOptions( (DartdocSyntheticOption option, Folder dir) => resolveTildePath(Platform.environment['FLUTTER_ROOT']), resourceProvider, - isDir: true, + optionIs: OptionKind.dir, help: 'Root of the Flutter SDK, specified from environment.', mustExist: true), DartdocOptionArgOnly('hideSdkText', false, resourceProvider, @@ -1592,7 +1605,7 @@ Future>> createDartdocOptions( help: 'Library names to generate docs for.', splitCommas: true), DartdocOptionArgFile>( 'includeExternal', null, resourceProvider, - isFile: true, + optionIs: OptionKind.file, help: 'Additional (external) dart files to include; use "dir/fileName", ' 'as in lib/material.dart.', @@ -1605,7 +1618,9 @@ Future>> createDartdocOptions( 'HTML into dartdoc output.'), DartdocOptionArgOnly( 'input', resourceProvider.pathContext.current, resourceProvider, - isDir: true, help: 'Path to source directory', mustExist: true), + optionIs: OptionKind.dir, + help: 'Path to source directory', + mustExist: true), DartdocOptionSyntheticOnly('inputDir', (DartdocSyntheticOption option, Folder dir) { if (option.parent['sdkDocs'].valueAt(dir)) { @@ -1614,7 +1629,7 @@ Future>> createDartdocOptions( return option.parent['input'].valueAt(dir); }, resourceProvider, help: 'Path to source directory (with override if --sdk-docs)', - isDir: true, + optionIs: OptionKind.dir, mustExist: true), DartdocOptionSet('linkTo', resourceProvider) ..addAll([ @@ -1656,7 +1671,7 @@ Future>> createDartdocOptions( ]), DartdocOptionArgOnly('output', resourceProvider.pathContext.join('doc', 'api'), resourceProvider, - isDir: true, help: 'Path to output directory.'), + optionIs: OptionKind.dir, help: 'Path to output directory.'), DartdocOptionSyntheticOnly( 'packageMeta', (DartdocSyntheticOption option, Folder dir) { @@ -1691,7 +1706,9 @@ Future>> createDartdocOptions( } return packageMetaProvider.defaultSdkDir.path; }, packageMetaProvider.resourceProvider, - help: 'Path to the SDK directory.', isDir: true, mustExist: true), + help: 'Path to the SDK directory.', + optionIs: OptionKind.dir, + mustExist: true), DartdocOptionArgFile( 'showUndocumentedCategories', false, resourceProvider, help: "Label categories that aren't documented", negatable: true), diff --git a/lib/src/generator/generator.dart b/lib/src/generator/generator.dart index a6967abda4..42583425f9 100644 --- a/lib/src/generator/generator.dart +++ b/lib/src/generator/generator.dart @@ -55,7 +55,7 @@ Future>> createGeneratorOptions( var resourceProvider = packageMetaProvider.resourceProvider; return [ DartdocOptionArgFile>('footer', [], resourceProvider, - isFile: true, + optionIs: OptionKind.file, help: 'Paths to files with content to add to page footers, but possibly ' 'outside of dedicated footer elements for the generator (e.g. ' @@ -64,13 +64,13 @@ Future>> createGeneratorOptions( mustExist: true, splitCommas: true), DartdocOptionArgFile>('footerText', [], resourceProvider, - isFile: true, + optionIs: OptionKind.file, help: 'Paths to files with content to add to page footers (next to the ' 'package name and version).', mustExist: true, splitCommas: true), DartdocOptionArgFile>('header', [], resourceProvider, - isFile: true, + optionIs: OptionKind.file, help: 'Paths to files with content to add to page headers.', splitCommas: true), DartdocOptionArgOnly('prettyIndexJson', false, resourceProvider, @@ -79,7 +79,7 @@ Future>> createGeneratorOptions( 'larger, but it\'s also easier to diff.', negatable: false), DartdocOptionArgFile('favicon', null, resourceProvider, - isFile: true, + optionIs: OptionKind.file, help: 'A path to a favicon for the generated docs.', mustExist: true), DartdocOptionArgOnly('relCanonicalPrefix', null, resourceProvider, @@ -88,7 +88,7 @@ Future>> createGeneratorOptions( 'Consider using if building many versions of the docs for public ' 'SEO; learn more at https://goo.gl/gktN6F.'), DartdocOptionArgOnly('templatesDir', null, resourceProvider, - isDir: true, + optionIs: OptionKind.dir, mustExist: true, hide: true, help: diff --git a/lib/src/source_linker.dart b/lib/src/source_linker.dart index 4bfd715042..26d07329c5 100644 --- a/lib/src/source_linker.dart +++ b/lib/src/source_linker.dart @@ -36,14 +36,14 @@ Future>> createSourceLinkerOptions( DartdocOptionSet('linkToSource', resourceProvider) ..addAll([ DartdocOptionArgFile>('excludes', [], resourceProvider, - isDir: true, + optionIs: OptionKind.dir, help: 'A list of directories to exclude from linking to a source code repository.'), // TODO(jcollins-g): Use [DartdocOptionArgSynth], possibly in combination with a repository type and the root directory, and get revision number automatically DartdocOptionArgOnly('revision', null, resourceProvider, help: 'Revision number to insert into the URI.'), DartdocOptionArgFile('root', null, resourceProvider, - isDir: true, + optionIs: OptionKind.dir, help: 'Path to a local directory that is the root of the repository we link to. All source code files under this directory will be linked.'), DartdocOptionArgFile('uriTemplate', null, resourceProvider, diff --git a/test/dartdoc_options_test.dart b/test/dartdoc_options_test.dart index 1be0bc7778..c454e4293f 100644 --- a/test/dartdoc_options_test.dart +++ b/test/dartdoc_options_test.dart @@ -73,7 +73,7 @@ void main() { DartdocOptionSyntheticOnly>('vegetableLoaderChecked', (DartdocSyntheticOption> option, Folder dir) { return option.root['vegetableLoader'].valueAt(dir); - }, resourceProvider, isFile: true, mustExist: true)); + }, resourceProvider, optionIs: OptionKind.file, mustExist: true)); dartdocOptionSetSynthetic.add(DartdocOptionFileSynth('double', (DartdocSyntheticOption option, Folder dir) { return 3.7 + 4.1; @@ -82,7 +82,7 @@ void main() { DartdocOptionArgSynth('nonCriticalFileOption', (DartdocSyntheticOption option, Folder dir) { return option.root['vegetableLoader'].valueAt(dir).first; - }, resourceProvider, isFile: true)); + }, resourceProvider, optionIs: OptionKind.file)); dartdocOptionSetFiles = DartdocOptionSet('dartdoc', resourceProvider); dartdocOptionSetFiles.add(DartdocOptionFileOnly>( @@ -95,24 +95,24 @@ void main() { 'mapOption', {'hello': 'world'}, resourceProvider)); dartdocOptionSetFiles.add(DartdocOptionFileOnly>( 'fileOptionList', [], resourceProvider, - isFile: true, mustExist: true)); + optionIs: OptionKind.file, mustExist: true)); dartdocOptionSetFiles.add(DartdocOptionFileOnly( 'fileOption', null, resourceProvider, - isFile: true, mustExist: true)); + optionIs: OptionKind.file, mustExist: true)); dartdocOptionSetFiles.add(DartdocOptionFileOnly( 'parentOverride', 'oops', resourceProvider, parentDirOverridesChild: true)); dartdocOptionSetFiles.add(DartdocOptionFileOnly( 'nonCriticalFileOption', null, resourceProvider, - isFile: true)); + optionIs: OptionKind.file)); dartdocOptionSetFiles.add(DartdocOptionSet('nestedOption', resourceProvider) ..addAll([DartdocOptionFileOnly('flag', false, resourceProvider)])); dartdocOptionSetFiles.add(DartdocOptionFileOnly( 'dirOption', null, resourceProvider, - isDir: true, mustExist: true)); + optionIs: OptionKind.dir, mustExist: true)); dartdocOptionSetFiles.add(DartdocOptionFileOnly( 'nonCriticalDirOption', null, resourceProvider, - isDir: true)); + optionIs: OptionKind.dir)); dartdocOptionSetFiles.add(DartdocOptionFileOnly( 'convertThisMap', null, @@ -142,13 +142,13 @@ void main() { splitCommas: true)); dartdocOptionSetArgs.add(DartdocOptionArgOnly>( 'filesFlag', [], resourceProvider, - isFile: true, mustExist: true)); + optionIs: OptionKind.file, mustExist: true)); dartdocOptionSetArgs.add(DartdocOptionArgOnly( 'singleFile', 'hello', resourceProvider, - isFile: true, mustExist: true)); + optionIs: OptionKind.file, mustExist: true)); dartdocOptionSetArgs.add(DartdocOptionArgOnly( 'unimportantFile', 'whatever', resourceProvider, - isFile: true)); + optionIs: OptionKind.file)); dartdocOptionSetAll = DartdocOptionSet('dartdoc', resourceProvider); dartdocOptionSetAll.add(DartdocOptionArgFile>( @@ -166,7 +166,13 @@ void main() { 'notInAnyFile', 'so there', resourceProvider)); dartdocOptionSetAll.add(DartdocOptionArgFile( 'fileOption', null, resourceProvider, - isFile: true, mustExist: true)); + optionIs: OptionKind.file, mustExist: true)); + dartdocOptionSetAll.add(DartdocOptionArgFile>( + 'globOption', + [], + resourceProvider, + optionIs: OptionKind.glob, + )); tempDir = resourceProvider.createSystemTemp('options_test'); firstDir = resourceProvider @@ -222,6 +228,7 @@ dartdoc: dirOption: 'firstSub' fileOptionList: ['existing.dart', 'thing/that/does/not/exist'] mySpecialInteger: 11 + globOption: ['q*.html', 'e*.dart'] fileOption: "not existing" '''); dartdocOptionsTwoFirstSub.writeAsStringSync(''' @@ -229,6 +236,7 @@ dartdoc: categoryOrder: ['options_two_first_sub'] parentOverride: 'child' nonCriticalDirOption: 'not_existing' + globOption: ['**/*.dart'] '''); }); @@ -373,6 +381,42 @@ dartdoc: expect(dartdocOptionSetAll['mySpecialInteger'].valueAt(secondDir), equals(91)); }); + + group('glob options', () { + test('work via the command line', () { + dartdocOptionSetAll.parseArguments(['--glob-option', 'foo/**']); + expect( + dartdocOptionSetAll['globOption'].valueAtCurrent(), + equals([ + resourceProvider.pathContext + .join(resourceProvider.pathContext.current, 'foo/**') + ])); + }); + + test('work via files', () { + dartdocOptionSetAll.parseArguments([]); + expect( + dartdocOptionSetAll['globOption'].valueAt(secondDir), + equals([ + resourceProvider.pathContext.join(secondDir.path, 'q*.html'), + resourceProvider.pathContext.join(secondDir.path, 'e*.dart') + ])); + // No child override, should be the same as parent + expect( + dartdocOptionSetAll['globOption'].valueAt(secondDirSecondSub), + equals([ + resourceProvider.pathContext.join(secondDir.path, 'q*.html'), + resourceProvider.pathContext.join(secondDir.path, 'e*.dart') + ])); + // Child directory overrides + expect( + dartdocOptionSetAll['globOption'].valueAt(secondDirFirstSub), + equals([ + resourceProvider.pathContext + .join(secondDirFirstSub.path, '**/*.dart') + ])); + }); + }); }); group('new style dartdoc arg-only options', () {