diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a84d4b9e1..e3de1d9307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ # Changelog +# 1.2.0 +- Allow to analyze multiple directories ## 1.1.5 - Tweak console reporter diff --git a/README.md b/README.md index 3242e07861..7641e3b938 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ metrics lib ### Full usage: ``` -Usage: metrics [options...] +Usage: metrics [options...] -h, --help Print this usage information. -r, --reporter= The format of the output of the analysis [console (default), json, html, codeclimate] diff --git a/bin/metrics.dart b/bin/metrics.dart index 44de17da39..18fe0d575e 100644 --- a/bin/metrics.dart +++ b/bin/metrics.dart @@ -5,73 +5,104 @@ import 'package:dart_code_metrics/metrics_analyzer.dart'; import 'package:dart_code_metrics/reporters.dart'; import 'package:glob/glob.dart'; -void main(List args) { - const helpFlagName = 'help'; - const reporterOptionName = 'reporter'; - const cyclomaticComplexityThreshold = 'cyclomatic-complexity'; - const linesOfCodeThreshold = 'lines-of-code'; - const verboseName = 'verbose'; - const ignoredFilesName = 'ignore-files'; - const rootFolderName = 'root-folder'; - - var showUsage = false; - - final parser = ArgParser() - ..addFlag(helpFlagName, - abbr: 'h', help: 'Print this usage information.', negatable: false) - ..addOption(reporterOptionName, - abbr: 'r', - help: 'The format of the output of the analysis', - valueHelp: 'console', - allowed: ['console', 'json', 'html', 'codeclimate'], - defaultsTo: 'console') - ..addOption(cyclomaticComplexityThreshold, - help: 'Cyclomatic complexity threshold', - valueHelp: '20', - defaultsTo: '20') - ..addOption(linesOfCodeThreshold, - help: 'Lines of code threshold', valueHelp: '50', defaultsTo: '50') - ..addOption(rootFolderName, - help: 'Root folder', - valueHelp: './', - defaultsTo: Directory.current.path) - ..addOption(ignoredFilesName, - help: 'Filepaths in Glob syntax to be ignored', - valueHelp: '{/**.g.dart,/**.template.dart}', - defaultsTo: '{/**.g.dart,/**.template.dart}') - ..addFlag(verboseName, negatable: false); - - ArgResults arguments; +const _usageHeader = 'Usage: metrics [options...] '; +const _helpFlagName = 'help'; +const _reporterOptionName = 'reporter'; +const _cyclomaticComplexityThreshold = 'cyclomatic-complexity'; +const _linesOfCodeThreshold = 'lines-of-code'; +const _verboseName = 'verbose'; +const _ignoredFilesName = 'ignore-files'; +const _rootFolderName = 'root-folder'; + +final _parser = ArgParser() + ..addFlag(_helpFlagName, + abbr: 'h', help: 'Print this usage information.', negatable: false) + ..addOption(_reporterOptionName, + abbr: 'r', + help: 'The format of the output of the analysis', + valueHelp: 'console', + allowed: ['console', 'json', 'html', 'codeclimate'], + defaultsTo: 'console') + ..addOption(_cyclomaticComplexityThreshold, + help: 'Cyclomatic complexity threshold', + valueHelp: '20', + defaultsTo: '20', callback: (String i) { + if (int.tryParse(i) == null) print('$_cyclomaticComplexityThreshold:'); + }) + ..addOption(_linesOfCodeThreshold, + help: 'Lines of code threshold', + valueHelp: '50', + defaultsTo: '50', callback: (String i) { + if (int.tryParse(i) == null) print('$_linesOfCodeThreshold:'); + }) + ..addOption(_rootFolderName, + help: 'Root folder', valueHelp: './', defaultsTo: Directory.current.path) + ..addOption(_ignoredFilesName, + help: 'Filepaths in Glob syntax to be ignored', + valueHelp: '{/**.g.dart,/**.template.dart}', + defaultsTo: '{/**.g.dart,/**.template.dart}') + ..addFlag(_verboseName, negatable: false); +void main(List args) { try { - arguments = parser.parse(args); - } on FormatException catch (_) { - showUsage = true; - } + final arguments = _parser.parse(args); - if (arguments == null || - arguments[helpFlagName] as bool || - arguments.rest.length != 1) { - showUsage = true; - } else if (!Directory(arguments.rest.single).existsSync()) { - print("Can't find directory ${arguments.rest.single}"); - showUsage = true; - } + if (arguments[_helpFlagName] as bool) { + _showUsageAndExit(0); + } + + if (arguments.rest.isEmpty) { + throw _InvalidArgumentException( + 'Invalid number of directories. At least one must be specified'); + } + + arguments.rest.forEach((p) { + if (!Directory(p).existsSync()) { + throw _InvalidArgumentException( + "$p doesn't exist or isn't a directory"); + } + }); - if (showUsage) { - print('Usage: metrics [options...] '); - print(parser.usage); - return; + // TODO: check that directories to analyze are all children of root folder + + _runAnalysis( + arguments[_rootFolderName] as String, + arguments.rest, + arguments[_ignoredFilesName] as String, + int.parse(arguments[_cyclomaticComplexityThreshold] as String), + int.parse(arguments[_linesOfCodeThreshold] as String), + arguments[_reporterOptionName] as String, + arguments[_verboseName] as bool); + } on FormatException catch (e) { + print('${e.message}\n'); + _showUsageAndExit(1); + } on _InvalidArgumentException catch (e) { + print('${e.message}\n'); + _showUsageAndExit(1); } +} + +void _showUsageAndExit(int exitCode) { + print(_usageHeader); + print(_parser.usage); + exit(exitCode); +} - final rootFolder = arguments[rootFolderName] as String; - var dartFilePaths = Glob('${arguments.rest.single}**.dart') - .listSync(root: rootFolder, followLinks: false) - .whereType() - .map((entity) => entity.path); +void _runAnalysis( + String rootFolder, + Iterable analysisDirectories, + String ignoreFilesPattern, + int cyclomaticComplexityThreshold, + int linesOfCodeThreshold, + String reporterType, + bool verbose) { + var dartFilePaths = analysisDirectories.expand((directory) => + Glob('$directory**.dart') + .listSync(root: rootFolder, followLinks: false) + .whereType() + .map((entity) => entity.path)); - final ignoreFilesPattern = arguments[ignoredFilesName] as Object; - if (ignoreFilesPattern is String && ignoreFilesPattern.isNotEmpty) { + if (ignoreFilesPattern.isNotEmpty) { final ignoreFilesGlob = Glob(ignoreFilesPattern); dartFilePaths = dartFilePaths.where((path) => !ignoreFilesGlob.matches(path)); @@ -84,17 +115,14 @@ void main(List args) { ..run(); final config = Config( - cyclomaticComplexityWarningLevel: - int.parse(arguments[cyclomaticComplexityThreshold] as String), - linesOfCodeWarningLevel: - int.parse(arguments[linesOfCodeThreshold] as String)); + cyclomaticComplexityWarningLevel: cyclomaticComplexityThreshold, + linesOfCodeWarningLevel: linesOfCodeThreshold); Reporter reporter; - switch (arguments[reporterOptionName] as String) { + switch (reporterType) { case 'console': - reporter = ConsoleReporter( - reportConfig: config, reportAll: arguments[verboseName] as bool); + reporter = ConsoleReporter(reportConfig: config, reportAll: verbose); break; case 'json': reporter = JsonReporter(reportConfig: config); @@ -106,9 +134,13 @@ void main(List args) { reporter = CodeClimateReporter(reportConfig: config); break; default: - throw ArgumentError.value( - arguments[reporterOptionName], reporterOptionName); + throw ArgumentError.value(reporterType, 'reporter'); } reporter.report(runner.results()).forEach(print); } + +class _InvalidArgumentException implements Exception { + final String message; + _InvalidArgumentException(this.message); +} diff --git a/pubspec.yaml b/pubspec.yaml index 566ba876db..605b0eb63e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: dart_code_metrics description: | Command line tool which helps to improve code quality. Reports cyclomatic complexity and method length in plain console, JSON, HTML and Codeclimate formats -version: 1.1.5 +version: 1.2.0 homepage: https://github.com/wrike/dart-code-metrics environment: