1 var chokidar = require('chokidar')
2 var mm = require('minimatch')
4 var helper = require('./helper')
5 var log = require('./logger').create('watcher')
7 var DIR_SEP = require('path').sep
9 // Get parent folder, that be watched (does not contain any special globbing character)
10 var baseDirFromPattern = function (pattern) {
12 .replace(/[\/\\][^\/\\]*\*.*$/, '') // remove parts with *
13 .replace(/[\/\\][^\/\\]*[\!\+]\(.*$/, '') // remove parts with !(...) and +(...)
14 .replace(/[\/\\][^\/\\]*\)\?.*$/, '') || DIR_SEP // remove parts with (...)?
17 var watchPatterns = function (patterns, watcher) {
18 // filter only unique non url patterns paths
23 patterns.forEach(function (pattern) {
24 path = baseDirFromPattern(pattern)
25 if (!uniqueMap[path]) {
26 uniqueMap[path] = true
27 pathsToWatch.push(path)
31 // watch only common parents, no sub paths
32 pathsToWatch.forEach(function (path) {
33 if (!pathsToWatch.some(function (p) {
34 return p !== path && path.substr(0, p.length + 1) === p + DIR_SEP
37 log.debug('Watching "%s"', path)
42 // Function to test if a path should be ignored by chokidar.
43 var createIgnore = function (patterns, excludes) {
44 return function (path, stat) {
45 if (!stat || stat.isDirectory()) {
49 // Check if the path matches any of the watched patterns.
50 if (!patterns.some(function (pattern) {
51 return mm(path, pattern, {dot: true})
56 // Check if the path matches any of the exclude patterns.
57 if (excludes.some(function (pattern) {
58 return mm(path, pattern, {dot: true})
67 var onlyWatchedTrue = function (pattern) {
68 return pattern.watched
71 var getWatchedPatterns = function (patternObjects) {
72 return patternObjects.filter(onlyWatchedTrue).map(function (patternObject) {
73 return patternObject.pattern
77 exports.watch = function (patterns, excludes, fileList, usePolling, emitter) {
78 var watchedPatterns = getWatchedPatterns(patterns)
80 usePolling: usePolling,
81 ignorePermissionErrors: true,
83 ignored: createIgnore(watchedPatterns, excludes)
85 var chokidarWatcher = new chokidar.FSWatcher(options)
87 watchPatterns(watchedPatterns, chokidarWatcher)
89 var bind = function (fn) {
90 return function (path) {
91 return fn.call(fileList, helper.normalizeWinPath(path))
96 chokidarWatcher.on('add', bind(fileList.addFile))
97 .on('change', bind(fileList.changeFile))
98 .on('unlink', bind(fileList.removeFile))
99 // If we don't subscribe; unhandled errors from Chokidar will bring Karma down
100 // (see GH Issue #959)
101 .on('error', function (e) {
105 emitter.on('exit', function (done) {
106 chokidarWatcher.close()
110 return chokidarWatcher
113 exports.watch.$inject = ['config.files', 'config.exclude', 'fileList', 'config.usePolling',