UNPKG

ydoc

Version:

基于 Markdown 的静态站点生成工具

185 lines (163 loc) 4.23 kB
/** * 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;