Skip to content

Commit

Permalink
Merge pull request #94 from jorrit/tolerance
Browse files Browse the repository at this point in the history
Add option to ignore small modification time differences.
  • Loading branch information
tschaub committed Apr 13, 2016
2 parents 31e3f4b + a81a237 commit 77723b5
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 30 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,29 @@ Example use of the `override` option:
});
```

#### <a id="optionstolerance">options.tolerance</a>
* type: `number` (milliseconds)
* default: `0`

The `newer` tasks compares the file modification times of the source and destination files with millisecond precision.
On some file systems this causes destination files to always be considered older because of imprecision in the registration of modification times.

If your tasks are always run even though the source files are not modified use the `tolerance` option to compensate for this imprecision.

In most cases setting the option to `1000` milliseconds should be enough. If the file system is very imprecise use a higher value.

Example use of the `tolerance` option:

```js
grunt.initConfig({
newer: {
options: {
tolerance: 1000
}
}
});
```

## That's it

Please [submit an issue](https://github.com/tschaub/grunt-newer/issues) if you encounter any trouble. Contributions or suggestions for improvements welcome!
Expand Down
32 changes: 24 additions & 8 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,27 @@ var path = require('path');

var async = require('async');


/**
* Filter a list of files by mtime.
* @param {Array.<string>} paths List of file paths.
* @param {Date} time The comparison time.
* @param {number} tolerance Maximum time in milliseconds that the destination
* file is allowed to be newer than the source file to compensate for
* imprecisions in modification times in file systems.
* @param {function(string, Date, function(boolean))} override Override.
* @param {function(Err, Array.<string>)} callback Callback called with any
* error and a list of files that have mtimes newer than the provided time.
*/
var filterPathsByTime = exports.filterPathsByTime = function(paths, time,
override, callback) {
tolerance, override, callback) {
async.map(paths, fs.stat, function(err, stats) {
if (err) {
return callback(err);
}

var olderPaths = [];
var newerPaths = paths.filter(function(filePath, index) {
var newer = stats[index].mtime > time;
var newer = stats[index].mtime - time > tolerance;
if (!newer) {
olderPaths.push(filePath);
}
Expand All @@ -42,12 +44,16 @@ var filterPathsByTime = exports.filterPathsByTime = function(paths, time,
* Determine if any of the given files are newer than the provided time.
* @param {Array.<string>} paths List of file paths.
* @param {Date} time The comparison time.
* @param {number} tolerance Maximum time in milliseconds that the destination
* file is allowed to be newer than the source file to compensate for
* imprecisions in modification times in file systems.
* @param {function(string, Date, function(boolean))} override Override.
* @param {function(Err, boolean)} callback Callback called with any error and
* a boolean indicating whether any one of the supplied files is newer than
* the comparison time.
*/
var anyNewer = exports.anyNewer = function(paths, time, override, callback) {
var anyNewer = exports.anyNewer = function(paths, time, tolerance, override,
callback) {
if (paths.length === 0) {
process.nextTick(function() {
callback(null, false);
Expand All @@ -60,7 +66,12 @@ var anyNewer = exports.anyNewer = function(paths, time, override, callback) {
if (err) {
return callback(err);
}
if (stats.mtime > time) {

var pathTime = stats.mtime.getTime();
var comparisonTime = time.getTime();
var difference = pathTime - comparisonTime;

if (difference > tolerance) {
return callback(null, true);
} else {
override(paths[complete], time, function(include) {
Expand Down Expand Up @@ -89,26 +100,31 @@ var anyNewer = exports.anyNewer = function(paths, time, override, callback) {
* are returned from `grunt.task.normalizeMultiTaskFiles` and have a src
* property (Array.<string>) and an optional dest property (string).
* @param {Date} previous Comparison time.
* @param {number} tolerance Maximum time in milliseconds that the destination
* file is allowed to be newer than the source file to compensate for
* imprecisions in modification times in file systems.
* @param {function(string, Date, function(boolean))} override Override.
* @param {function(Error, Array.<Object>)} callback Callback called with
* modified file config objects. Objects with no more src files are
* filtered from the results.
*/
var filterFilesByTime = exports.filterFilesByTime = function(files, previous,
override, callback) {
tolerance, override, callback) {
async.map(files, function(obj, done) {
if (obj.dest && !(obj.src.length === 1 && obj.dest === obj.src[0])) {
fs.stat(obj.dest, function(err, stats) {
if (err) {
// dest file not yet created, use all src files
return done(null, obj);
}
return anyNewer(obj.src, stats.mtime, override, function(err, any) {
return anyNewer(
obj.src, stats.mtime, tolerance, override, function(err, any) {
done(err, any && obj);
});
});
} else {
filterPathsByTime(obj.src, previous, override, function(err, src) {
filterPathsByTime(
obj.src, previous, tolerance, override, function(err, src) {
if (err) {
return done(err);
}
Expand Down
18 changes: 16 additions & 2 deletions tasks/newer.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ function createTask(grunt) {
var args = Array.prototype.slice.call(arguments, 2).join(':');
var options = this.options({
cache: path.join(__dirname, '..', '.cache'),
override: nullOverride
override: nullOverride,
tolerance: 0 // allowed difference between src and dst in ms
});

// support deprecated timestamps option
Expand All @@ -56,6 +57,18 @@ function createTask(grunt) {
options.cache = options.timestamps;
}

// Sanity check for the tolerance option
if (typeof options.tolerance !== 'number') {
grunt.log.warn('The tolerance value must be a number, ignoring current ' +
'value');
options.tolerance = 0;
}
if (options.tolerance < 0) {
grunt.log.warn('A tolerance value of ' + options.tolerance +
' is invalid');
options.tolerance = 0;
}

var done = this.async();

var originalConfig = grunt.config.get([taskName, targetName]);
Expand Down Expand Up @@ -97,7 +110,8 @@ function createTask(grunt) {
}

var files = grunt.task.normalizeMultiTaskFiles(config, targetName);
util.filterFilesByTime(files, previous, override, function(e, newerFiles) {
util.filterFilesByTime(
files, previous, options.tolerance, override, function(e, newerFiles) {
if (e) {
return done(e);
} else if (newerFiles.length === 0) {
Expand Down
101 changes: 101 additions & 0 deletions test/integration/fixtures/newer-tolerance/gruntfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
var assert = require('assert');
var path = require('path');


/**
* @param {Object} grunt Grunt.
*/
module.exports = function(grunt) {

var log = [];

grunt.initConfig({
newer: {
options: {
cache: path.join(__dirname, '.cache'),
tolerance: 2000
}
},
modified: {
one: {
files: [{
expand: true,
cwd: 'src/',
src: 'one.coffee',
dest: 'dest/',
ext: '.js'
}]
},
all: {
files: [{
expand: true,
cwd: 'src/',
src: '**/*.coffee',
dest: 'dest/',
ext: '.js'
}]
},
none: {
src: []
}
},
log: {
all: {
files: [{
expand: true,
cwd: 'src/',
src: '**/*.coffee',
dest: 'dest/',
ext: '.js'
}],
getLog: function() {
return log;
}
}
},
assert: {
that: {
getLog: function() {
return log;
}
}
}
});

grunt.loadTasks('../../../tasks');
grunt.loadTasks('../../../test/integration/tasks');

grunt.registerTask('assert-reconfigured', function() {
var config = grunt.config.get(['log', 'all']);
assert.deepEqual(Object.keys(config).sort(), ['files', 'getLog']);
var files = config.files;
assert.equal(files.length, 1);
assert.deepEqual(Object.keys(files[0]).sort(),
['cwd', 'dest', 'expand', 'ext', 'src']);
assert.equal(files[0].src, '**/*.coffee');
});

grunt.registerTask('default', function() {

grunt.task.run([
// run the log task with newer, expect all files
'newer:log',
'assert:that:modified:all',

// HFS+ filesystem mtime resolution
'wait:1001',

// modify one file
'modified:one',

// run assert task again, expect no files
'newer:log',
'assert:that:modified:none',

// check that log:all task has been reconfigured with original config
'assert-reconfigured'
]);

});

};
3 changes: 3 additions & 0 deletions test/integration/fixtures/newer-tolerance/src/one.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
coffee =
is: 'good'
hot: true
3 changes: 3 additions & 0 deletions test/integration/fixtures/newer-tolerance/src/two.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
semicolons = true
coffee = true
semicolons = false if coffee
23 changes: 23 additions & 0 deletions test/integration/newer-tolerance.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
var path = require('path');

var helper = require('../helper');

var name = 'newer-tolerance';
var gruntfile = path.join(name, 'gruntfile.js');

describe(name, function() {
var fixture;

it('runs the default task (see ' + gruntfile + ')', function(done) {
this.timeout(6000);
helper.buildFixture(name, function(error, dir) {
fixture = dir;
done(error);
});
});

after(function(done) {
helper.afterFixture(fixture, done);
});

});
Loading

0 comments on commit 77723b5

Please sign in to comment.