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:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+ Attributes
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ 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:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+ Attributes
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ 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();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Classes
+
+
+
+
+
+ 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'];
+
+
+
+
+
+
+
+
+
+ Classes
+
+
+
+
+
+ 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
+dir
- a full system path to a directory
+type
- a regular expression to match files
+exclude
- files/directories to exclude (not full paths, just file/directory names)
+extension
- a file extension to watch for
+callback
- a callback function to execute whenever a file system change occurs
+
+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
- Modules Classes
+ Classes
- 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:
-
-
-
-
-
-
- Name
-
-
- Type
-
-
-
-
-
- Description
-
-
-
-
-
-
-
-
- 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:
-
-
-
-
-
-
- Name
-
-
- Type
-
-
-
-
-
- Description
-
-
-
-
-
-
-
-
- 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:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Modules Classes
-
-
-
-
-
- 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;
-
-
-
-
-
-
-
-
-
- Modules Classes
-
-
-
-
-
- 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:
-
-
-
-
-
-
- Name
-
-
- Type
-
-
- Attributes
-
-
-
-
- Description
-
-
-
-
-
-
-
-
- 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:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Modules Classes
-
-
-
-
-
- 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'];
-
-
-
-
-
-
-
-
-
- Modules Classes
-
-
-
-
-
- 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);
+ });
+
+});