-
Notifications
You must be signed in to change notification settings - Fork 1
/
wtchr.js
230 lines (192 loc) · 4.51 KB
/
wtchr.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
var fs = require("fs"),
_ = require("underscore"),
crypto = require("crypto"),
minimatch = require("minimatch");
function wtchr(path, options) {
// Watch options
var o = _.extend({
persistent: true,
interval: 500,
ignore: /(\/\.DS_Store$)/
}, options || {}),
// Cache watch contexts by path
cache = {
},
// Events stack
events = {
},
// Wtchr object
Wtchr = {
// Bind event
on: function (types, match, handle) {
// Without minimatch selector
if (typeof match !== "string") {
handle = match;
match = undefined;
}
// Bind each event type
types.replace(/^\s+|\s+$/g, "")
.split(/\s+/)
.forEach(function (type) {
var eventStack = events[type] || (events[type] = []);
// Register event
eventStack.push({
match: match,
handle: handle
});
});
return Wtchr;
},
// Unbind event
off: function (types, match, handle) {
// Unbind all events
if (!arguments.length) {
events = {};
}
else {
// Without minimatch selector
if (typeof match !== "string") {
handle = match;
match = undefined;
}
// Unbind each event type
types.replace(/^\s+|\s+$/g, "")
.split(/\s+/)
.forEach(function (type) {
var eventStack = events[type];
if (eventStack) {
eventStack = eventStack.filter(function (event) {
return !((match === undefined || match === event.match) && (handle === undefined || handle === event.handle));
});
if (!eventStack.length) {
delete events[type];
}
}
});
}
return Wtchr;
},
// Destroy watcher and stop all watchings
destroy: function () {
Wtchr.off();
unwatch();
}
};
// Event trigger handle
function trigger(type, path, curr, prev) {
(events[type] || [])
.forEach(function (event) {
if (event.match === undefined || minimatch(path.replace(/^\.\//), event.match)) {
event.handle(path, curr, prev);
}
});
}
// Create hash from file
function hash(path) {
var hex = crypto.createHash("md5");
hex.update(fs.readFileSync(path));
return hex.digest("hex");
}
// Stop watching
function unwatch(path) {
// Unwatch all
if (!arguments.length) {
for (path in cache) {
unwatch(cache[path]);
}
}
// Unwatch by path
else if (cache[path]) {
fs.unwatchFile(path, cache[path].handle);
delete cache[path];
}
}
// Start watching
function watch(path, childs) {
var context = cache[path] = {
childs: childs,
hash: childs ? false : hash(path),
handle: function (curr, prev) {
var eventType = fs.existsSync(path) ? "change" : "delete";
// Delete
if (eventType === "delete") {
trigger(eventType, path, curr, prev);
unwatch(path);
}
// Create
else if (childs) {
var childsCurr = fs.readdirSync(path);
(childsCurr)
.filter(function (child) {
// Filter for new childs
return childs.indexOf(child) === -1;
})
.forEach(function (child) {
// Trigger create and init recursive
init(path + "/" + child, true);
});
// Refresh child list
childs = childsCurr;
}
// Change
else {
var hashCurr = hash(path);
if (hashCurr !== context.hash) {
context.hash = hashCurr;
trigger(eventType, path, curr, prev);
}
}
}
};
// Create watch worker
fs.watchFile(path, {
persistent: o.persistent,
interval: o.interval
}, context.handle);
}
// Initialize a watch path
function init(path, created) {
var childs,
isDir;
// Trim trailing slash
path = path.replace(/\/$/, "");
// Break if ignore matches
if (o.ignore.test(path)) {
return;
}
// Check whether current path is dir
isDir = typeof isDir === "undefined" ? fs.lstatSync(path)
.isDirectory() : isDir;
// Trigger create event if specified
if (created) {
trigger("create", path, fs.statSync(path));
}
// Init watch
watch(path, isDir ? (childs = fs.readdirSync(path)) : false);
if (isDir) {
// Walk recursive
childs.forEach(function (child) {
init(path + "/" + child, created);
});
}
}
// Initialize
init(path);
// Return wtchr object
return Wtchr;
};
// Example
/*
wtchr("./")
.on("create", function (path) {
console.log("CREATE: ", path)
})
.on("change", function (path) {
console.log("CHANGE: ", path)
})
.on("delete", function (path) {
console.log("DELETE: ", path)
});
*/
// Expose module
module.exports = wtchr;