grunt-simple-watch-legacy
Version:
no need to change your gruntfile configurations
242 lines (215 loc) • 7.42 kB
JavaScript
/*global module, require */
/*
* grunt
* https://github.com/cowboy/grunt
*
*
* Copyright (c) 2012 "Cowboy" Ben Alman
* Licensed under the MIT license.
* http://benalman.com/about/license/
*
* Simple Watch task
* Copyright (c) 2012 Amir Souchami
* This file is based on the grunt/tasks/watch.js written by "Cowboy" Ben Alman
* changes where made to get rid of the problematic use of fs.watch because sadly it did not respond on my server setup
*
* Licensed under the MIT license.
* http://benalman.com/about/license/
*/
module.exports = function (grunt) {
'use strict';
// Nodejs libs.
var fs = require('fs');
var _ = require('lodash');
// ==========================================================================
// TASKS
// ==========================================================================
// Keep track of last modified times of files, in case files are reported to
// have changed incorrectly.
var mtimes = {};
grunt.registerTask('simple-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'),
targets = target ? [target] : Object.keys(watch).filter(function (key) {
return typeof watch[key] !== 'string' && !Array.isArray(watch[key]);
}),
taskDone,
patterns,
getFiles,
tasks,
nameArgs,
intervalId, // An ID by which the setInterval can be canceled.
watchedFiles,
changedFiles,
done;
targets = targets.map(function (target) {
var name = target,
targetData;
// Fail if any required config properties have been omitted.
target = ['watch', target];
this.requiresConfig(target.concat('files'), target.concat('tasks'));
targetData = grunt.config(target);
targetData.name = name;
return targetData;
}, 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.writeln('Simple Watch, Waiting...');
grunt.log.writeln('Simple Watch, Waiting...');
// This task is asynchronous.
taskDone = this.async();
// Get a list of files to be watched.
patterns = _.chain(targets).map('files').flatten().uniq().value();
getFiles = grunt.file.expand.bind(grunt.file, patterns);
// The tasks to be run.
tasks = []; //grunt.config(tasksProp);
// This task's name + optional args, in string format.
nameArgs = this.nameArgs;
// Files that are being watched.
watchedFiles = {};
// File changes to be logged.
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.
done = _.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.
// Removed: Grunt 0.4 does not support this anymore
// 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.expand(target.files),
intersection = _.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;
// Emit watch events if anyone is listening
if (grunt.event.listeners('watch').length > 0) {
var matchingTargets = [];
targets.forEach(function (target) {
if (grunt.file.match(target.files, filepath).length > 0) {
matchingTargets.push(target.name);
}
});
matchingTargets.forEach(function (matchingTarget) {
grunt.event.emit('watch', status, filepath, matchingTarget);
});
}
// Keep track of changed status for later.
changedFiles[filepath] = status;
// Execute debounced done function.
done();
}
// Watch a file.
function watchFile(filepath) {
if (!watchedFiles[filepath]) {
// add a new file to watched files
var statStructure = null;
try {
statStructure = fs.statSync(filepath);
} catch (e) { // StatSync can throw an error if the file has dissapeared in between
return;
}
watchedFiles[filepath] = statStructure;
mtimes[filepath] = +watchedFiles[filepath].mtime;
}
}
// Unwatch a file.
function unWatchFile(filepath) {
if (watchedFiles[filepath]) {
// Remove from watched files.
delete watchedFiles[filepath];
}
}
function rerun() {
// Clear queue and rerun to prevent failing
grunt.task.clearQueue();
grunt.task.run('simple-watch');
}
grunt.warn = grunt.fail.warn = function (e) {
var message = typeof e === 'string' ? e : e.message;
grunt.log.writeln(('Warning: ' + message).yellow);
if (!grunt.option('force')) {
rerun();
}
};
grunt.fatal = grunt.fail.fatal = function (e) {
var message = typeof e === 'string' ? e : e.message;
grunt.log.writeln(('Fatal error: ' + message).red);
rerun();
};
// Watch all currently existing files for changes.
getFiles().forEach(watchFile);
// Watch for files to be added.
intervalId = setInterval(function () {
var currFiles = getFiles(),
lastWatched = Object.keys(watchedFiles),
added,
deleted;
// Files that have been added since last interval execution.
added = _.difference(currFiles, lastWatched);
added.forEach(function (filepath) {
// This file has been added.
fileChanged('added', filepath);
// Watch this file.
watchFile(filepath);
});
// Files that have been deleted since last interval execution.
deleted = _.difference(lastWatched, currFiles);
deleted.forEach(function (filepath) {
// This file has been deleted.
fileChanged('deleted', filepath);
// UN-Watch this file.
unWatchFile(filepath);
});
currFiles.forEach(function (filepath) {
if (grunt.file.exists(filepath)) {
// Get last modified time of file.
var 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;
// the file has been changed
fileChanged('changed', filepath);
}
});
}, 200);
});
};