diff --git a/.flowconfig b/.flowconfig index c805bae..e91fbc2 100644 --- a/.flowconfig +++ b/.flowconfig @@ -1,10 +1,13 @@ [ignore] .*/invalidPackageJson/* .*/test/* +.*/build/* +.*/node_modules/.*/spec/* [include] [libs] +interfaces/ [options] suppress_comment=.*@noflow.* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a5509e9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - "iojs" +after_script: cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js \ No newline at end of file diff --git a/README.md b/README.md index 30fc458..63cb607 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,43 @@ library is for. npm i simple-recursive-watch --save ``` -### Example usage +### API Documentation + +To get better acquainted with the available tools feel free to skim through the auto-generated +[API Docs](https://rawgit.com/thealjey/simple-recursive-watch/master/docs/index.html). + +### Exposes 1 class +`DirectoryWatcher` - recursively watches for file changes in a directory + +``` +interface DirectoryWatcher { + constructor(dir: string, type: RegExp, ...exclude: Array); + start(); + stop(); + static watch(dir: string, extension: string, callback: Function, ...exclude: Array): DirectoryWatcher; +} ``` -import watch from 'simple-recursive-watch'; -watch('lib', 'js', function () { - console.log('something changed'); +### Arguments + +1. `dir` - a full system path to a directory +2. `type` - a regular expression to match files +3. `exclude` - files/directories to exclude (not full paths, just file/directory names) +4. `extension` - a file extension to watch for +5. `callback` - a callback function to execute whenever a file system change occurs + +### Example usage + +```javascript +import {DirectoryWatcher} from 'simple-recursive-watch'; +import {join} from 'path'; + +var libDir = join(__dirname, 'lib'); + +DirectoryWatcher.watch(libDir, 'js', function () { + // some JavaScript file, not named "ignoreMe.js" and not residing + // in a "__tests__" directory, was changed in the "lib" directory }, '__tests__', 'ignoreMe.js'); ``` diff --git a/bin/build.js b/bin/build.js new file mode 100644 index 0000000..a364f59 --- /dev/null +++ b/bin/build.js @@ -0,0 +1,22 @@ +/* @flow */ + +import {join} from 'path'; +import {JS, NativeProcess} from 'webcompiler'; + +var rootDir = join(__dirname, '..'), + buildDir = join(rootDir, 'build'), + libDir = join(rootDir, 'lib'), + docsDir = join(rootDir, 'docs'), + specDir = join(rootDir, 'spec'), + readme = join(rootDir, 'README.md'), + js = new JS(), + jsdoc = new NativeProcess(join(rootDir, 'node_modules', '.bin', 'jsdoc')); + +js.beDir(libDir, buildDir, function () { + jsdoc.run(function (e) { + if (e) { + return console.error(e); + } + console.log('\x1b[32mGenerated API documentation!\x1b[0m'); + }, [buildDir, '-d', docsDir, '-R', readme]); +}, specDir, __filename); diff --git a/bin/compile.js b/bin/compile.js deleted file mode 100644 index 4bf178e..0000000 --- a/bin/compile.js +++ /dev/null @@ -1,33 +0,0 @@ -var path = require('path'), - mkdirp = require('mkdirp'), - compiler = require('webcompiler'), - NativeProcess = require('webcompiler/build/NativeProcess'); - -/*eslint-disable one-var*/ -var rootDir = path.join(__dirname, '..'), - libDir = path.join(rootDir, 'lib'), - buildDir = path.join(rootDir, 'build'), - flow = compiler.flow, - packageJS = compiler.packageJS; - -/*eslint-enable one-var*/ - -mkdirp(buildDir, - compiler.lintJS.bind(null, - [libDir, __filename], - flow.run.bind(flow, - packageJS.bind(null, path.join(libDir, 'DirectoryWatcher.js'), path.join(buildDir, 'DirectoryWatcher.js'), - packageJS.bind(null, path.join(libDir, 'watch.js'), path.join(buildDir, 'watch.js'), - function compiled() { - (new NativeProcess(path.join(rootDir, 'node_modules', '.bin', 'jsdoc'))).run(Function.prototype, [ - buildDir, - '-d', path.join(rootDir, 'docs'), - '-P', path.join(rootDir, 'package.json'), - '-R', path.join(rootDir, 'README.md') - ]); - } - ) - ) - ) - ) -); diff --git a/build/DirectoryWatcher.js b/build/DirectoryWatcher.js index 8a39cb4..b035450 100644 --- a/build/DirectoryWatcher.js +++ b/build/DirectoryWatcher.js @@ -1,195 +1,351 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { + value: true +}); +var _bind = Function.prototype.bind; -'use strict'; +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); -var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default']; +var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; -var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default']; +function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } } -exports.__esModule = true; +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } var _fs = require('fs'); -var _fs2 = _interopRequireDefault(_fs); +var _path = require('path'); + +var _events = require('events'); /** - * A simple `fs.watch` wrapper class + * `fs.watch` wrapper class (extends EventEmitter) with the reliable recursive watching capabilities * * @class - * @param {string} dir - A full system path to a directory - * @param {RegExp} type - A regular expression to match files - * @param {Array} exclude - An array of file and directory names to exclude - * @param {Function} callback - A callback function + * @param {string} dir - a full system path to a directory + * @param {RegExp} type - a regular expression to match files + * @param {...string} exclude - files/directories to exclude (not full paths, just file/directory names) + * @fires DirectoryWatcher#change + * @example + * import {DirectoryWatcher} from 'simple-recursive-watch'; + * import {join} from 'path'; + * + * var libDir = join(__dirname, 'lib'), + * watcher = new DirectoryWatcher(libDir, /\.js$/, 'ignoreMe.js'); */ -var DirectoryWatcher = (function () { - - /** - * Instantiates and starts the watcher - * - * @param {string} dir - A full system path to a directory - * @param {RegExp} type - A regular expression to match files - * @param {Array} exclude - An array of file and directory names to exclude - * @param {Function} callback - A callback function - */ - - function DirectoryWatcher(dir, type, exclude, callback) { - var _this = this; +var DirectoryWatcher = (function (_EventEmitter) { + _inherits(DirectoryWatcher, _EventEmitter); + function DirectoryWatcher(dir, type) { _classCallCheck(this, DirectoryWatcher); - var files = []; + _get(Object.getPrototypeOf(DirectoryWatcher.prototype), 'constructor', this).call(this); /** - * A full system path to a directory + * a full system path to a directory * * @memberof DirectoryWatcher + * @protected * @instance * @type {string} */ this.dir = dir; - _fs2['default'].readdir(dir, function (e, contents) { - if (e) { - return console.error(e); + + /** + * a regular expression to match files + * + * @memberof DirectoryWatcher + * @protected + * @instance + * @type {RegExp} + */ + this.type = type; + + for (var _len = arguments.length, exclude = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { + exclude[_key - 2] = arguments[_key]; + } + + /** + * files/directories to exclude + * + * @memberof DirectoryWatcher + * @protected + * @instance + * @type {Array} + */ + this.exclude = exclude; + + /** + * a collection of known files in this directory + * + * @memberof DirectoryWatcher + * @private + * @instance + * @type {Array} + */ + this.files = []; + + /** + * A collection of child watchers + * + * @memberof DirectoryWatcher + * @private + * @instance + * @type {Array} + */ + this.children = []; + } + + _createClass(DirectoryWatcher, [{ + key: 'indexOf', + + /** + * Searches for a child watcher with a specific directory name. + * + * @memberof DirectoryWatcher + * @private + * @instance + * @method indexOf + * @param {string} dir - a directory name to search for + * @return {number} an index of a child watcher assigned to a directory, or -1 if not found + */ + value: function indexOf(dir) { + var i = this.children.length; + + while (i--) { + if (dir === this.children[i].dir) { + return i; + } } + return -1; + } + }, { + key: 'handleEvent', - /** - * A collection of child watchers - * - * @memberof DirectoryWatcher - * @private - * @instance - * @type {Array} - */ - _this.children = contents.filter( + /** + * Handles the "rename" system event for a file + * + * @memberof DirectoryWatcher + * @private + * @instance + * @method handleEvent + * @param {string} file - a base name of a file/directory that triggered the event + * @param {string} location - a full system path to "file" + */ + value: function handleEvent(file, location) { + var _this = this; - /** - * Executed for each entry in the directory contents - * - * @param {string} file - A file or directory name - * @return {boolean} if false the entry will be filtered out - */ - function loopContents(file) { - if (-1 !== exclude.indexOf(file)) { - return false; + (0, _fs.stat)(location, function (e, stats) { + var i; + + if (e) { + i = _this.files.indexOf(file); + if (-1 !== i) { + _this.files.splice(i, 1); + _this.emit('change'); + return; + } + i = _this.indexOf(location); + if (-1 !== i) { + _this.children[i].stop(); + _this.children.splice(i, 1); + _this.emit('change'); + } + return; } - if (_fs2['default'].statSync('' + dir + '/' + file).isDirectory()) { - return true; + if (stats.isDirectory()) { + i = _this.indexOf(location); + if (-1 === i) { + _this.children.push(_this.createChild(location)); + return; + } + _this.children[i].stop(); + _this.children.splice(i, 1); + } else { + if (!_this.type.test(file)) { + return; + } + i = _this.files.indexOf(file); + if (-1 === i) { + _this.files.push(file); + } else { + _this.files.splice(i, 1); + } } - if (type.test(file)) { - files.push(file); + _this.emit('change'); + }); + } + }, { + key: 'createChild', + + /** + * Creates, starts and returns a new child DirectoryWatcher + * + * @memberof DirectoryWatcher + * @private + * @instance + * @method createChild + * @param {string} location - a full system path to a child directory + * @return {DirectoryWatcher} a newly created child DirectoryWatcher + */ + value: function createChild(location) { + var child = new (_bind.apply(DirectoryWatcher, [null].concat([location, this.type], _toConsumableArray(this.exclude))))(); + + child.start(); + child.on('change', this.emit.bind(this, 'change')); + return child; + } + }, { + key: 'createChildren', + + /** + * Scans the directory for the sub-directories and creates a list of child DirectoryWatcher instances + * + * @memberof DirectoryWatcher + * @private + * @instance + * @method createChildren + */ + value: function createChildren() { + var _this2 = this; + + (0, _fs.readdir)(this.dir, function (e, contents) { + if (e) { + return console.error(e); } - return false; - }).map(function (file) { - return new DirectoryWatcher('' + dir + '/' + file, type, exclude, callback); + _this2.children = contents.filter(function (file) { + if (-1 !== _this2.exclude.indexOf(file)) { + return false; + } + if ((0, _fs.statSync)((0, _path.join)(_this2.dir, file)).isDirectory()) { + return true; + } + if (_this2.type.test(file)) { + _this2.files.push(file); + } + return false; + }).map(function (file) { + return _this2.createChild((0, _path.join)(_this2.dir, file)); + }); + _this2.createTask(); }); + } + }, { + key: 'createTask', + + /** + * Creates the `fs.watch` task + * + * @memberof DirectoryWatcher + * @private + * @instance + * @method createTask + */ + value: function createTask() { + var _this3 = this; /** - * The `fs.FSWatcher` task + * a native FSWatcher instance * * @memberof DirectoryWatcher * @private * @instance * @type {FSWatcher} */ - _this.task = _fs2['default'].watch(dir, - - /** - * Executed whenever a change is detected in the contents of the "dir" - * - * @param {string} event - either 'rename' or 'change' - * @param {string} file - the name of the file which triggered the event - */ - function (event, file) { - var path = '' + dir + '/' + file, - i; - - if (-1 !== exclude.indexOf(file)) { + this.task = (0, _fs.watch)(this.dir, function (event, file) { + if (-1 !== _this3.exclude.indexOf(file)) { return; } if ('rename' === event) { - _fs2['default'].stat(path, function (statErr, stats) { - if (statErr) { - i = files.indexOf(file); - if (-1 !== i) { - files.splice(i, 1); - callback(); - return; - } - i = _this.indexOf(file); - if (-1 !== i) { - _this.children[i].stop(); - _this.children.splice(i, 1); - callback(); - } - return; - } - if (stats.isDirectory()) { - i = _this.indexOf(file); - if (-1 === i) { - _this.children.push(new DirectoryWatcher(path, type, exclude, callback)); - return; - } - _this.children[i].stop(); - _this.children.splice(i, 1); - } else { - if (!type.test(file)) { - return; - } - i = files.indexOf(file); - if (-1 === i) { - files.push(file); - } else { - files.splice(i, 1); - } - } - callback(); - }); - } else if (type.test(file)) { - callback(); + _this3.handleEvent(file, (0, _path.join)(_this3.dir, file)); + } else if (_this3.type.test(file)) { + _this3.emit('change'); } }); - }); - } + } + }, { + key: 'start', - /** - * Searches for a child watcher with a specific directory name. - * - * @memberof DirectoryWatcher - * @instance - * @method indexOf - * @param {string} dir - A directory name to search for - * @return {number} an index of a child watcher assigned to a directory, or -1 if not found - */ - - DirectoryWatcher.prototype.indexOf = function indexOf(dir) { - var i = this.children.length; - - while (i--) { - if (dir === this.children[i].dir) { - return i; + /** + * Starts the watcher + * + * @memberof DirectoryWatcher + * @instance + * @method start + * @example + * watcher.on('change', function () { + * // some JavaScript file, not named "ignoreMe.js", was changed in the "lib" directory + * }); + * watcher.start(); + */ + value: function start() { + if (this.task) { + console.error('already started'); + } else { + this.createChildren(); } } - return -1; - }; - - /** - * Stops the watcher - * - * @memberof DirectoryWatcher - * @instance - * @method stop - */ - - DirectoryWatcher.prototype.stop = function stop() { - this.task.close(); - this.children.forEach(function loopChildren(child) { - child.stop(); - }); - this.children = []; - }; + }, { + key: 'stop', + + /** + * Stops the watcher + * + * @memberof DirectoryWatcher + * @instance + * @method stop + * @example + * watcher.stop(); + */ + value: function stop() { + if (this.task) { + this.task.close(); + this.children.forEach(function (child) { + child.stop(); + }); + this.children = []; + } else { + console.error('not running'); + } + } + }], [{ + key: 'watch', + + /** + * A convenience shortcut method for starting a watcher + * + * @memberof DirectoryWatcher + * @static + * @method watch + * @param {string} dir - a full system path to a directory + * @param {string} extension - a file extension to watch for + * @param {Function} callback - a callback function to execute whenever a file system change occurs + * @param {...string} exclude - files/directories to exclude (not full paths, just file/directory names) + * @return {DirectoryWatcher} a watcher instance + * @example + * var watcher = DirectoryWatcher.watch(libDir, 'js', function () { + * // some JavaScript file, not named "ignoreMe.js", was changed in the "lib" directory + * }, 'ignoreMe.js'); + */ + value: function watch(dir, extension, callback) { + for (var _len2 = arguments.length, exclude = Array(_len2 > 3 ? _len2 - 3 : 0), _key2 = 3; _key2 < _len2; _key2++) { + exclude[_key2 - 3] = arguments[_key2]; + } + + var watcher = new (_bind.apply(DirectoryWatcher, [null].concat([dir, new RegExp('\\.' + extension + '$')], exclude)))(); + + watcher.start(); + watcher.on('change', callback); + return watcher; + } + }]); return DirectoryWatcher; -})(); +})(_events.EventEmitter); -exports.DirectoryWatcher = DirectoryWatcher; \ No newline at end of file +exports['default'] = DirectoryWatcher; +module.exports = exports['default']; \ No newline at end of file diff --git a/build/index.js b/build/index.js new file mode 100644 index 0000000..dadaea9 --- /dev/null +++ b/build/index.js @@ -0,0 +1,13 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _DirectoryWatcher = require('./DirectoryWatcher'); + +var _DirectoryWatcher2 = _interopRequireDefault(_DirectoryWatcher); + +exports.DirectoryWatcher = _DirectoryWatcher2['default']; \ No newline at end of file diff --git a/build/watch.js b/build/watch.js deleted file mode 100644 index 7466455..0000000 --- a/build/watch.js +++ /dev/null @@ -1,48 +0,0 @@ - -/** @module watch */ - -'use strict'; - -var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default']; - -exports.__esModule = true; - -var _fs = require('fs'); - -var _fs2 = _interopRequireDefault(_fs); - -var _DirectoryWatcher = require('./DirectoryWatcher'); - -var _lodashFunctionDebounce = require('lodash/function/debounce'); - -var _lodashFunctionDebounce2 = _interopRequireDefault(_lodashFunctionDebounce); - -/** - * A convenience shortcut method for starting a watcher - * - * @memberOf module:watch - * @param {string} dir - A directory name to watch for changes in - * @param {string} type - A file extension to watch for - * @param {Function} callback - A callback function to execute whenever a file system change occurs - * @param {...Array} exclude - The rest of the arguments, if any, - the file as well as directory names to - * exclude - */ - -exports['default'] = function (dir, type, callback) { - for (var _len = arguments.length, exclude = Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) { - exclude[_key - 3] = arguments[_key]; - } - - _fs2['default'].realpath(dir, function getDirectoryRealPath(e, directory) { - if (e) { - return console.error(e); - } - - /*eslint-disable no-new*/ - new _DirectoryWatcher.DirectoryWatcher(directory, new RegExp('.' + type + '$'), exclude, (0, _lodashFunctionDebounce2['default'])(callback, 150)); - - /*eslint-enable no-new*/ - }); -}; - -module.exports = exports['default']; \ No newline at end of file diff --git a/conf/jsdoc.json b/conf/jsdoc.json deleted file mode 100644 index cea4266..0000000 --- a/conf/jsdoc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "opts": { - "destination": "docs", - "package": "package.json", - "readme": "README.md" - } -} diff --git a/docs/DirectoryWatcher.html b/docs/DirectoryWatcher.html new file mode 100644 index 0000000..166dc72 --- /dev/null +++ b/docs/DirectoryWatcher.html @@ -0,0 +1,747 @@ + + + + + JSDoc: Class: DirectoryWatcher + + + + + + + + + + +
+ +

Class: DirectoryWatcher

+ + + + + + +
+ +
+ +

+ DirectoryWatcher +

+ + +
+ +
+
+ + + + + +

new DirectoryWatcher(dir, type, …exclude)

+ + + + + +
+ `fs.watch` wrapper class (extends EventEmitter) with the reliable recursive watching capabilities +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
dir + + +string + + + + + + + + + + a full system path to a directory
type + + +RegExp + + + + + + + + + + a regular expression to match files
exclude + + +string + + + + + + + + + + <repeatable>
+ +
files/directories to exclude (not full paths, just file/directory names)
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + +
Fires:
+
    +
  • DirectoryWatcher#event:change
  • +
+ + + + + + + + + + + +
Example
+ +
import {DirectoryWatcher} from 'simple-recursive-watch';
+import {join} from 'path';
+
+var libDir = join(__dirname, 'lib'),
+    watcher = new DirectoryWatcher(libDir, /\.js$/, 'ignoreMe.js');
+ + + + +
+ + + + + + + + + + + + + + +

Methods

+ + + + + + +

(static) watch(dir, extension, callback, …exclude) → {DirectoryWatcher}

+ + + + + +
+ A convenience shortcut method for starting a watcher +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
dir + + +string + + + + + + + + + + a full system path to a directory
extension + + +string + + + + + + + + + + a file extension to watch for
callback + + +function + + + + + + + + + + a callback function to execute whenever a file system change occurs
exclude + + +string + + + + + + + + + + <repeatable>
+ +
files/directories to exclude (not full paths, just file/directory names)
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ a watcher instance +
+ + + +
+
+ Type +
+
+ +DirectoryWatcher + + +
+
+ + + + +
Example
+ +
var watcher = DirectoryWatcher.watch(libDir, 'js', function () {
+  // some JavaScript file, not named "ignoreMe.js", was changed in the "lib" directory
+}, 'ignoreMe.js');
+ + + + + + + + +

start()

+ + + + + +
+ Starts the watcher +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Example
+ +
watcher.on('change', function () {
+  // some JavaScript file, not named "ignoreMe.js", was changed in the "lib" directory
+});
+watcher.start();
+ + + + + + + + +

stop()

+ + + + + +
+ Stops the watcher +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Example
+ +
watcher.stop();
+ + + + + + + + + +
+ +
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.3.2 on Wed Jul 15 2015 11:01:47 GMT+0300 (EEST) +
+ + + + + \ No newline at end of file diff --git a/docs/DirectoryWatcher.js.html b/docs/DirectoryWatcher.js.html new file mode 100644 index 0000000..806ec54 --- /dev/null +++ b/docs/DirectoryWatcher.js.html @@ -0,0 +1,401 @@ + + + + + JSDoc: Source: DirectoryWatcher.js + + + + + + + + + + +
+ +

Source: DirectoryWatcher.js

+ + + + + + +
+
+
'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+  value: true
+});
+var _bind = Function.prototype.bind;
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }
+
+var _fs = require('fs');
+
+var _path = require('path');
+
+var _events = require('events');
+
+/**
+ * `fs.watch` wrapper class (extends EventEmitter) with the reliable recursive watching capabilities
+ *
+ * @class
+ * @param {string}    dir     - a full system path to a directory
+ * @param {RegExp}    type    - a regular expression to match files
+ * @param {...string} exclude - files/directories to exclude (not full paths, just file/directory names)
+ * @fires DirectoryWatcher#change
+ * @example
+ * import {DirectoryWatcher} from 'simple-recursive-watch';
+ * import {join} from 'path';
+ *
+ * var libDir = join(__dirname, 'lib'),
+ *     watcher = new DirectoryWatcher(libDir, /\.js$/, 'ignoreMe.js');
+ */
+
+var DirectoryWatcher = (function (_EventEmitter) {
+  _inherits(DirectoryWatcher, _EventEmitter);
+
+  function DirectoryWatcher(dir, type) {
+    _classCallCheck(this, DirectoryWatcher);
+
+    _get(Object.getPrototypeOf(DirectoryWatcher.prototype), 'constructor', this).call(this);
+
+    /**
+     * a full system path to a directory
+     *
+     * @memberof DirectoryWatcher
+     * @protected
+     * @instance
+     * @type {string}
+     */
+    this.dir = dir;
+
+    /**
+     * a regular expression to match files
+     *
+     * @memberof DirectoryWatcher
+     * @protected
+     * @instance
+     * @type {RegExp}
+     */
+    this.type = type;
+
+    for (var _len = arguments.length, exclude = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
+      exclude[_key - 2] = arguments[_key];
+    }
+
+    /**
+     * files/directories to exclude
+     *
+     * @memberof DirectoryWatcher
+     * @protected
+     * @instance
+     * @type {Array<string>}
+     */
+    this.exclude = exclude;
+
+    /**
+     * a collection of known files in this directory
+     *
+     * @memberof DirectoryWatcher
+     * @private
+     * @instance
+     * @type {Array<string>}
+     */
+    this.files = [];
+
+    /**
+     * A collection of child watchers
+     *
+     * @memberof DirectoryWatcher
+     * @private
+     * @instance
+     * @type {Array<DirectoryWatcher>}
+     */
+    this.children = [];
+  }
+
+  _createClass(DirectoryWatcher, [{
+    key: 'indexOf',
+
+    /**
+     * Searches for a child watcher with a specific directory name.
+     *
+     * @memberof DirectoryWatcher
+     * @private
+     * @instance
+     * @method indexOf
+     * @param  {string} dir - a directory name to search for
+     * @return {number} an index of a child watcher assigned to a directory, or -1 if not found
+     */
+    value: function indexOf(dir) {
+      var i = this.children.length;
+
+      while (i--) {
+        if (dir === this.children[i].dir) {
+          return i;
+        }
+      }
+      return -1;
+    }
+  }, {
+    key: 'handleEvent',
+
+    /**
+     * Handles the "rename" system event for a file
+     *
+     * @memberof DirectoryWatcher
+     * @private
+     * @instance
+     * @method handleEvent
+     * @param {string} file     - a base name of a file/directory that triggered the event
+     * @param {string} location - a full system path to "file"
+     */
+    value: function handleEvent(file, location) {
+      var _this = this;
+
+      (0, _fs.stat)(location, function (e, stats) {
+        var i;
+
+        if (e) {
+          i = _this.files.indexOf(file);
+          if (-1 !== i) {
+            _this.files.splice(i, 1);
+            _this.emit('change');
+            return;
+          }
+          i = _this.indexOf(location);
+          if (-1 !== i) {
+            _this.children[i].stop();
+            _this.children.splice(i, 1);
+            _this.emit('change');
+          }
+          return;
+        }
+        if (stats.isDirectory()) {
+          i = _this.indexOf(location);
+          if (-1 === i) {
+            _this.children.push(_this.createChild(location));
+            return;
+          }
+          _this.children[i].stop();
+          _this.children.splice(i, 1);
+        } else {
+          if (!_this.type.test(file)) {
+            return;
+          }
+          i = _this.files.indexOf(file);
+          if (-1 === i) {
+            _this.files.push(file);
+          } else {
+            _this.files.splice(i, 1);
+          }
+        }
+        _this.emit('change');
+      });
+    }
+  }, {
+    key: 'createChild',
+
+    /**
+     * Creates, starts and returns a new child DirectoryWatcher
+     *
+     * @memberof DirectoryWatcher
+     * @private
+     * @instance
+     * @method createChild
+     * @param  {string} location - a full system path to a child directory
+     * @return {DirectoryWatcher}  a newly created child DirectoryWatcher
+     */
+    value: function createChild(location) {
+      var child = new (_bind.apply(DirectoryWatcher, [null].concat([location, this.type], _toConsumableArray(this.exclude))))();
+
+      child.start();
+      child.on('change', this.emit.bind(this, 'change'));
+      return child;
+    }
+  }, {
+    key: 'createChildren',
+
+    /**
+     * Scans the directory for the sub-directories and creates a list of child DirectoryWatcher instances
+     *
+     * @memberof DirectoryWatcher
+     * @private
+     * @instance
+     * @method createChildren
+     */
+    value: function createChildren() {
+      var _this2 = this;
+
+      (0, _fs.readdir)(this.dir, function (e, contents) {
+        if (e) {
+          return console.error(e);
+        }
+        _this2.children = contents.filter(function (file) {
+          if (-1 !== _this2.exclude.indexOf(file)) {
+            return false;
+          }
+          if ((0, _fs.statSync)((0, _path.join)(_this2.dir, file)).isDirectory()) {
+            return true;
+          }
+          if (_this2.type.test(file)) {
+            _this2.files.push(file);
+          }
+          return false;
+        }).map(function (file) {
+          return _this2.createChild((0, _path.join)(_this2.dir, file));
+        });
+        _this2.createTask();
+      });
+    }
+  }, {
+    key: 'createTask',
+
+    /**
+     * Creates the `fs.watch` task
+     *
+     * @memberof DirectoryWatcher
+     * @private
+     * @instance
+     * @method createTask
+     */
+    value: function createTask() {
+      var _this3 = this;
+
+      /**
+       * a native FSWatcher instance
+       *
+       * @memberof DirectoryWatcher
+       * @private
+       * @instance
+       * @type {FSWatcher}
+       */
+      this.task = (0, _fs.watch)(this.dir, function (event, file) {
+        if (-1 !== _this3.exclude.indexOf(file)) {
+          return;
+        }
+        if ('rename' === event) {
+          _this3.handleEvent(file, (0, _path.join)(_this3.dir, file));
+        } else if (_this3.type.test(file)) {
+          _this3.emit('change');
+        }
+      });
+    }
+  }, {
+    key: 'start',
+
+    /**
+     * Starts the watcher
+     *
+     * @memberof DirectoryWatcher
+     * @instance
+     * @method start
+     * @example
+     * watcher.on('change', function () {
+     *   // some JavaScript file, not named "ignoreMe.js", was changed in the "lib" directory
+     * });
+     * watcher.start();
+     */
+    value: function start() {
+      if (this.task) {
+        console.error('already started');
+      } else {
+        this.createChildren();
+      }
+    }
+  }, {
+    key: 'stop',
+
+    /**
+     * Stops the watcher
+     *
+     * @memberof DirectoryWatcher
+     * @instance
+     * @method stop
+     * @example
+     * watcher.stop();
+     */
+    value: function stop() {
+      if (this.task) {
+        this.task.close();
+        this.children.forEach(function (child) {
+          child.stop();
+        });
+        this.children = [];
+      } else {
+        console.error('not running');
+      }
+    }
+  }], [{
+    key: 'watch',
+
+    /**
+     * A convenience shortcut method for starting a watcher
+     *
+     * @memberof DirectoryWatcher
+     * @static
+     * @method watch
+     * @param {string}    dir       - a full system path to a directory
+     * @param {string}    extension - a file extension to watch for
+     * @param {Function}  callback  - a callback function to execute whenever a file system change occurs
+     * @param {...string} exclude   - files/directories to exclude (not full paths, just file/directory names)
+     * @return {DirectoryWatcher} a watcher instance
+     * @example
+     * var watcher = DirectoryWatcher.watch(libDir, 'js', function () {
+     *   // some JavaScript file, not named "ignoreMe.js", was changed in the "lib" directory
+     * }, 'ignoreMe.js');
+     */
+    value: function watch(dir, extension, callback) {
+      for (var _len2 = arguments.length, exclude = Array(_len2 > 3 ? _len2 - 3 : 0), _key2 = 3; _key2 < _len2; _key2++) {
+        exclude[_key2 - 3] = arguments[_key2];
+      }
+
+      var watcher = new (_bind.apply(DirectoryWatcher, [null].concat([dir, new RegExp('\\.' + extension + '$')], exclude)))();
+
+      watcher.start();
+      watcher.on('change', callback);
+      return watcher;
+    }
+  }]);
+
+  return DirectoryWatcher;
+})(_events.EventEmitter);
+
+exports['default'] = DirectoryWatcher;
+module.exports = exports['default'];
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.3.2 on Wed Jul 15 2015 11:01:46 GMT+0300 (EEST) +
+ + + + + diff --git a/docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Bold-webfont.eot b/docs/fonts/OpenSans-Bold-webfont.eot similarity index 100% rename from docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Bold-webfont.eot rename to docs/fonts/OpenSans-Bold-webfont.eot diff --git a/docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Bold-webfont.svg b/docs/fonts/OpenSans-Bold-webfont.svg similarity index 100% rename from docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Bold-webfont.svg rename to docs/fonts/OpenSans-Bold-webfont.svg diff --git a/docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Bold-webfont.woff b/docs/fonts/OpenSans-Bold-webfont.woff similarity index 100% rename from docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Bold-webfont.woff rename to docs/fonts/OpenSans-Bold-webfont.woff diff --git a/docs/simple-recursive-watch/1.2.3/fonts/OpenSans-BoldItalic-webfont.eot b/docs/fonts/OpenSans-BoldItalic-webfont.eot similarity index 100% rename from docs/simple-recursive-watch/1.2.3/fonts/OpenSans-BoldItalic-webfont.eot rename to docs/fonts/OpenSans-BoldItalic-webfont.eot diff --git a/docs/simple-recursive-watch/1.2.3/fonts/OpenSans-BoldItalic-webfont.svg b/docs/fonts/OpenSans-BoldItalic-webfont.svg similarity index 100% rename from docs/simple-recursive-watch/1.2.3/fonts/OpenSans-BoldItalic-webfont.svg rename to docs/fonts/OpenSans-BoldItalic-webfont.svg diff --git a/docs/simple-recursive-watch/1.2.3/fonts/OpenSans-BoldItalic-webfont.woff b/docs/fonts/OpenSans-BoldItalic-webfont.woff similarity index 100% rename from docs/simple-recursive-watch/1.2.3/fonts/OpenSans-BoldItalic-webfont.woff rename to docs/fonts/OpenSans-BoldItalic-webfont.woff diff --git a/docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Italic-webfont.eot b/docs/fonts/OpenSans-Italic-webfont.eot similarity index 100% rename from docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Italic-webfont.eot rename to docs/fonts/OpenSans-Italic-webfont.eot diff --git a/docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Italic-webfont.svg b/docs/fonts/OpenSans-Italic-webfont.svg similarity index 100% rename from docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Italic-webfont.svg rename to docs/fonts/OpenSans-Italic-webfont.svg diff --git a/docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Italic-webfont.woff b/docs/fonts/OpenSans-Italic-webfont.woff similarity index 100% rename from docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Italic-webfont.woff rename to docs/fonts/OpenSans-Italic-webfont.woff diff --git a/docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Light-webfont.eot b/docs/fonts/OpenSans-Light-webfont.eot similarity index 100% rename from docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Light-webfont.eot rename to docs/fonts/OpenSans-Light-webfont.eot diff --git a/docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Light-webfont.svg b/docs/fonts/OpenSans-Light-webfont.svg similarity index 100% rename from docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Light-webfont.svg rename to docs/fonts/OpenSans-Light-webfont.svg diff --git a/docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Light-webfont.woff b/docs/fonts/OpenSans-Light-webfont.woff similarity index 100% rename from docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Light-webfont.woff rename to docs/fonts/OpenSans-Light-webfont.woff diff --git a/docs/simple-recursive-watch/1.2.3/fonts/OpenSans-LightItalic-webfont.eot b/docs/fonts/OpenSans-LightItalic-webfont.eot similarity index 100% rename from docs/simple-recursive-watch/1.2.3/fonts/OpenSans-LightItalic-webfont.eot rename to docs/fonts/OpenSans-LightItalic-webfont.eot diff --git a/docs/simple-recursive-watch/1.2.3/fonts/OpenSans-LightItalic-webfont.svg b/docs/fonts/OpenSans-LightItalic-webfont.svg similarity index 100% rename from docs/simple-recursive-watch/1.2.3/fonts/OpenSans-LightItalic-webfont.svg rename to docs/fonts/OpenSans-LightItalic-webfont.svg diff --git a/docs/simple-recursive-watch/1.2.3/fonts/OpenSans-LightItalic-webfont.woff b/docs/fonts/OpenSans-LightItalic-webfont.woff similarity index 100% rename from docs/simple-recursive-watch/1.2.3/fonts/OpenSans-LightItalic-webfont.woff rename to docs/fonts/OpenSans-LightItalic-webfont.woff diff --git a/docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Regular-webfont.eot b/docs/fonts/OpenSans-Regular-webfont.eot similarity index 100% rename from docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Regular-webfont.eot rename to docs/fonts/OpenSans-Regular-webfont.eot diff --git a/docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Regular-webfont.svg b/docs/fonts/OpenSans-Regular-webfont.svg similarity index 100% rename from docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Regular-webfont.svg rename to docs/fonts/OpenSans-Regular-webfont.svg diff --git a/docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Regular-webfont.woff b/docs/fonts/OpenSans-Regular-webfont.woff similarity index 100% rename from docs/simple-recursive-watch/1.2.3/fonts/OpenSans-Regular-webfont.woff rename to docs/fonts/OpenSans-Regular-webfont.woff diff --git a/docs/simple-recursive-watch/1.2.3/index.html b/docs/index.html similarity index 59% rename from docs/simple-recursive-watch/1.2.3/index.html rename to docs/index.html index 4b7691c..5445ea1 100644 --- a/docs/simple-recursive-watch/1.2.3/index.html +++ b/docs/index.html @@ -26,7 +26,7 @@

Home

-

simple-recursive-watch 1.2.3

+

@@ -55,10 +55,29 @@

simple-recursive-watch 1.2.3

intensive, because it uses native operating system events to do it's job.

Unfortunately, though it cannot be reliably used to spy on a whole tree of folders recursively. Which is what this library is for.

-

Installation

npm i simple-recursive-watch --save

Example usage

import watch from 'simple-recursive-watch';
-
-watch('lib', 'js', function () {
-  console.log('something changed');
+

Installation

npm i simple-recursive-watch --save

API Documentation

To get better acquainted with the available tools feel free to skim through the auto-generated +API Docs.

+

Exposes 1 class

DirectoryWatcher - recursively watches for file changes in a directory

+
interface DirectoryWatcher {
+  constructor(dir: string, type: RegExp, ...exclude: Array<string>);
+  start();
+  stop();
+  static watch(dir: string, extension: string, callback: Function, ...exclude: Array<string>): DirectoryWatcher;
+}

Arguments

    +
  1. dir - a full system path to a directory
  2. +
  3. type - a regular expression to match files
  4. +
  5. exclude - files/directories to exclude (not full paths, just file/directory names)
  6. +
  7. extension - a file extension to watch for
  8. +
  9. callback - a callback function to execute whenever a file system change occurs
  10. +
+

Example usage

import {DirectoryWatcher} from 'simple-recursive-watch';
+import {join} from 'path';
+
+var libDir = join(__dirname, 'lib');
+
+DirectoryWatcher.watch(libDir, 'js', function () {
+  // some JavaScript file, not named "ignoreMe.js" and not residing
+  // in a "__tests__" directory, was changed in the "lib" directory
 }, '__tests__', 'ignoreMe.js');

Explanation

The provided callback is invoked, immediately and only once, when somewhere in the src directory a ".js" file is created, edited, renamed, moved around or deleted, excluding those residing in a "__tests__" directory or having a name "ignoreMe.js".

@@ -73,13 +92,13 @@

Installation

npm i simple-recursiv
 
 
 
 
 
- Documentation generated by JSDoc 3.3.2 on Wed Jun 17 2015 18:30:38 GMT+0300 (EEST) + Documentation generated by JSDoc 3.3.2 on Wed Jul 15 2015 11:01:46 GMT+0300 (EEST)
diff --git a/docs/simple-recursive-watch/1.2.3/scripts/linenumber.js b/docs/scripts/linenumber.js similarity index 100% rename from docs/simple-recursive-watch/1.2.3/scripts/linenumber.js rename to docs/scripts/linenumber.js diff --git a/docs/simple-recursive-watch/1.2.3/scripts/prettify/Apache-License-2.0.txt b/docs/scripts/prettify/Apache-License-2.0.txt similarity index 100% rename from docs/simple-recursive-watch/1.2.3/scripts/prettify/Apache-License-2.0.txt rename to docs/scripts/prettify/Apache-License-2.0.txt diff --git a/docs/simple-recursive-watch/1.2.3/scripts/prettify/lang-css.js b/docs/scripts/prettify/lang-css.js similarity index 100% rename from docs/simple-recursive-watch/1.2.3/scripts/prettify/lang-css.js rename to docs/scripts/prettify/lang-css.js diff --git a/docs/simple-recursive-watch/1.2.3/scripts/prettify/prettify.js b/docs/scripts/prettify/prettify.js similarity index 100% rename from docs/simple-recursive-watch/1.2.3/scripts/prettify/prettify.js rename to docs/scripts/prettify/prettify.js diff --git a/docs/simple-recursive-watch/1.2.3/DirectoryWatcher.html b/docs/simple-recursive-watch/1.2.3/DirectoryWatcher.html deleted file mode 100644 index f90a943..0000000 --- a/docs/simple-recursive-watch/1.2.3/DirectoryWatcher.html +++ /dev/null @@ -1,521 +0,0 @@ - - - - - JSDoc: Class: DirectoryWatcher - - - - - - - - - - -
- -

Class: DirectoryWatcher

- - - - - - -
- -
- -

- DirectoryWatcher -

- - -
- -
-
- - - - - -

new DirectoryWatcher(dir, type, exclude, callback)

- - - - - -
- A simple `fs.watch` wrapper class -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
dir - - -string - - - - A full system path to a directory
type - - -RegExp - - - - A regular expression to match files
exclude - - -Array.<string> - - - - An array of file and directory names to exclude
callback - - -function - - - - A callback function
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -

Methods

- - - - - - -

indexOf(dir) → {number}

- - - - - -
- Searches for a child watcher with a specific directory name. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
dir - - -string - - - - A directory name to search for
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - -
Returns:
- - -
- an index of a child watcher assigned to a directory, or -1 if not found -
- - - -
-
- Type -
-
- -number - - -
-
- - - - - - - - - - -

stop()

- - - - - -
- Stops the watcher -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.3.2 on Wed Jun 17 2015 18:30:38 GMT+0300 (EEST) -
- - - - - \ No newline at end of file diff --git a/docs/simple-recursive-watch/1.2.3/DirectoryWatcher.js.html b/docs/simple-recursive-watch/1.2.3/DirectoryWatcher.js.html deleted file mode 100644 index 5bc1f7c..0000000 --- a/docs/simple-recursive-watch/1.2.3/DirectoryWatcher.js.html +++ /dev/null @@ -1,245 +0,0 @@ - - - - - JSDoc: Source: DirectoryWatcher.js - - - - - - - - - - -
- -

Source: DirectoryWatcher.js

- - - - - - -
-
-

-
-'use strict';
-
-var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
-
-var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
-
-exports.__esModule = true;
-
-var _fs = require('fs');
-
-var _fs2 = _interopRequireDefault(_fs);
-
-/**
- * A simple `fs.watch` wrapper class
- *
- * @class
- * @param {string}        dir      - A full system path to a directory
- * @param {RegExp}        type     - A regular expression to match files
- * @param {Array<string>} exclude  - An array of file and directory names to exclude
- * @param {Function}      callback - A callback function
- */
-
-var DirectoryWatcher = (function () {
-
-  /**
-   * Instantiates and starts the watcher
-   *
-   * @param {string}        dir      - A full system path to a directory
-   * @param {RegExp}        type     - A regular expression to match files
-   * @param {Array<string>} exclude  - An array of file and directory names to exclude
-   * @param {Function}      callback - A callback function
-   */
-
-  function DirectoryWatcher(dir, type, exclude, callback) {
-    var _this = this;
-
-    _classCallCheck(this, DirectoryWatcher);
-
-    var files = [];
-
-    /**
-     * A full system path to a directory
-     *
-     * @memberof DirectoryWatcher
-     * @instance
-     * @type {string}
-     */
-    this.dir = dir;
-    _fs2['default'].readdir(dir, function (e, contents) {
-      if (e) {
-        return console.error(e);
-      }
-
-      /**
-       * A collection of child watchers
-       *
-       * @memberof DirectoryWatcher
-       * @private
-       * @instance
-       * @type {Array<DirectoryWatcher>}
-       */
-      _this.children = contents.filter(
-
-      /**
-       * Executed for each entry in the directory contents
-       *
-       * @param  {string} file - A file or directory name
-       * @return {boolean} if false the entry will be filtered out
-       */
-      function loopContents(file) {
-        if (-1 !== exclude.indexOf(file)) {
-          return false;
-        }
-        if (_fs2['default'].statSync('' + dir + '/' + file).isDirectory()) {
-          return true;
-        }
-        if (type.test(file)) {
-          files.push(file);
-        }
-        return false;
-      }).map(function (file) {
-        return new DirectoryWatcher('' + dir + '/' + file, type, exclude, callback);
-      });
-
-      /**
-       * The `fs.FSWatcher` task
-       *
-       * @memberof DirectoryWatcher
-       * @private
-       * @instance
-       * @type {FSWatcher}
-       */
-      _this.task = _fs2['default'].watch(dir,
-
-      /**
-       * Executed whenever a change is detected in the contents of the "dir"
-       *
-       * @param {string} event - either 'rename' or 'change'
-       * @param {string} file  - the name of the file which triggered the event
-       */
-      function (event, file) {
-        var path = '' + dir + '/' + file,
-            i;
-
-        if (-1 !== exclude.indexOf(file)) {
-          return;
-        }
-        if ('rename' === event) {
-          _fs2['default'].stat(path, function (statErr, stats) {
-            if (statErr) {
-              i = files.indexOf(file);
-              if (-1 !== i) {
-                files.splice(i, 1);
-                callback();
-                return;
-              }
-              i = _this.indexOf(file);
-              if (-1 !== i) {
-                _this.children[i].stop();
-                _this.children.splice(i, 1);
-                callback();
-              }
-              return;
-            }
-            if (stats.isDirectory()) {
-              i = _this.indexOf(file);
-              if (-1 === i) {
-                _this.children.push(new DirectoryWatcher(path, type, exclude, callback));
-                return;
-              }
-              _this.children[i].stop();
-              _this.children.splice(i, 1);
-            } else {
-              if (!type.test(file)) {
-                return;
-              }
-              i = files.indexOf(file);
-              if (-1 === i) {
-                files.push(file);
-              } else {
-                files.splice(i, 1);
-              }
-            }
-            callback();
-          });
-        } else if (type.test(file)) {
-          callback();
-        }
-      });
-    });
-  }
-
-  /**
-   * Searches for a child watcher with a specific directory name.
-   *
-   * @memberof DirectoryWatcher
-   * @instance
-   * @method indexOf
-   * @param  {string} dir - A directory name to search for
-   * @return {number} an index of a child watcher assigned to a directory, or -1 if not found
-   */
-
-  DirectoryWatcher.prototype.indexOf = function indexOf(dir) {
-    var i = this.children.length;
-
-    while (i--) {
-      if (dir === this.children[i].dir) {
-        return i;
-      }
-    }
-    return -1;
-  };
-
-  /**
-   * Stops the watcher
-   *
-   * @memberof DirectoryWatcher
-   * @instance
-   * @method stop
-   */
-
-  DirectoryWatcher.prototype.stop = function stop() {
-    this.task.close();
-    this.children.forEach(function loopChildren(child) {
-      child.stop();
-    });
-    this.children = [];
-  };
-
-  return DirectoryWatcher;
-})();
-
-exports.DirectoryWatcher = DirectoryWatcher;
-
-
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.3.2 on Wed Jun 17 2015 18:30:38 GMT+0300 (EEST) -
- - - - - diff --git a/docs/simple-recursive-watch/1.2.3/module-watch.html b/docs/simple-recursive-watch/1.2.3/module-watch.html deleted file mode 100644 index 49d29b7..0000000 --- a/docs/simple-recursive-watch/1.2.3/module-watch.html +++ /dev/null @@ -1,324 +0,0 @@ - - - - - JSDoc: Module: watch - - - - - - - - - - -
- -

Module: watch

- - - - - - -
- -
- - - -
- -
-
- - - - - -
- - - - - - - - - - - - - - -

Methods

- - - - - - -

(static) exports['default'](dir, type, callback, …exclude)

- - - - - -
- A convenience shortcut method for starting a watcher -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
dir - - -string - - - - - - - - - - A directory name to watch for changes in
type - - -string - - - - - - - - - - A file extension to watch for
callback - - -function - - - - - - - - - - A callback function to execute whenever a file system change occurs
exclude - - -Array.<string> - - - - - - - - - - <repeatable>
- -
The rest of the arguments, if any, - the file as well as directory names to - exclude
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.3.2 on Wed Jun 17 2015 18:30:38 GMT+0300 (EEST) -
- - - - - \ No newline at end of file diff --git a/docs/simple-recursive-watch/1.2.3/watch.js.html b/docs/simple-recursive-watch/1.2.3/watch.js.html deleted file mode 100644 index f86b14b..0000000 --- a/docs/simple-recursive-watch/1.2.3/watch.js.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - JSDoc: Source: watch.js - - - - - - - - - - -
- -

Source: watch.js

- - - - - - -
-
-

-/** @module watch */
-
-'use strict';
-
-var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
-
-exports.__esModule = true;
-
-var _fs = require('fs');
-
-var _fs2 = _interopRequireDefault(_fs);
-
-var _DirectoryWatcher = require('./DirectoryWatcher');
-
-var _lodashFunctionDebounce = require('lodash/function/debounce');
-
-var _lodashFunctionDebounce2 = _interopRequireDefault(_lodashFunctionDebounce);
-
-/**
- * A convenience shortcut method for starting a watcher
- *
- * @memberOf module:watch
- * @param {string}           dir      - A directory name to watch for changes in
- * @param {string}           type     - A file extension to watch for
- * @param {Function}         callback - A callback function to execute whenever a file system change occurs
- * @param {...Array<string>} exclude  - The rest of the arguments, if any, - the file as well as directory names to
- *                                      exclude
- */
-
-exports['default'] = function (dir, type, callback) {
-  for (var _len = arguments.length, exclude = Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) {
-    exclude[_key - 3] = arguments[_key];
-  }
-
-  _fs2['default'].realpath(dir, function getDirectoryRealPath(e, directory) {
-    if (e) {
-      return console.error(e);
-    }
-
-    /*eslint-disable no-new*/
-    new _DirectoryWatcher.DirectoryWatcher(directory, new RegExp('.' + type + '$'), exclude, (0, _lodashFunctionDebounce2['default'])(callback, 150));
-
-    /*eslint-enable no-new*/
-  });
-};
-
-module.exports = exports['default'];
-
-
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.3.2 on Wed Jun 17 2015 18:30:38 GMT+0300 (EEST) -
- - - - - diff --git a/docs/simple-recursive-watch/1.2.3/styles/jsdoc-default.css b/docs/styles/jsdoc-default.css similarity index 100% rename from docs/simple-recursive-watch/1.2.3/styles/jsdoc-default.css rename to docs/styles/jsdoc-default.css diff --git a/docs/simple-recursive-watch/1.2.3/styles/prettify-jsdoc.css b/docs/styles/prettify-jsdoc.css similarity index 100% rename from docs/simple-recursive-watch/1.2.3/styles/prettify-jsdoc.css rename to docs/styles/prettify-jsdoc.css diff --git a/docs/simple-recursive-watch/1.2.3/styles/prettify-tomorrow.css b/docs/styles/prettify-tomorrow.css similarity index 100% rename from docs/simple-recursive-watch/1.2.3/styles/prettify-tomorrow.css rename to docs/styles/prettify-tomorrow.css diff --git a/interfaces/jasmine.js b/interfaces/jasmine.js new file mode 100644 index 0000000..955fd3d --- /dev/null +++ b/interfaces/jasmine.js @@ -0,0 +1,7 @@ +declare function describe(title: string, callback: Function): any; +declare function beforeEach(callback: Function): any; +declare function afterEach(callback: Function): any; +declare function it(title: string, callback: Function): any; +declare function expect(obj: any): any; +declare function spyOn(obj: any, fn: string): any; +declare var jasmine: Object; diff --git a/lib/DirectoryWatcher.js b/lib/DirectoryWatcher.js index d45f53f..6b483e4 100644 --- a/lib/DirectoryWatcher.js +++ b/lib/DirectoryWatcher.js @@ -1,151 +1,100 @@ /* @flow */ -import fs from 'fs'; +import {stat, statSync, readdir, watch} from 'fs'; +import {join} from 'path'; +import {EventEmitter} from 'events'; /** - * A simple `fs.watch` wrapper class + * `fs.watch` wrapper class (extends EventEmitter) with the reliable recursive watching capabilities * * @class - * @param {string} dir - A full system path to a directory - * @param {RegExp} type - A regular expression to match files - * @param {Array} exclude - An array of file and directory names to exclude - * @param {Function} callback - A callback function + * @param {string} dir - a full system path to a directory + * @param {RegExp} type - a regular expression to match files + * @param {...string} exclude - files/directories to exclude (not full paths, just file/directory names) + * @fires DirectoryWatcher#change + * @example + * import {DirectoryWatcher} from 'simple-recursive-watch'; + * import {join} from 'path'; + * + * var libDir = join(__dirname, 'lib'), + * watcher = new DirectoryWatcher(libDir, /\.js$/, 'ignoreMe.js'); */ -export class DirectoryWatcher { +export default class DirectoryWatcher extends EventEmitter { dir: string; + type: RegExp; + + exclude: Array; + + files: Array; + children: Array; task: any; - /** - * Instantiates and starts the watcher - * - * @param {string} dir - A full system path to a directory - * @param {RegExp} type - A regular expression to match files - * @param {Array} exclude - An array of file and directory names to exclude - * @param {Function} callback - A callback function - */ - constructor(dir: string, type: RegExp, exclude: Array, callback: Function) { - var files = []; + constructor(dir: string, type: RegExp, ...exclude: Array) { + super(); /** - * A full system path to a directory + * a full system path to a directory * * @memberof DirectoryWatcher + * @protected * @instance * @type {string} */ this.dir = dir; - fs.readdir(dir, (e, contents) => { - if (e) { - return console.error(e); - } - /** - * A collection of child watchers - * - * @memberof DirectoryWatcher - * @private - * @instance - * @type {Array} - */ - this.children = contents.filter( - - /** - * Executed for each entry in the directory contents - * - * @param {string} file - A file or directory name - * @return {boolean} if false the entry will be filtered out - */ - function loopContents(file) { - if (-1 !== exclude.indexOf(file)) { - return false; - } - if (fs.statSync(`${dir}/${file}`).isDirectory()) { - return true; - } - if (type.test(file)) { - files.push(file); - } - return false; - }).map(file => new DirectoryWatcher(`${dir}/${file}`, type, exclude, callback)); - - /** - * The `fs.FSWatcher` task - * - * @memberof DirectoryWatcher - * @private - * @instance - * @type {FSWatcher} - */ - this.task = fs.watch(dir, - - /** - * Executed whenever a change is detected in the contents of the "dir" - * - * @param {string} event - either 'rename' or 'change' - * @param {string} file - the name of the file which triggered the event - */ - (event, file) => { - var path = `${dir}/${file}`, i; - - if (-1 !== exclude.indexOf(file)) { - return; - } - if ('rename' === event) { - fs.stat(path, (statErr, stats) => { - if (statErr) { - i = files.indexOf(file); - if (-1 !== i) { - files.splice(i, 1); - callback(); - return; - } - i = this.indexOf(file); - if (-1 !== i) { - this.children[i].stop(); - this.children.splice(i, 1); - callback(); - } - return; - } - if (stats.isDirectory()) { - i = this.indexOf(file); - if (-1 === i) { - this.children.push(new DirectoryWatcher(path, type, exclude, callback)); - return; - } - this.children[i].stop(); - this.children.splice(i, 1); - } else { - if (!type.test(file)) { - return; - } - i = files.indexOf(file); - if (-1 === i) { - files.push(file); - } else { - files.splice(i, 1); - } - } - callback(); - }); - } else if (type.test(file)) { - callback(); - } - }); - }); + /** + * a regular expression to match files + * + * @memberof DirectoryWatcher + * @protected + * @instance + * @type {RegExp} + */ + this.type = type; + + /** + * files/directories to exclude + * + * @memberof DirectoryWatcher + * @protected + * @instance + * @type {Array} + */ + this.exclude = exclude; + + /** + * a collection of known files in this directory + * + * @memberof DirectoryWatcher + * @private + * @instance + * @type {Array} + */ + this.files = []; + + /** + * A collection of child watchers + * + * @memberof DirectoryWatcher + * @private + * @instance + * @type {Array} + */ + this.children = []; } /** * Searches for a child watcher with a specific directory name. * * @memberof DirectoryWatcher + * @private * @instance * @method indexOf - * @param {string} dir - A directory name to search for + * @param {string} dir - a directory name to search for * @return {number} an index of a child watcher assigned to a directory, or -1 if not found */ indexOf(dir: string): number { @@ -159,19 +108,198 @@ export class DirectoryWatcher { return -1; } + /** + * Handles the "rename" system event for a file + * + * @memberof DirectoryWatcher + * @private + * @instance + * @method handleEvent + * @param {string} file - a base name of a file/directory that triggered the event + * @param {string} location - a full system path to "file" + */ + handleEvent(file: string, location: string) { + stat(location, (e, stats) => { + var i; + + if (e) { + i = this.files.indexOf(file); + if (-1 !== i) { + this.files.splice(i, 1); + this.emit('change'); + return; + } + i = this.indexOf(location); + if (-1 !== i) { + this.children[i].stop(); + this.children.splice(i, 1); + this.emit('change'); + } + return; + } + if (stats.isDirectory()) { + i = this.indexOf(location); + if (-1 === i) { + this.children.push(this.createChild(location)); + return; + } + this.children[i].stop(); + this.children.splice(i, 1); + } else { + if (!this.type.test(file)) { + return; + } + i = this.files.indexOf(file); + if (-1 === i) { + this.files.push(file); + } else { + this.files.splice(i, 1); + } + } + this.emit('change'); + }); + } + + /** + * Creates, starts and returns a new child DirectoryWatcher + * + * @memberof DirectoryWatcher + * @private + * @instance + * @method createChild + * @param {string} location - a full system path to a child directory + * @return {DirectoryWatcher} a newly created child DirectoryWatcher + */ + createChild(location: string): DirectoryWatcher { + var child = new DirectoryWatcher(location, this.type, ...this.exclude); + + child.start(); + child.on('change', this.emit.bind(this, 'change')); + return child; + } + + /** + * Scans the directory for the sub-directories and creates a list of child DirectoryWatcher instances + * + * @memberof DirectoryWatcher + * @private + * @instance + * @method createChildren + */ + createChildren() { + readdir(this.dir, (e, contents) => { + if (e) { + return console.error(e); + } + this.children = contents.filter(file => { + if (-1 !== this.exclude.indexOf(file)) { + return false; + } + if (statSync(join(this.dir, file)).isDirectory()) { + return true; + } + if (this.type.test(file)) { + this.files.push(file); + } + return false; + }).map(file => this.createChild(join(this.dir, file))); + this.createTask(); + }); + } + + /** + * Creates the `fs.watch` task + * + * @memberof DirectoryWatcher + * @private + * @instance + * @method createTask + */ + createTask() { + + /** + * a native FSWatcher instance + * + * @memberof DirectoryWatcher + * @private + * @instance + * @type {FSWatcher} + */ + this.task = watch(this.dir, (event, file) => { + if (-1 !== this.exclude.indexOf(file)) { + return; + } + if ('rename' === event) { + this.handleEvent(file, join(this.dir, file)); + } else if (this.type.test(file)) { + this.emit('change'); + } + }); + } + + /** + * Starts the watcher + * + * @memberof DirectoryWatcher + * @instance + * @method start + * @example + * watcher.on('change', function () { + * // some JavaScript file, not named "ignoreMe.js", was changed in the "lib" directory + * }); + * watcher.start(); + */ + start() { + if (this.task) { + console.error('already started'); + } else { + this.createChildren(); + } + } + /** * Stops the watcher * * @memberof DirectoryWatcher * @instance * @method stop + * @example + * watcher.stop(); */ stop() { - this.task.close(); - this.children.forEach(function loopChildren(child) { - child.stop(); - }); - this.children = []; + if (this.task) { + this.task.close(); + this.children.forEach(function (child) { + child.stop(); + }); + this.children = []; + } else { + console.error('not running'); + } + } + + /** + * A convenience shortcut method for starting a watcher + * + * @memberof DirectoryWatcher + * @static + * @method watch + * @param {string} dir - a full system path to a directory + * @param {string} extension - a file extension to watch for + * @param {Function} callback - a callback function to execute whenever a file system change occurs + * @param {...string} exclude - files/directories to exclude (not full paths, just file/directory names) + * @return {DirectoryWatcher} a watcher instance + * @example + * var watcher = DirectoryWatcher.watch(libDir, 'js', function () { + * // some JavaScript file, not named "ignoreMe.js", was changed in the "lib" directory + * }, 'ignoreMe.js'); + */ + static watch(dir: string, extension: string, callback: Function, ...exclude: Array): DirectoryWatcher { + var watcher = new DirectoryWatcher(dir, new RegExp('\\.' + extension + '$'), ...exclude); + + watcher.start(); + watcher.on('change', callback); + return watcher; } } diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..49b4cc1 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,5 @@ +/* @flow */ + +import DirectoryWatcher from './DirectoryWatcher'; + +export {DirectoryWatcher}; diff --git a/lib/watch.js b/lib/watch.js deleted file mode 100644 index aeff959..0000000 --- a/lib/watch.js +++ /dev/null @@ -1,29 +0,0 @@ -/* @flow */ -/** @module watch */ - -import fs from 'fs'; -import {DirectoryWatcher} from './DirectoryWatcher'; -import debounce from 'lodash/function/debounce'; - -/** - * A convenience shortcut method for starting a watcher - * - * @memberOf module:watch - * @param {string} dir - A directory name to watch for changes in - * @param {string} type - A file extension to watch for - * @param {Function} callback - A callback function to execute whenever a file system change occurs - * @param {...Array} exclude - The rest of the arguments, if any, - the file as well as directory names to - * exclude - */ -export default function (dir: string, type: string, callback: Function, ...exclude: Array) { - fs.realpath(dir, function getDirectoryRealPath(e, directory) { - if (e) { - return console.error(e); - } - - /*eslint-disable no-new*/ - new DirectoryWatcher(directory, new RegExp('\.' + type + '$'), exclude, debounce(callback, 150)); - - /*eslint-enable no-new*/ - }); -} diff --git a/package.json b/package.json index d8d9d7f..cbd9380 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "simple-recursive-watch", - "version": "1.2.3", + "version": "2.0.0", "description": "A simple cross platform recursive directory watcher for NodeJS, based on \"fs.watch\", but not relying on its \"recursive\" option.", - "main": "build/watch.js", + "main": "build/index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "compile": "node bin/compile" + "test": "babel-istanbul cover --root lib --print detail --include-all-sources --include-babel-polyfill jasmine", + "build": "npm run test && babel-node bin/build" }, "repository": { "type": "git", @@ -24,12 +24,15 @@ }, "homepage": "https://github.com/thealjey/simple-recursive-watch", "dependencies": { - "babel-runtime": "^5.5.8", - "lodash": "^3.9.3" + "babel-runtime": "^5.6.20" }, "devDependencies": { + "babel": "^5.6.14", + "babel-istanbul": "^0.2.10", + "coveralls": "^2.11.2", + "jasmine-es6": "0.0.14", "jsdoc": "^3.3.2", - "mkdirp": "^0.5.1", - "webcompiler": "^0.3.5" + "lodash": "^3.10.0", + "webcompiler": "^1.0.1" } } diff --git a/spec/DirectoryWatcherSpec.js b/spec/DirectoryWatcherSpec.js new file mode 100644 index 0000000..72c1ba1 --- /dev/null +++ b/spec/DirectoryWatcherSpec.js @@ -0,0 +1,586 @@ +/* @flow */ +/*global describe, it, expect, beforeEach, spyOn, jasmine*/ + +import fs from 'fs'; +import DirectoryWatcher from '../lib/DirectoryWatcher'; +import {EventEmitter} from 'events'; + +describe('DirectoryWatcher', function () { + + /* @noflow */ + var watcher, spy; + + beforeEach(function () { + spy = jasmine.createSpy('spy'); + spyOn(console, 'error'); + watcher = new DirectoryWatcher('/path/to/a/directory', /\.js$/, 'ignoreMe.js', 'ignoreMeToo'); + watcher.on('change', spy); + }); + + it('extends EventEmitter', function () { + expect(watcher instanceof EventEmitter).toBeTruthy(); + }); + + it('configures itself', function () { + expect(watcher.dir).toBe('/path/to/a/directory'); + expect(watcher.type).toEqual(/\.js$/); + expect(watcher.exclude).toEqual(['ignoreMe.js', 'ignoreMeToo']); + expect(watcher.files).toEqual([]); + expect(watcher.children).toEqual([]); + }); + + describe('stop not started', function () { + + beforeEach(function () { + watcher.stop(); + }); + + it('does not throw', function () { + expect(watcher.stop.bind(watcher)).not.toThrow(); + }); + + it('prints the error on screen', function () { + expect(console.error).toHaveBeenCalledWith('not running'); + }); + + }); + + describe('stop started', function () { + + /* @noflow */ + var child1, child2; + + beforeEach(function () { + child1 = new DirectoryWatcher('/path/to/the/first/directory', /\.js$/, 'ignoreMe.js', 'ignoreMeToo'); + child2 = new DirectoryWatcher('/path/to/the/second/directory', /\.js$/); + spyOn(child1, 'stop'); + spyOn(child2, 'stop'); + watcher.task = {close: jasmine.createSpy('close')}; + watcher.children = [child1, child2]; + watcher.stop(); + }); + + it('closes the task', function () { + expect(watcher.task.close).toHaveBeenCalled(); + }); + + it('loops through children', function () { + expect(child1.stop).toHaveBeenCalled(); + expect(child2.stop).toHaveBeenCalled(); + expect(watcher.children).toEqual([]); + }); + + it('does not print any errors', function () { + expect(console.error).not.toHaveBeenCalled(); + }); + + }); + + describe('start not started', function () { + + beforeEach(function () { + spyOn(watcher, 'createChildren'); + watcher.start(); + }); + + it('does not print any errors', function () { + expect(console.error).not.toHaveBeenCalled(); + }); + + it('calls createChildren', function () { + expect(watcher.createChildren).toHaveBeenCalled(); + }); + + }); + + describe('start already started', function () { + + beforeEach(function () { + watcher.task = true; + watcher.start(); + }); + + it('prints the error on screen', function () { + expect(console.error).toHaveBeenCalledWith('already started'); + }); + + }); + + describe('createChildren', function () { + + beforeEach(function () { + spyOn(watcher, 'createChild').and.callFake(function (file) { + return `DirectoryWatcher for: ${file}`; + }); + spyOn(watcher, 'createChildren').and.callThrough(); + }); + + describe('readdir error', function () { + var contents = []; + + beforeEach(function () { + spyOn(fs, 'readdir').and.callFake(function (dir, callback) { + callback('something bad happened', contents); + }); + spyOn(contents, 'filter'); + watcher.createChildren(); + }); + + it('calls fs.readdir', function () { + expect(fs.readdir).toHaveBeenCalledWith('/path/to/a/directory', jasmine.any(Function)); + }); + + it('prints the error on screen', function () { + expect(console.error).toHaveBeenCalledWith('something bad happened'); + }); + + it('does not call contents.filter', function () { + expect(contents.filter).not.toHaveBeenCalled(); + }); + + }); + + describe('readdir success', function () { + var contents = ['app.js', 'app.css', 'childRir1', 'childRir2', 'ignoreMe.js', 'script.js', 'ignoreMeToo']; + + beforeEach(function () { + spyOn(fs, 'readdir').and.callFake(function (dir, callback) { + callback(null, contents); + }); + spyOn(fs, 'statSync').and.callFake(function (file) { + return { + isDirectory() { + return '/path/to/a/directory/childRir1' === file || '/path/to/a/directory/childRir2' === file || + '/path/to/a/directory/ignoreMeToo' === file; + } + }; + }); + spyOn(watcher, 'createTask'); + watcher.createChildren(); + }); + + it('creates children', function () { + expect(watcher.createChild).toHaveBeenCalledWith('/path/to/a/directory/childRir1'); + expect(watcher.createChild).toHaveBeenCalledWith('/path/to/a/directory/childRir2'); + expect(watcher.children).toEqual(['DirectoryWatcher for: /path/to/a/directory/childRir1', + 'DirectoryWatcher for: /path/to/a/directory/childRir2']); + }); + + it('calls createTask', function () { + expect(watcher.createTask).toHaveBeenCalled(); + }); + + }); + + }); + + describe('createChild', function () { + + /* @noflow */ + var child; + + beforeEach(function () { + spyOn(fs, 'readdir'); + spyOn(DirectoryWatcher.prototype, 'start'); + child = watcher.createChild('/path/to/a/directory/childRir'); + }); + + it('creates a new DirectoryWatcher', function () { + expect(child).toEqual(jasmine.any(DirectoryWatcher)); + }); + + it('configures', function () { + expect(child.dir).toBe('/path/to/a/directory/childRir'); + expect(child.type).toEqual(/\.js$/); + expect(child.exclude).toEqual(['ignoreMe.js', 'ignoreMeToo']); + expect(child.files).toEqual([]); + expect(child.children).toEqual([]); + }); + + it('calls start', function () { + expect(DirectoryWatcher.prototype.start).toHaveBeenCalled(); + }); + + it('it bubbles up the change event', function () { + child.emit('change'); + expect(spy).toHaveBeenCalled(); + }); + + }); + + describe('createTask', function () { + + beforeEach(function () { + spyOn(watcher, 'handleEvent'); + }); + + describe('exclude', function () { + + beforeEach(function () { + spyOn(fs, 'watch').and.callFake(function (file, callback) { + callback('change', 'ignoreMeToo'); + return 'native watcher instance'; + }); + watcher.createTask(); + }); + + it('assigns the task', function () { + expect(watcher.task).toBe('native watcher instance'); + }); + + it('calls fs.watch', function () { + expect(fs.watch).toHaveBeenCalledWith('/path/to/a/directory', jasmine.any(Function)); + }); + + it('does not trigger a change event', function () { + expect(spy).not.toHaveBeenCalled(); + }); + + it('does not call handleEvent', function () { + expect(watcher.handleEvent).not.toHaveBeenCalled(); + }); + + }); + + describe('target file', function () { + + beforeEach(function () { + spyOn(fs, 'watch').and.callFake(function (file, callback) { + callback('change', 'script.js'); + }); + watcher.createTask(); + }); + + it('triggers a change event', function () { + expect(spy).toHaveBeenCalled(); + }); + + it('does not call handleEvent', function () { + expect(watcher.handleEvent).not.toHaveBeenCalled(); + }); + + }); + + describe('other changes', function () { + + beforeEach(function () { + spyOn(fs, 'watch').and.callFake(function (file, callback) { + callback('rename', 'script.js'); + }); + watcher.createTask(); + }); + + it('calls handleEvent', function () { + expect(watcher.handleEvent).toHaveBeenCalled(); + }); + + }); + + describe('change in an unsupported file', function () { + + beforeEach(function () { + spyOn(fs, 'watch').and.callFake(function (file, callback) { + callback('change', 'style.css'); + }); + watcher.createTask(); + }); + + it('does not trigger a change event', function () { + expect(spy).not.toHaveBeenCalled(); + }); + + it('does not call handleEvent', function () { + expect(watcher.handleEvent).not.toHaveBeenCalled(); + }); + + }); + + }); + + describe('handleEvent', function () { + + beforeEach(function () { + spyOn(watcher.files, 'indexOf').and.callThrough(); + spyOn(watcher, 'indexOf').and.callThrough(); + }); + + describe('fs.stat error', function () { + + /* @noflow */ + var stats; + + beforeEach(function () { + stats = {isDirectory: jasmine.createSpy('isDirectory')}; + spyOn(fs, 'stat').and.callFake(function (file, callback) { + callback('something bad happened', stats); + }); + }); + + describe('known file', function () { + + beforeEach(function () { + watcher.files = ['script.js']; + watcher.handleEvent('script.js', '/path/to/a/directory/script.js'); + }); + + it('calls fs.stat', function () { + expect(fs.stat).toHaveBeenCalledWith('/path/to/a/directory/script.js', jasmine.any(Function)); + }); + + it('forgets the file', function () { + expect(watcher.files).toEqual([]); + }); + + it('triggers a change event', function () { + expect(spy).toHaveBeenCalled(); + }); + + it('does not search through the child watchers', function () { + expect(watcher.indexOf).not.toHaveBeenCalled(); + }); + + it('does not call stats.isDirectory', function () { + expect(stats.isDirectory).not.toHaveBeenCalled(); + }); + + }); + + describe('known directory', function () { + + /* @noflow */ + var child; + + beforeEach(function () { + child = new DirectoryWatcher('/path/to/a/directory/someDir', /\.js$/, 'ignoreMe.js', 'ignoreMeToo'); + spyOn(child, 'stop').and.callThrough(); + watcher.children = [child]; + watcher.handleEvent('someDir', '/path/to/a/directory/someDir'); + }); + + it('searches through the child watchers', function () { + expect(watcher.indexOf).toHaveBeenCalledWith('/path/to/a/directory/someDir'); + }); + + it('calls child.stop', function () { + expect(child.stop).toHaveBeenCalled(); + }); + + it('forgets the directory', function () { + expect(watcher.children).toEqual([]); + }); + + it('triggers a change event', function () { + expect(spy).toHaveBeenCalled(); + }); + + it('does not call stats.isDirectory', function () { + expect(stats.isDirectory).not.toHaveBeenCalled(); + }); + + }); + + describe('other errors', function () { + + beforeEach(function () { + watcher.handleEvent('someDir', '/path/to/a/directory/someDir'); + }); + + it('does not trigger a change event', function () { + expect(spy).not.toHaveBeenCalled(); + }); + + it('does not call stats.isDirectory', function () { + expect(stats.isDirectory).not.toHaveBeenCalled(); + }); + + }); + + }); + + describe('fs.stat success', function () { + + beforeEach(function () { + spyOn(watcher.type, 'test').and.callThrough(); + }); + + describe('is directory', function () { + + /* @noflow */ + var stats; + + beforeEach(function () { + stats = {isDirectory: jasmine.createSpy('isDirectory').and.returnValue(true)}; + spyOn(fs, 'stat').and.callFake(function (file, callback) { + callback(null, stats); + }); + }); + + describe('unknown directory', function () { + + beforeEach(function () { + spyOn(watcher, 'createChild').and.returnValue('new child instance'); + watcher.handleEvent('someDir', '/path/to/a/directory/someDir'); + }); + + it('calls stats.isDirectory', function () { + expect(stats.isDirectory).toHaveBeenCalled(); + }); + + it('searches through the child watchers', function () { + expect(watcher.indexOf).toHaveBeenCalledWith('/path/to/a/directory/someDir'); + }); + + it('creates a child', function () { + expect(watcher.createChild).toHaveBeenCalledWith('/path/to/a/directory/someDir'); + expect(watcher.children).toEqual(['new child instance']); + }); + + it('does not trigger a change event', function () { + expect(spy).not.toHaveBeenCalled(); + }); + + }); + + describe('known directory', function () { + + /* @noflow */ + var child; + + beforeEach(function () { + child = new DirectoryWatcher('/path/to/a/directory/someDir', /\.js$/, 'ignoreMe.js', 'ignoreMeToo'); + spyOn(child, 'stop').and.callThrough(); + watcher.children = [child]; + watcher.handleEvent('someDir', '/path/to/a/directory/someDir'); + }); + + it('calls child.stop', function () { + expect(child.stop).toHaveBeenCalled(); + }); + + it('forgets the directory', function () { + expect(watcher.children).toEqual([]); + }); + + it('does not test the type', function () { + expect(watcher.type.test).not.toHaveBeenCalled(); + }); + + it('triggers a change event', function () { + expect(spy).toHaveBeenCalled(); + }); + + }); + + }); + + describe('is not a directory', function () { + var stats; + + beforeEach(function () { + stats = {isDirectory: jasmine.createSpy('isDirectory').and.returnValue(false)}; + spyOn(fs, 'stat').and.callFake(function (file, callback) { + callback(null, stats); + }); + }); + + describe('wrong type', function () { + + beforeEach(function () { + watcher.handleEvent('style.css', '/path/to/a/directory/style.css'); + }); + + it('tests the type', function () { + expect(watcher.type.test).toHaveBeenCalledWith('style.css'); + }); + + it('does not check the known files', function () { + expect(watcher.files.indexOf).not.toHaveBeenCalled(); + }); + + }); + + describe('correct type unknown file', function () { + + beforeEach(function () { + watcher.handleEvent('script.js', '/path/to/a/directory/script.js'); + }); + + it('checks known files', function () { + expect(watcher.files.indexOf).toHaveBeenCalled(); + }); + + it('remembers the file', function () { + expect(watcher.files.length).toBe(1); + }); + + }); + + describe('correct type known file', function () { + + beforeEach(function () { + watcher.files = ['app.js']; + watcher.handleEvent('app.js', '/path/to/a/directory/app.js'); + }); + + it('forgets the file', function () { + expect(watcher.files.length).toBe(0); + }); + + }); + + }); + + }); + + }); + + describe('indexOf', function () { + + beforeEach(function () { + watcher.children = [new DirectoryWatcher('/path/to/a/directory/someDir', /\.js$/, 'ignoreMe.js', 'ignoreMeToo')]; + }); + + it('finds a child', function () { + expect(watcher.indexOf('/path/to/a/directory/someDir')).toBe(0); + }); + + it('returns -1 if nothing is found', function () { + expect(watcher.indexOf('anything')).toBe(-1); + }); + + }); + + describe('watch', function () { + + /* @noflow */ + var child; + + beforeEach(function () { + spyOn(DirectoryWatcher.prototype, 'start'); + child = DirectoryWatcher.watch('/path/to/a/directory', 'js', spy, 'ignoreMe.js', 'ignoreMeToo'); + }); + + it('creates a new DirectoryWatcher', function () { + expect(child).toEqual(jasmine.any(DirectoryWatcher)); + }); + + it('configures', function () { + expect(child.dir).toBe('/path/to/a/directory'); + expect(child.type).toEqual(/\.js$/); + expect(child.exclude).toEqual(['ignoreMe.js', 'ignoreMeToo']); + expect(child.files).toEqual([]); + expect(child.children).toEqual([]); + }); + + it('calls start', function () { + expect(DirectoryWatcher.prototype.start).toHaveBeenCalled(); + }); + + it('it bubbles up the change event', function () { + child.emit('change'); + expect(spy).toHaveBeenCalled(); + }); + + }); + +}); diff --git a/spec/indexSpec.js b/spec/indexSpec.js new file mode 100644 index 0000000..2df617e --- /dev/null +++ b/spec/indexSpec.js @@ -0,0 +1,14 @@ +/* @flow */ +/*global describe, it, expect*/ + +import {DirectoryWatcher as TestDirectoryWatcher} from '../lib'; + +import DirectoryWatcher from '../lib/DirectoryWatcher'; + +describe('index', function () { + + it('re-exports DirectoryWatcher', function () { + expect(TestDirectoryWatcher).toBe(DirectoryWatcher); + }); + +});