Skip to content

Commit 082e55d

Browse files
committed
feat: implement responsibility chain pattern with middlewares
1 parent bfc46d2 commit 082e55d

File tree

10 files changed

+128
-64
lines changed

10 files changed

+128
-64
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## 1.3.0
4+
- Migrate filters to responsibility chain pattern
5+
36
## 1.2.0
47
- Implement `debounce` delay
58
- Ignore `.dart_tool` and IDE directories by default

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,13 @@ void main() {
8989
));
9090
9191
final watcher = Watcher(
92-
includes: [Glob("**.dart")],
9392
onStart: () => print('Watching for changes...'),
93+
middlewares: [
94+
IgnoreMiddleware(['~', '.dart_tool', '.git', '.idea', '.vscode']),
95+
ExcludeMiddleware(config?.excludes ?? []),
96+
DebounceMiddleware(5, dateTime),
97+
IncludeMiddleware([Glob("**.dart")]),
98+
],
9499
onFileChange: (int eventType, File file) async {
95100
final action = switch (eventType) {
96101
FileSystemEvent.create => 'created',

bin/hmr.dart

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import 'dart:io';
22

33
import 'package:glob/glob.dart';
44
import 'package:hmr/hmr.dart';
5+
import 'package:hmr/src/middlewares/debounce.dart';
6+
import 'package:hmr/src/middlewares/exclude.dart';
7+
import 'package:hmr/src/middlewares/ignore.dart';
8+
import 'package:hmr/src/middlewares/include.dart';
59
import 'package:mansion/mansion.dart';
610
import 'package:path/path.dart' as path;
711

@@ -41,10 +45,14 @@ void main(List<String> arguments) async {
4145

4246
(File, int)? lastFileChanged;
4347

48+
final dateTime = DateTime.now();
4449
final watcher = Watcher(
45-
includes: config?.includes ?? [Glob("**.dart")],
46-
excludes: config?.excludes ?? [],
47-
debounce: config?.debounce ?? 5,
50+
middlewares: [
51+
IgnoreMiddleware(['~', '.dart_tool', '.git', '.idea', '.vscode']),
52+
ExcludeMiddleware(config?.excludes ?? []),
53+
DebounceMiddleware(Duration(milliseconds: config?.debounce ?? 5), dateTime),
54+
IncludeMiddleware(config?.includes ?? [Glob("**.dart")]),
55+
],
4856
onStart: () {
4957
final List<Sequence> sequences = [
5058
const CursorPosition.moveTo(0, 0),

lib/src/contracts/hmr.dart

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import 'dart:async';
22
import 'dart:io';
33

4-
import 'package:glob/glob.dart';
5-
64
abstract interface class RunnerContract {
75
/// The entrypoint file to run.
86
File get entrypoint;
@@ -18,14 +16,8 @@ abstract interface class RunnerContract {
1816
}
1917

2018
abstract interface class WatcherContract {
21-
/// The list of files to exclude.
22-
List<Glob> get includes;
23-
24-
/// The list of files to exclude.
25-
List<Glob> get excludes;
26-
27-
/// The debounce time to wait before trigger the event.
28-
int get debounce;
19+
/// The list of executed middlewares before trigger the watcher emitter.
20+
List<MiddlewareWatcher> get middlewares;
2921

3022
/// Emitted when the watcher is started.
3123
FutureOr Function()? get onStart;
@@ -48,3 +40,10 @@ abstract interface class WatcherContract {
4840
/// Emitted when the watcher is started.
4941
void watch();
5042
}
43+
44+
/// The next function to execute the next middleware.
45+
typedef NextFn = Function();
46+
abstract interface class MiddlewareWatcher {
47+
/// Handle the [FileSystemEvent] and execute the next middleware.
48+
void handle(FileSystemEvent event, NextFn next);
49+
}

lib/src/entities/watcher.dart

Lines changed: 27 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,13 @@
11
import 'dart:async';
22
import 'dart:io';
33

4-
import 'package:glob/glob.dart';
54
import 'package:hmr/src/contracts/hmr.dart';
65

76
final class Watcher implements WatcherContract {
87
StreamSubscription<FileSystemEvent>? _subscription;
98

10-
DateTime? _dateTime = DateTime.now();
11-
12-
@override
13-
final List<Glob> includes;
14-
159
@override
16-
final List<Glob> excludes;
17-
18-
@override
19-
final int debounce;
10+
late final List<MiddlewareWatcher> middlewares;
2011

2112
@override
2213
final FutureOr Function()? onStart;
@@ -37,59 +28,46 @@ final class Watcher implements WatcherContract {
3728
final FutureOr Function(int type, File)? onFileChange;
3829

3930
Watcher({
40-
this.excludes = const [],
41-
this.includes = const [],
42-
this.debounce = 5,
31+
List<MiddlewareWatcher>? middlewares,
4332
this.onStart,
4433
this.onFileChange,
4534
this.onFileCreate,
4635
this.onFileDelete,
4736
this.onFileMove,
4837
this.onFileModify,
49-
});
38+
}) : middlewares = middlewares ?? [];
5039

5140
@override
5241
void watch() {
5342
onStart?.call();
5443

5544
_subscription = Directory.current.watch(recursive: true).listen((event) {
56-
if (_dateTime case DateTime value
57-
when DateTime.now().difference(value) < Duration(milliseconds: debounce)) {
58-
return;
59-
}
60-
61-
final ignoredFiles = event.path.endsWith('~') ||
62-
event.path.contains('.idea') ||
63-
event.path.contains('.git') ||
64-
event.path.contains('.dart_tool');
65-
66-
if (ignoredFiles) {
67-
return;
68-
}
69-
70-
final hasExclude = excludes.any((glob) => glob.matches(event.path));
71-
if (hasExclude) {
72-
return;
73-
}
74-
75-
final hasMatch = includes.any((glob) => glob.matches(event.path));
76-
if (hasMatch) {
77-
switch (event.type) {
78-
case FileSystemEvent.modify:
79-
onFileModify?.call(File(event.path));
80-
case FileSystemEvent.create:
81-
onFileCreate?.call(File(event.path));
82-
case FileSystemEvent.delete:
83-
onFileDelete?.call(File(event.path));
84-
case FileSystemEvent.move:
85-
onFileMove?.call(File(event.path));
86-
default:
87-
() => throw 'Unknown event type: ${event.type}';
45+
int currentMiddleware = 0;
46+
47+
void executeNext() {
48+
if (currentMiddleware < middlewares.length) {
49+
final middleware = middlewares[currentMiddleware];
50+
currentMiddleware++;
51+
middleware.handle(event, executeNext);
52+
} else {
53+
switch (event.type) {
54+
case FileSystemEvent.modify:
55+
onFileModify?.call(File(event.path));
56+
case FileSystemEvent.create:
57+
onFileCreate?.call(File(event.path));
58+
case FileSystemEvent.delete:
59+
onFileDelete?.call(File(event.path));
60+
case FileSystemEvent.move:
61+
onFileMove?.call(File(event.path));
62+
default:
63+
() => throw 'Unknown event type: ${event.type}';
64+
}
65+
66+
onFileChange?.call(event.type, File(event.path));
8867
}
89-
90-
onFileChange?.call(event.type, File(event.path));
91-
_dateTime = DateTime.now();
9268
}
69+
70+
executeNext();
9371
});
9472
}
9573

lib/src/middlewares/debounce.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import 'dart:io';
2+
3+
import 'package:hmr/hmr.dart';
4+
5+
final class DebounceMiddleware implements MiddlewareWatcher {
6+
final Duration _debounce;
7+
DateTime _dateTime;
8+
9+
DebounceMiddleware(this._debounce, this._dateTime);
10+
11+
@override
12+
void handle(FileSystemEvent event, NextFn next) {
13+
if (!(DateTime.now().difference(_dateTime) < _debounce)) {
14+
next();
15+
_dateTime = DateTime.now();
16+
}
17+
}
18+
}

lib/src/middlewares/exclude.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import 'dart:io';
2+
3+
import 'package:glob/glob.dart';
4+
import 'package:hmr/hmr.dart';
5+
6+
final class ExcludeMiddleware implements MiddlewareWatcher {
7+
final List<Glob> _globs;
8+
9+
ExcludeMiddleware(this._globs);
10+
11+
@override
12+
void handle(FileSystemEvent event, NextFn next) {
13+
final hasExclude = _globs.any((glob) => glob.matches(event.path));
14+
if (!hasExclude) {
15+
next();
16+
}
17+
}
18+
}

lib/src/middlewares/ignore.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import 'dart:io';
2+
3+
import 'package:hmr/hmr.dart';
4+
5+
final class IgnoreMiddleware implements MiddlewareWatcher {
6+
final List<String> _matches;
7+
8+
IgnoreMiddleware(this._matches);
9+
10+
@override
11+
void handle(FileSystemEvent event, NextFn next) {
12+
final ignoredFiles = _matches.any((match) => event.path.contains(match));
13+
if (!ignoredFiles) {
14+
next();
15+
}
16+
}
17+
}

lib/src/middlewares/include.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import 'dart:io';
2+
3+
import 'package:glob/glob.dart';
4+
import 'package:hmr/hmr.dart';
5+
6+
final class IncludeMiddleware implements MiddlewareWatcher {
7+
final List<Glob> _globs;
8+
9+
IncludeMiddleware(this._globs);
10+
11+
@override
12+
void handle(FileSystemEvent event, NextFn next) {
13+
final hasMatch = _globs.any((glob) => glob.matches(event.path));
14+
if (hasMatch) {
15+
next();
16+
}
17+
}
18+
}

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: hmr
22
description: Hot Module Replacement system specially designed for command-line Dart applications.
3-
version: 1.2.0
3+
version: 1.3.0
44
repository: https://github.com/LeadcodeDev/hmr
55
executables:
66
hmr: hmr

0 commit comments

Comments
 (0)