c6404cbc416461a84236b3bf4c5f0f506f2e0ddb.svn-base
5.98 KB
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
/*
* grunt
* http://gruntjs.com/
*
* Copyright (c) 2012 "Cowboy" Ben Alman
* Licensed under the MIT license.
* https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
*/
module.exports = function(grunt) {
// Nodejs libs.
var fs = require('fs');
var path = require('path');
// In Nodejs 0.8.0, existsSync moved from path -> fs.
var existsSync = fs.existsSync || path.existsSync;
// ==========================================================================
// TASKS
// ==========================================================================
// Keep track of last modified times of files, in case files are reported to
// have changed incorrectly.
var mtimes = {};
grunt.registerTask('watch', 'Run predefined tasks whenever watched files change.', function(target) {
this.requiresConfig('watch');
// Build an array of files/tasks objects.
var watch = grunt.config('watch');
var targets = target ? [target] : Object.keys(watch).filter(function(key) {
return typeof watch[key] !== 'string' && !Array.isArray(watch[key]);
});
targets = targets.map(function(target) {
// Fail if any required config properties have been omitted.
target = ['watch', target];
this.requiresConfig(target.concat('files'), target.concat('tasks'));
return grunt.config(target);
}, this);
// Allow "basic" non-target format.
if (typeof watch.files === 'string' || Array.isArray(watch.files)) {
targets.push({files: watch.files, tasks: watch.tasks});
}
grunt.log.write('Waiting...');
// This task is asynchronous.
var taskDone = this.async();
// Get a list of files to be watched.
var patterns = grunt.utils._.chain(targets).pluck('files').flatten().uniq().value();
var getFiles = grunt.file.expandFiles.bind(grunt.file, patterns);
// The tasks to be run.
var tasks = []; //grunt.config(tasksProp);
// This task's name + optional args, in string format.
var nameArgs = this.nameArgs;
// An ID by which the setInterval can be canceled.
var intervalId;
// Files that are being watched.
var watchedFiles = {};
// File changes to be logged.
var changedFiles = {};
// Define an alternate fail "warn" behavior.
grunt.fail.warnAlternate = function() {
grunt.task.clearQueue({untilMarker: true}).run(nameArgs);
};
// Cleanup when files have changed. This is debounced to handle situations
// where editors save multiple files "simultaneously" and should wait until
// all the files are saved.
var done = grunt.utils._.debounce(function() {
// Clear the files-added setInterval.
clearInterval(intervalId);
// Ok!
grunt.log.ok();
var fileArray = Object.keys(changedFiles);
fileArray.forEach(function(filepath) {
// Log which file has changed, and how.
grunt.log.ok('File "' + filepath + '" ' + changedFiles[filepath] + '.');
// Clear the modified file's cached require data.
grunt.file.clearRequireCache(filepath);
});
// Unwatch all watched files.
Object.keys(watchedFiles).forEach(unWatchFile);
// For each specified target, test to see if any files matching that
// target's file patterns were modified.
targets.forEach(function(target) {
var files = grunt.file.expandFiles(target.files);
var intersection = grunt.utils._.intersection(fileArray, files);
// Enqueue specified tasks if a matching file was found.
if (intersection.length > 0 && target.tasks) {
grunt.task.run(target.tasks).mark();
}
});
// Enqueue the watch task, so that it loops.
grunt.task.run(nameArgs);
// Continue task queue.
taskDone();
}, 250);
// Handle file changes.
function fileChanged(status, filepath) {
// If file was deleted and then re-added, consider it changed.
if (changedFiles[filepath] === 'deleted' && status === 'added') {
status = 'changed';
}
// Keep track of changed status for later.
changedFiles[filepath] = status;
// Execute debounced done function.
done();
}
// Watch a file.
function watchFile(filepath) {
if (!watchedFiles[filepath]) {
// Watch this file for changes. This probably won't scale to hundreds of
// files.. but I bet someone will try it!
watchedFiles[filepath] = fs.watch(filepath, function(event) {
var mtime;
// Has the file been deleted?
var deleted = !existsSync(filepath);
if (deleted) {
// If file was deleted, stop watching file.
unWatchFile(filepath);
// Remove from mtimes.
delete mtimes[filepath];
} else {
// Get last modified time of file.
mtime = +fs.statSync(filepath).mtime;
// If same as stored mtime, the file hasn't changed.
if (mtime === mtimes[filepath]) { return; }
// Otherwise it has, store mtime for later use.
mtimes[filepath] = mtime;
}
// Call "change" for this file, setting status appropriately (rename ->
// renamed, change -> changed).
fileChanged(deleted ? 'deleted' : event + 'd', filepath);
});
}
}
// Unwatch a file.
function unWatchFile(filepath) {
if (watchedFiles[filepath]) {
// Close watcher.
watchedFiles[filepath].close();
// Remove from watched files.
delete watchedFiles[filepath];
}
}
// Watch all currently existing files for changes.
getFiles().forEach(watchFile);
// Watch for files to be added.
intervalId = setInterval(function() {
// Files that have been added since last interval execution.
var added = grunt.utils._.difference(getFiles(), Object.keys(watchedFiles));
added.forEach(function(filepath) {
// This file has been added.
fileChanged('added', filepath);
// Watch this file.
watchFile(filepath);
});
}, 200);
});
};