@@ -21,6 +21,7 @@ import 'package:pana/src/license_detection/license_detector.dart' as detector;
2121import 'package:path/path.dart' as path;
2222import 'package:very_good_cli/src/pub_license/spdx_license.gen.dart' ;
2323import 'package:very_good_cli/src/pubspec_lock/pubspec_lock.dart' ;
24+ import 'package:very_good_cli/src/very_good_config/very_good_config.dart' ;
2425
2526/// Overrides the [package_config.findPackageConfig] function for testing.
2627@visibleForTesting
@@ -65,6 +66,87 @@ typedef _DependencyLicenseMap = Map<String, Set<String>?>;
6566/// as values.
6667typedef _BannedDependencyLicenseMap = Map <String , Set <String >>;
6768
69+ /// Options for configuring the `very_good packages check licenses` command.
70+ class PackagesCheckLicensesOptions {
71+ PackagesCheckLicensesOptions ._({
72+ required this .ignoreRetrievalFailures,
73+ required this .dependencyTypes,
74+ required this .allowedLicenses,
75+ required this .forbiddenLicenses,
76+ required this .skippedPackages,
77+ required this .reporterOutputFormat,
78+ });
79+
80+ /// Parses [ArgResults] into a [PackagesCheckLicensesOptions] instance.
81+ ///
82+ /// When [config] is provided, its values are used as defaults for any
83+ /// option that was not explicitly parsed on the command line.
84+ factory PackagesCheckLicensesOptions .parse (
85+ ArgResults argResults, {
86+ VeryGoodConfig config = VeryGoodConfig .empty,
87+ }) {
88+ final licensesConfig = config.packages.check.licenses;
89+
90+ final ignoreRetrievalFailures = resolveArg (
91+ argResults,
92+ 'ignore-retrieval-failures' ,
93+ licensesConfig.ignoreRetrievalFailures,
94+ );
95+ final dependencyTypes = resolveArg <List <String >>(
96+ argResults,
97+ 'dependency-type' ,
98+ licensesConfig.dependencyType,
99+ );
100+ final allowedLicenses = resolveArg <List <String >>(
101+ argResults,
102+ 'allowed' ,
103+ licensesConfig.allowed,
104+ );
105+ final forbiddenLicenses = resolveArg <List <String >>(
106+ argResults,
107+ 'forbidden' ,
108+ licensesConfig.forbidden,
109+ );
110+ final skippedPackages = resolveArg <List <String >>(
111+ argResults,
112+ 'skip-packages' ,
113+ licensesConfig.skipPackages,
114+ );
115+ final reporter = resolveArg <String ?>(
116+ argResults,
117+ 'reporter' ,
118+ licensesConfig.reporter,
119+ );
120+
121+ return PackagesCheckLicensesOptions ._(
122+ ignoreRetrievalFailures: ignoreRetrievalFailures,
123+ dependencyTypes: dependencyTypes.toSet (),
124+ allowedLicenses: allowedLicenses.withoutBlanks,
125+ forbiddenLicenses: forbiddenLicenses.withoutBlanks,
126+ skippedPackages: skippedPackages.toSet (),
127+ reporterOutputFormat: ReporterOutputFormat .fromString (reporter),
128+ );
129+ }
130+
131+ /// Whether to disregard licenses that failed to be retrieved.
132+ final bool ignoreRetrievalFailures;
133+
134+ /// The type of dependencies to check licenses for.
135+ final Set <String > dependencyTypes;
136+
137+ /// The only licenses allowed to be used.
138+ final List <String > allowedLicenses;
139+
140+ /// The licenses denied from being used.
141+ final List <String > forbiddenLicenses;
142+
143+ /// Packages skipped from having their licenses checked.
144+ final Set <String > skippedPackages;
145+
146+ /// The format used to list all licenses.
147+ final ReporterOutputFormat ? reporterOutputFormat;
148+ }
149+
68150/// {@template packages_check_licenses_command}
69151/// `very_good packages check licenses` command for checking packages licenses.
70152/// {@endtemplate}
@@ -132,19 +214,27 @@ class PackagesCheckLicensesCommand extends Command<int> {
132214 usageException ('Too many arguments' );
133215 }
134216
135- final ignoreFailures = _argResults['ignore-retrieval-failures' ] as bool ;
136- final dependencyTypes = _argResults['dependency-type' ] as List <String >;
137- final allowedLicenses = _argResults['allowed' ] as List <String >;
138- final forbiddenLicenses = _argResults['forbidden' ] as List <String >;
139- final skippedPackages = _argResults['skip-packages' ] as List <String >;
140- final reporterOutput = _argResults['reporter' ] as String ? ;
217+ final target = _argResults.rest.length == 1 ? _argResults.rest[0 ] : '.' ;
218+ final targetPath = path.normalize (Directory (target).absolute.path);
141219
142- final reporterOutputFormat = ReporterOutputFormat .fromString (
143- reporterOutput,
220+ final VeryGoodConfig config;
221+ try {
222+ config = VeryGoodConfig .loadFromClosestAncestor (Directory (targetPath));
223+ } on VeryGoodConfigParseException catch (e) {
224+ _logger.err (
225+ 'Could not read `$veryGoodConfigFileName `.\n '
226+ '${e .message }' ,
227+ );
228+ return ExitCode .config.code;
229+ }
230+
231+ final options = PackagesCheckLicensesOptions .parse (
232+ _argResults,
233+ config: config,
144234 );
145235
146- allowedLicenses. removeWhere ((license) => license. trim ().isEmpty) ;
147- forbiddenLicenses. removeWhere ((license) => license. trim ().isEmpty) ;
236+ final allowedLicenses = options.allowedLicenses ;
237+ final forbiddenLicenses = options.forbiddenLicenses ;
148238
149239 if (allowedLicenses.isNotEmpty && forbiddenLicenses.isNotEmpty) {
150240 usageException (
@@ -166,8 +256,6 @@ class PackagesCheckLicensesCommand extends Command<int> {
166256 );
167257 }
168258
169- final target = _argResults.rest.length == 1 ? _argResults.rest[0 ] : '.' ;
170- final targetPath = path.normalize (Directory (target).absolute.path);
171259 final targetDirectory = Directory (targetPath);
172260 if (! targetDirectory.existsSync ()) {
173261 _logger.err (
@@ -195,24 +283,24 @@ class PackagesCheckLicensesCommand extends Command<int> {
195283 final filteredDependencies = pubspecLock.packages.where ((dependency) {
196284 if (! dependency.isPubHosted) return false ;
197285
198- if (skippedPackages.contains (dependency.name)) return false ;
286+ if (options. skippedPackages.contains (dependency.name)) return false ;
199287
200288 final dependencyType = dependency.type;
201- return (dependencyTypes.contains ('direct-main' ) &&
289+ return (options. dependencyTypes.contains ('direct-main' ) &&
202290 dependencyType == PubspecLockPackageDependencyType .directMain) ||
203- (dependencyTypes.contains ('direct-dev' ) &&
291+ (options. dependencyTypes.contains ('direct-dev' ) &&
204292 dependencyType == PubspecLockPackageDependencyType .directDev) ||
205- (dependencyTypes.contains ('transitive' ) &&
293+ (options. dependencyTypes.contains ('transitive' ) &&
206294 dependencyType == PubspecLockPackageDependencyType .transitive) ||
207- (dependencyTypes.contains ('direct-overridden' ) &&
295+ (options. dependencyTypes.contains ('direct-overridden' ) &&
208296 dependencyType ==
209297 PubspecLockPackageDependencyType .directOverridden);
210298 });
211299
212300 if (filteredDependencies.isEmpty) {
213301 progress.cancel ();
214302 _logger.info (
215- '''No hosted dependencies found in $targetPath of type: ${dependencyTypes .stringify ()}.''' ,
303+ '''No hosted dependencies found in $targetPath of type: ${options . dependencyTypes .stringify ()}.''' ,
216304 );
217305 return ExitCode .success.code;
218306 }
@@ -240,7 +328,7 @@ class PackagesCheckLicensesCommand extends Command<int> {
240328 if (cachePackageEntry == null ) {
241329 final errorMessage =
242330 '''[$dependencyName ] Could not find cached package path. Consider running `dart pub get` or `flutter pub get` to generate a new `package_config.json`.''' ;
243- if (! ignoreFailures ) {
331+ if (! options.ignoreRetrievalFailures ) {
244332 progress.cancel ();
245333 _logger.err (errorMessage);
246334 return ExitCode .noInput.code;
@@ -256,7 +344,7 @@ class PackagesCheckLicensesCommand extends Command<int> {
256344 if (! packageDirectory.existsSync ()) {
257345 final errorMessage =
258346 '''[$dependencyName ] Could not find package directory at $packagePath .''' ;
259- if (! ignoreFailures ) {
347+ if (! options.ignoreRetrievalFailures ) {
260348 progress.cancel ();
261349 _logger.err (errorMessage);
262350 return ExitCode .noInput.code;
@@ -284,7 +372,7 @@ class PackagesCheckLicensesCommand extends Command<int> {
284372 } on Exception catch (e) {
285373 final errorMessage =
286374 '''[$dependencyName ] Failed to detect license from $packagePath : $e ''' ;
287- if (! ignoreFailures ) {
375+ if (! options.ignoreRetrievalFailures ) {
288376 progress.cancel ();
289377 _logger.err (errorMessage);
290378 return ExitCode .software.code;
@@ -326,7 +414,7 @@ class PackagesCheckLicensesCommand extends Command<int> {
326414 _composeReport (
327415 licenses: licenses,
328416 bannedDependencies: bannedDependencies,
329- reporterOutputFormat: reporterOutputFormat,
417+ reporterOutputFormat: options. reporterOutputFormat,
330418 ),
331419 );
332420
@@ -522,15 +610,20 @@ String _composeBannedReport(_BannedDependencyLicenseMap bannedDependencies) {
522610 return '''${bannedDependencies .length } $prefix $suffix : ${bannedDependenciesList .stringify ()}.''' ;
523611}
524612
525- extension on List <Object > {
613+ extension on Iterable <Object > {
526614 String stringify () {
527615 if (isEmpty) return '' ;
528616 if (length == 1 ) return first.toString ();
529- final last = removeLast ();
530- return '${join (', ' )} and $last ' ;
617+ return '${take (length - 1 ).join (', ' )} and $last ' ;
531618 }
532619}
533620
621+ extension on List <String > {
622+ /// The licenses that are not blank.
623+ List <String > get withoutBlanks =>
624+ where ((license) => license.trim ().isNotEmpty).toList ();
625+ }
626+
534627/// Format type for listing all licenses via --reporter option.
535628enum ReporterOutputFormat {
536629 /// List all licenses separated by a dash.
0 commit comments