ydoc
Version:
基于 Markdown 的静态站点生成工具
185 lines (163 loc) • 4.23 kB
JavaScript
/**
* Module dependencies.
*/
var fs = require('fs')
, debug = require('debug')('koa-liveload')
, path = require('path');
/**
* Utility functions to synchronously test whether the giving path
* is a file or a directory.
*/
var is = (function(ret) {
['file', 'dir'].forEach(function(method) {
ret[method] = function(fpath) {
var suffix = ({file: 'File', dir: 'Directory'})[method];
if (fs.existsSync(fpath)) {
return fs.statSync(fpath)['is' + suffix]();
}
return false;
}
});
return ret;
}({}));
/**
* Get sub-directories in a directory.
*/
var sub = function(parent, cb) {
if (is.dir(parent)) {
fs.readdir(parent, function(err, all) {
all && all.forEach(function(f) {
var sdir = path.join(parent, f)
if (is.dir(sdir)) {
cb.call(null, sdir)
}
});
});
}
};
/**
* A container for memorizing names of files or directories.
*/
var memo = (function(memo) {
return {
push: function(name, type) {
memo[name] = type;
},
has: function(name) {
return memo.hasOwnProperty(name) ? true : false;
},
update: function(name) {
if (!is.file(name) && !is.dir(name)) {
delete memo[name];
}
return true;
}
};
}({}));
/**
* A Container for storing unique and valid filenames.
*/
var fileNameCache = (function(cache) {
return {
push: function(name) {
cache[name] = 1;
return this;
},
each: function() {
var temp = Object.keys(cache).filter(function(name){
if (memo.has(name)) {
memo.update(name);
}
return true;
});
temp.forEach.apply(temp, arguments);
return this;
},
clear: function(){
cache = {};
return this;
}
};
}({}));
/**
* Abstracting the way of avoiding duplicate function call.
*/
var worker = (function() {
var free = true;
return {
busydoing: function(cb) {
if (free) {
free = false;
cb.call();
}
},
free: function() {
free = true;
}
}
}());
/**
* Watch a file or a directory recursively.
*
* @param {String} fpath
* @param {Object} options includes ['html', 'js', 'css'] excludes ['node_modules', 'components']
* @param {Function} cb
*
* watch('fpath', function(file) {
* console.log(file, ' changed');
* });
*/
function watchDir(fpath, opts, cb){
if(typeof(opts) == "function"){
cb = opts;
opts = {};
}
opts.includes = opts.includes || ['js'];
opts.excludes = opts.excludes || ['node_modules'];
opts.ignoreHidden = opts.ignoreHidden || true;
watch(fpath, cb);
var normalizeCall = function(fname, cb) {
debug('changed file: ' + fname);
// Store each name of the modifying or temporary files generated by an editor.
fileNameCache.push(fname);
worker.busydoing(function() {
// A heuristic delay of the write-to-file process.
setTimeout(function() {
// When the write-to-file process is done, send all filtered filenames
// to the callback function and call it.
fileNameCache
.each(function(f) {
// Watch new created directory.
if (!memo.has(f) && is.dir(f)) {
watch(f, cb);
} else if (is.file(f)) {
cb.call(null, f);
}
}).clear();
worker.free();
}, 100);
})
}
function watch(fpath, cb) {
var basename = path.basename(fpath);
if(!is.dir(fpath)) return;
if(opts.ignoreHidden && /^\./.test(basename)) return;
if(opts.excludes && opts.excludes.indexOf(basename) !== -1) return;
debug('watching directory: ' + fpath);
memo.push(fpath, 'dir');
fs.watch(fpath, function(err, fname) {
// Windows "delete" operations do not pass the filename that was deleted
if(!fname) return;
var ext = path.extname(fname).slice(1);
var f = path.join(fpath, fname);
if (is.file(f) && opts.includes.indexOf(ext) === -1) return;
normalizeCall(f, cb);
});
// Recursively watch its sub-directories.
sub(fpath, function(dir) {
watch(dir, cb);
});
}
}
// Expose.
module.exports = watchDir;