PageRenderTime 53ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/platforms/blackberry10/cordova/node_modules/jasmine-node/node_modules/gaze/lib/gaze.js

https://gitlab.com/adamlwalker/phonegap-nfc-reader
JavaScript | 466 lines | 340 code | 52 blank | 74 comment | 66 complexity | 7e4af1b9a48b1e7a2ac93325e6106c69 MD5 | raw file
  1. /*
  2. * gaze
  3. * https://github.com/shama/gaze
  4. *
  5. * Copyright (c) 2013 Kyle Robinson Young
  6. * Licensed under the MIT license.
  7. */
  8. 'use strict';
  9. // libs
  10. var util = require('util');
  11. var EE = require('events').EventEmitter;
  12. var fs = require('fs');
  13. var path = require('path');
  14. var fileset = require('fileset');
  15. var minimatch = require('minimatch');
  16. // globals
  17. var delay = 10;
  18. // `Gaze` EventEmitter object to return in the callback
  19. function Gaze(patterns, opts, done) {
  20. var _this = this;
  21. EE.call(_this);
  22. // If second arg is the callback
  23. if (typeof opts === 'function') {
  24. done = opts;
  25. opts = {};
  26. }
  27. // Default options
  28. opts = opts || {};
  29. opts.mark = true;
  30. opts.interval = opts.interval || 100;
  31. opts.debounceDelay = opts.debounceDelay || 500;
  32. opts.cwd = opts.cwd || process.cwd();
  33. this.options = opts;
  34. // Default done callback
  35. done = done || function() {};
  36. // Remember our watched dir:files
  37. this._watched = Object.create(null);
  38. // Store watchers
  39. this._watchers = Object.create(null);
  40. // Store patterns
  41. this._patterns = [];
  42. // Cached events for debouncing
  43. this._cached = Object.create(null);
  44. // Set maxListeners
  45. if (this.options.maxListeners) {
  46. this.setMaxListeners(this.options.maxListeners);
  47. Gaze.super_.prototype.setMaxListeners(this.options.maxListeners);
  48. delete this.options.maxListeners;
  49. }
  50. // Initialize the watch on files
  51. if (patterns) {
  52. this.add(patterns, done);
  53. }
  54. return this;
  55. }
  56. util.inherits(Gaze, EE);
  57. // Main entry point. Start watching and call done when setup
  58. module.exports = function gaze(patterns, opts, done) {
  59. return new Gaze(patterns, opts, done);
  60. };
  61. module.exports.Gaze = Gaze;
  62. // Node v0.6 compat
  63. fs.existsSync = fs.existsSync || path.existsSync;
  64. path.sep = path.sep || path.normalize('/');
  65. /**
  66. * Lo-Dash 1.0.1 <http://lodash.com/>
  67. * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
  68. * Based on Underscore.js 1.4.4 <http://underscorejs.org/>
  69. * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
  70. * Available under MIT license <http://lodash.com/license>
  71. */
  72. function unique() { var array = Array.prototype.concat.apply(Array.prototype, arguments); var result = []; for (var i = 0; i < array.length; i++) { if (result.indexOf(array[i]) === -1) { result.push(array[i]); } } return result; }
  73. /**
  74. * Copyright (c) 2010 Caolan McMahon
  75. * Available under MIT license <https://raw.github.com/caolan/async/master/LICENSE>
  76. */
  77. function forEachSeries(arr, iterator, callback) {
  78. if (!arr.length) { return callback(); }
  79. var completed = 0;
  80. var iterate = function() {
  81. iterator(arr[completed], function (err) {
  82. if (err) {
  83. callback(err);
  84. callback = function() {};
  85. } else {
  86. completed += 1;
  87. if (completed === arr.length) {
  88. callback(null);
  89. } else {
  90. iterate();
  91. }
  92. }
  93. });
  94. };
  95. iterate();
  96. }
  97. // other helpers
  98. // Returns boolean whether filepath is dir terminated
  99. function _isDir(dir) {
  100. if (typeof dir !== 'string') { return false; }
  101. return (dir.slice(-(path.sep.length)) === path.sep);
  102. }
  103. // Create a `key:[]` if doesnt exist on `obj` then push or concat the `val`
  104. function _objectPush(obj, key, val) {
  105. if (obj[key] == null) { obj[key] = []; }
  106. if (Array.isArray(val)) { obj[key] = obj[key].concat(val); }
  107. else if (val) { obj[key].push(val); }
  108. return obj[key] = unique(obj[key]);
  109. }
  110. // Ensures the dir is marked with path.sep
  111. function _markDir(dir) {
  112. if (typeof dir === 'string' &&
  113. dir.slice(-(path.sep.length)) !== path.sep &&
  114. dir !== '.') {
  115. dir += path.sep;
  116. }
  117. return dir;
  118. }
  119. // Changes path.sep to unix ones for testing
  120. function _unixifyPathSep(filepath) {
  121. return (process.platform === 'win32') ? String(filepath).replace(/\\/g, '/') : filepath;
  122. }
  123. // Override the emit function to emit `all` events
  124. // and debounce on duplicate events per file
  125. Gaze.prototype.emit = function() {
  126. var _this = this;
  127. var args = arguments;
  128. var e = args[0];
  129. var filepath = args[1];
  130. var timeoutId;
  131. // If not added/deleted/changed/renamed then just emit the event
  132. if (e.slice(-2) !== 'ed') {
  133. Gaze.super_.prototype.emit.apply(_this, args);
  134. return this;
  135. }
  136. // Detect rename event, if added and previous deleted is in the cache
  137. if (e === 'added') {
  138. Object.keys(this._cached).forEach(function(oldFile) {
  139. if (_this._cached[oldFile].indexOf('deleted') !== -1) {
  140. args[0] = e = 'renamed';
  141. [].push.call(args, oldFile);
  142. delete _this._cached[oldFile];
  143. return false;
  144. }
  145. });
  146. }
  147. // If cached doesnt exist, create a delay before running the next
  148. // then emit the event
  149. var cache = this._cached[filepath] || [];
  150. if (cache.indexOf(e) === -1) {
  151. _objectPush(_this._cached, filepath, e);
  152. clearTimeout(timeoutId);
  153. timeoutId = setTimeout(function() {
  154. delete _this._cached[filepath];
  155. }, this.options.debounceDelay);
  156. // Emit the event and `all` event
  157. Gaze.super_.prototype.emit.apply(_this, args);
  158. Gaze.super_.prototype.emit.apply(_this, ['all', e].concat([].slice.call(args, 1)));
  159. }
  160. return this;
  161. };
  162. // Close watchers
  163. Gaze.prototype.close = function(_reset) {
  164. var _this = this;
  165. _reset = _reset === false ? false : true;
  166. Object.keys(_this._watchers).forEach(function(file) {
  167. _this._watchers[file].close();
  168. });
  169. _this._watchers = Object.create(null);
  170. Object.keys(this._watched).forEach(function(dir) {
  171. fs.unwatchFile(dir);
  172. _this._watched[dir].forEach(function(uFile) {
  173. fs.unwatchFile(uFile);
  174. });
  175. });
  176. if (_reset) {
  177. _this._watched = Object.create(null);
  178. setTimeout(function() {
  179. _this.emit('end');
  180. _this.removeAllListeners();
  181. }, delay + 100);
  182. }
  183. return _this;
  184. };
  185. // Add file patterns to be watched
  186. Gaze.prototype.add = function(files, done) {
  187. var _this = this;
  188. if (typeof files === 'string') {
  189. files = [files];
  190. }
  191. this._patterns = unique.apply(null, [this._patterns, files]);
  192. var include = [], exclude = [];
  193. this._patterns.forEach(function(p) {
  194. if (p.slice(0, 1) === '!') {
  195. exclude.push(p.slice(1));
  196. } else {
  197. include.push(p);
  198. }
  199. });
  200. fileset(include, exclude, _this.options, function(err, files) {
  201. if (err) {
  202. _this.emit('error', err);
  203. return done(err);
  204. }
  205. _this._addToWatched(files);
  206. _this.close(false);
  207. _this._initWatched(done);
  208. });
  209. };
  210. // Remove file/dir from `watched`
  211. Gaze.prototype.remove = function(file) {
  212. var _this = this;
  213. if (this._watched[file]) {
  214. // is dir, remove all files
  215. fs.unwatchFile(file);
  216. this._watched[file].forEach(fs.unwatchFile);
  217. delete this._watched[file];
  218. } else {
  219. // is a file, find and remove
  220. Object.keys(this._watched).forEach(function(dir) {
  221. var index = _this._watched[dir].indexOf(file);
  222. if (index !== -1) {
  223. fs.unwatchFile(file);
  224. _this._watched[dir].splice(index, 1);
  225. return false;
  226. }
  227. });
  228. }
  229. if (this._watchers[file]) {
  230. this._watchers[file].close();
  231. }
  232. return this;
  233. };
  234. // Return watched files
  235. Gaze.prototype.watched = function() {
  236. return this._watched;
  237. };
  238. // Returns `watched` files with relative paths to process.cwd()
  239. Gaze.prototype.relative = function(dir, unixify) {
  240. var _this = this;
  241. var relative = Object.create(null);
  242. var relDir, relFile, unixRelDir;
  243. var cwd = this.options.cwd || process.cwd();
  244. if (dir === '') { dir = '.'; }
  245. dir = _markDir(dir);
  246. unixify = unixify || false;
  247. Object.keys(this._watched).forEach(function(dir) {
  248. relDir = path.relative(cwd, dir) + path.sep;
  249. if (relDir === path.sep) { relDir = '.'; }
  250. unixRelDir = unixify ? _unixifyPathSep(relDir) : relDir;
  251. relative[unixRelDir] = _this._watched[dir].map(function(file) {
  252. relFile = path.relative(path.join(cwd, relDir), file);
  253. if (_isDir(file)) {
  254. relFile = _markDir(relFile);
  255. }
  256. if (unixify) {
  257. relFile = _unixifyPathSep(relFile);
  258. }
  259. return relFile;
  260. });
  261. });
  262. if (dir && unixify) {
  263. dir = _unixifyPathSep(dir);
  264. }
  265. return dir ? relative[dir] || [] : relative;
  266. };
  267. // Adds files and dirs to watched
  268. Gaze.prototype._addToWatched = function(files) {
  269. var _this = this;
  270. files.forEach(function(file) {
  271. var filepath = path.resolve(_this.options.cwd, file);
  272. if (file.slice(-1) === '/') { filepath += path.sep; }
  273. _objectPush(_this._watched, path.dirname(filepath) + path.sep, filepath);
  274. });
  275. return this;
  276. };
  277. // Returns true if the file matches this._patterns
  278. Gaze.prototype._isMatch = function(file) {
  279. var include = [], exclude = [];
  280. this._patterns.forEach(function(p) {
  281. if (p.slice(0, 1) === '!') {
  282. exclude.push(p.slice(1));
  283. } else {
  284. include.push(p);
  285. }
  286. });
  287. var matched = false, i = 0;
  288. for (i = 0; i < include.length; i++) {
  289. if (minimatch(file, include[i])) {
  290. matched = true;
  291. break;
  292. }
  293. }
  294. for (i = 0; i < exclude.length; i++) {
  295. if (minimatch(file, exclude[i])) {
  296. matched = false;
  297. break;
  298. }
  299. }
  300. return matched;
  301. };
  302. Gaze.prototype._watchDir = function(dir, done) {
  303. var _this = this;
  304. try {
  305. _this._watchers[dir] = fs.watch(dir, function(event) {
  306. // race condition. Let's give the fs a little time to settle down. so we
  307. // don't fire events on non existent files.
  308. setTimeout(function() {
  309. if (fs.existsSync(dir)) {
  310. done(null, dir);
  311. }
  312. }, delay + 100);
  313. });
  314. } catch (err) {
  315. return this._handleError(err);
  316. }
  317. return this;
  318. };
  319. Gaze.prototype._pollFile = function(file, done) {
  320. var _this = this;
  321. var opts = { persistent: true, interval: _this.options.interval };
  322. try {
  323. fs.watchFile(file, opts, function(curr, prev) {
  324. done(null, file);
  325. });
  326. } catch (err) {
  327. return this._handleError(err);
  328. }
  329. return this;
  330. };
  331. // Initialize the actual watch on `watched` files
  332. Gaze.prototype._initWatched = function(done) {
  333. var _this = this;
  334. var cwd = this.options.cwd || process.cwd();
  335. var curWatched = Object.keys(_this._watched);
  336. forEachSeries(curWatched, function(dir, next) {
  337. var files = _this._watched[dir];
  338. // Triggered when a watched dir has an event
  339. _this._watchDir(dir, function(event, dirpath) {
  340. var relDir = cwd === dir ? '.' : path.relative(cwd, dir);
  341. fs.readdir(dirpath, function(err, current) {
  342. if (err) { return _this.emit('error', err); }
  343. if (!current) { return; }
  344. try {
  345. // append path.sep to directories so they match previous.
  346. current = current.map(function(curPath) {
  347. if (fs.existsSync(path.join(dir, curPath)) && fs.statSync(path.join(dir, curPath)).isDirectory()) {
  348. return curPath + path.sep;
  349. } else {
  350. return curPath;
  351. }
  352. });
  353. } catch (err) {
  354. // race condition-- sometimes the file no longer exists
  355. }
  356. // Get watched files for this dir
  357. var previous = _this.relative(relDir);
  358. // If file was deleted
  359. previous.filter(function(file) {
  360. return current.indexOf(file) < 0;
  361. }).forEach(function(file) {
  362. if (!_isDir(file)) {
  363. var filepath = path.join(dir, file);
  364. _this.remove(filepath);
  365. _this.emit('deleted', filepath);
  366. }
  367. });
  368. // If file was added
  369. current.filter(function(file) {
  370. return previous.indexOf(file) < 0;
  371. }).forEach(function(file) {
  372. // Is it a matching pattern?
  373. var relFile = path.join(relDir, file);
  374. // TODO: this can be optimized more
  375. // we shouldnt need isMatch() and could just use add()
  376. if (_this._isMatch(relFile)) {
  377. // Add to watch then emit event
  378. _this.add(relFile, function() {
  379. _this.emit('added', path.join(dir, file));
  380. });
  381. }
  382. });
  383. });
  384. });
  385. // Watch for change/rename events on files
  386. files.forEach(function(file) {
  387. if (_isDir(file)) { return; }
  388. _this._pollFile(file, function(err, filepath) {
  389. // Only emit changed if the file still exists
  390. // Prevents changed/deleted duplicate events
  391. // TODO: This ignores changed events on folders, maybe support this?
  392. // When a file is added, a folder changed event emits first
  393. if (fs.existsSync(filepath)) {
  394. _this.emit('changed', filepath);
  395. }
  396. });
  397. });
  398. next();
  399. }, function() {
  400. // Return this instance of Gaze
  401. // delay before ready solves a lot of issues
  402. setTimeout(function() {
  403. _this.emit('ready', _this);
  404. done.call(_this, null, _this);
  405. }, delay + 100);
  406. });
  407. };
  408. // If an error, handle it here
  409. Gaze.prototype._handleError = function(err) {
  410. if (err.code === 'EMFILE') {
  411. return this.emit('error', new Error('EMFILE: Too many opened files.'));
  412. }
  413. return this.emit('error', err);
  414. };