Skip to content

Commit

Permalink
Change command line arguments.
Browse files Browse the repository at this point in the history
--path or -p to specify the path to the module
--max-depth or -d to specify the max depth (previously unavailable)
  • Loading branch information
dam5s committed Oct 6, 2023
1 parent 652602b commit ce5d1de
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 29 deletions.
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ install:
dart pub get

check:
dart format lib --line-length 100 --set-exit-if-changed;
dart scripts/generate_big_codebase.dart;
dart test;
dart format lib --line-length 100 --set-exit-if-changed
dart scripts/generate_big_codebase.dart
dart test
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ Again, from inside your Dart project
```
dart run cyclic_dependency_checks
```

## Command-Line flags

* `--path` or `-p` for the path to the module that is checked, defaults to current working directory.
* `--max-depth` or `-d` to limit the depth of the check, defaults to no max depth.
28 changes: 3 additions & 25 deletions bin/cyclic_dependency_checks.dart
Original file line number Diff line number Diff line change
@@ -1,33 +1,11 @@
import 'dart:io';

import 'package:cyclic_dependency_checks/cycle_detection/cycle_detector.dart';
import 'package:cyclic_dependency_checks/cycle_detection/module_dependency.dart';
import 'package:cyclic_dependency_checks/cycle_detection/module_dependency_graph.dart';
import 'package:cyclic_dependency_checks/cycle_detection/cycle_detector_runner.dart';

void main(List<String> args) async {
await _run(path: args.firstOrNull ?? '.');
}

Future _run({required String path}) async {
final stopwatch = Stopwatch()..start();
final cycles = await CycleDetector().detect(path);
stopwatch.stop();
final success = await CycleDetectorRunner().run(args);

final formattedTime = stopwatch.elapsed.toString().substring(0, 11);

if (cycles.isNotEmpty) {
stderr.writeln('Detected cycles after ${formattedTime}');
for (final cycle in cycles) {
cycle.printError();
}
if (!success) {
exit(1);
}

stdout.writeln('No import cycles were detected after ${formattedTime}');
}

extension ErrorPrinting on List<ModuleDependency> {
void printError() {
stderr.writeln(path().join(' -> '));
}
}
86 changes: 86 additions & 0 deletions lib/cycle_detection/cycle_detector_runner.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import 'dart:io';

import 'package:args/args.dart';

import 'cycle_detector.dart';
import 'module_dependency_graph.dart';

class Printer {
out(String text) => stdout.writeln(text);

err(String text) => stderr.writeln(text);
}

class CycleDetectorRunner {
final CycleDetector detector;
final Printer printer;

CycleDetectorRunner({
CycleDetector? detector,
Printer? printer,
}) : detector = detector ?? CycleDetector(),
printer = printer ?? Printer();

Future<bool> run(List<String> args) async {
final parser = ArgParser()
..addOption('path', abbr: 'p')
..addOption('max-depth', abbr: 'd');

final parsedArgs = parser.tryParse(args);
if (parsedArgs == null) {
return false;
}

final path = parsedArgs.getString('path', fallback: '.');
final maxDepth = parsedArgs.tryGetInt('max-depth');

final stopwatch = Stopwatch()..start();
final cycles = await detector.detect(path, maxDepth: maxDepth);

stopwatch.stop();

final formattedTime = stopwatch.elapsed.toString().substring(0, 11);

if (cycles.isNotEmpty) {
printer.err('Detected cycles after ${formattedTime}');

for (final cycle in cycles) {
printer.err(cycle.path().join(' -> '));
}

return false;
}

printer.out('No import cycles were detected after ${formattedTime}');

return true;
}
}

extension _SafeParse on ArgParser {
ArgResults? tryParse(List<String> args, {Printer? printer}) {
try {
return parse(args);
} catch (e) {
printer?.err('Failed to parse arguments');
printer?.err(usage);
return null;
}
}
}

extension _TypeSafeArgs on ArgResults {
String getString(String name, {required String fallback}) {
return this[name] ?? fallback;
}

int? tryGetInt(String name) {
final value = this[name];

if (value == null) {
return null;
}

return int.tryParse(value);
}
}
2 changes: 1 addition & 1 deletion pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ packages:
source: hosted
version: "6.1.0"
args:
dependency: transitive
dependency: "direct main"
description:
name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
Expand Down
2 changes: 2 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ environment:

dev_dependencies:
test: ^1.24.5
dependencies:
args: ^2.4.2
98 changes: 98 additions & 0 deletions test/cycle_detector_runner_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import 'package:cyclic_dependency_checks/cycle_detection/cycle_detector.dart';
import 'package:cyclic_dependency_checks/cycle_detection/cycle_detector_runner.dart';
import 'package:cyclic_dependency_checks/cycle_detection/module_dependency.dart';
import 'package:test/test.dart';

class TestCycleDetector extends CycleDetector {
List<List<ModuleDependency>> stubbedCycles = [];
String? recordedPackagePath;
int? recordedMaxDepth;

@override
Future<List<List<ModuleDependency>>> detect(String packagePath, {int? maxDepth}) async {
recordedPackagePath = packagePath;
recordedMaxDepth = maxDepth;
return stubbedCycles;
}
}

class RecordingPrinter extends Printer {
final List<String> outLog = [];
final List<String> errLog = [];

out(String text) => outLog.add(text);

err(String text) => errLog.add(text);
}

void main() {
test('with no arguments, on success', () async {
final printer = RecordingPrinter();
final detector = TestCycleDetector();
final runner = CycleDetectorRunner(detector: detector, printer: printer);

final success = await runner.run([]);

expect(success, isTrue);
expect(printer.errLog, isEmpty);
expect(detector.recordedPackagePath, equals('.'));
expect(detector.recordedMaxDepth, isNull);
});

test('with no arguments, on error', () async {
final printer = RecordingPrinter();
final detector = TestCycleDetector()
..stubbedCycles.add([
ModuleDependency(from: Module('a'), to: Module('b')),
ModuleDependency(from: Module('b'), to: Module('a')),
]);
final runner = CycleDetectorRunner(detector: detector, printer: printer);

final success = await runner.run([]);

expect(success, isFalse);
expect(printer.errLog, contains('module:a -> module:b -> module:a'));
});

test('with full length args', () async {
final printer = RecordingPrinter();
final detector = TestCycleDetector();
final runner = CycleDetectorRunner(detector: detector, printer: printer);

await runner.run([
'--path', '/some/path',
'--max-depth', '10',
]);

expect(detector.recordedPackagePath, equals('/some/path'));
expect(detector.recordedMaxDepth, equals(10));
});

test('with short name args', () async {
final printer = RecordingPrinter();
final detector = TestCycleDetector();
final runner = CycleDetectorRunner(detector: detector, printer: printer);

await runner.run([
'-p', '/some/path',
'-d', '10',
]);

expect(detector.recordedPackagePath, equals('/some/path'));
expect(detector.recordedMaxDepth, equals(10));
});

test('with invalid arguments', () async {
final printer = RecordingPrinter();
final detector = TestCycleDetector();
final runner = CycleDetectorRunner(detector: detector, printer: printer);

final success = await runner.run([
'--oops'
]);

expect(success, equals(false));
expect(detector.recordedPackagePath, isNull);
expect(detector.recordedMaxDepth, isNull);
});
}

0 comments on commit ce5d1de

Please sign in to comment.