This repository was archived by the owner on Dec 15, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathpath-scanner.coffee
More file actions
127 lines (109 loc) · 3.36 KB
/
Copy pathpath-scanner.coffee
File metadata and controls
127 lines (109 loc) · 3.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
fs = require("fs")
path = require("path")
{EventEmitter} = require("events")
PathFilter = require("./path-filter")
DIR_SEP = path.sep
# Public: Scans a directory and emits events when paths matching input options
# have been found.
#
# Note: `PathScanner` keeps no state. You must consume paths via the {::path-found} event.
#
# ## Examples
#
# ```coffee
# {PathScanner} = require 'scandal'
# scanner = new PathScanner('/Users/me/myDopeProject', includeHidden: false)
#
# scanner.on 'path-found', (path) ->
# console.log(path)
#
# scanner.on 'finished-scanning', ->
# console.log('All done!')
#
# scanner.scan()
# ```
#
# ## Events
#
# * `path-found` Emit when a path has been found
# * `pathName` {String} name of the path
# * `finished-scanning` Emit when the scanner is finished
#
module.exports =
class PathScanner extends EventEmitter
# Public: Create a {PathScanner} object.
#
# * `rootPath` {String} top level directory to scan. eg. `/Users/ben/somedir`
# * `options` {Object} options hash
# * `excludeVcsIgnores` {Boolean}; default false; true to exclude paths
# defined in a .gitignore. Uses git-utils to check ignred files.
# * `inclusions` {Array} of patterns to include. Uses minimatch with a couple
# additions: `['dirname']` and `['dirname/']` will match all paths in
# directory dirname.
# * `exclusions` {Array} of patterns to exclude. Same matcher as inclusions.
# * `includeHidden` {Boolean} default false; true includes hidden files
constructor: (@rootPath, @options={}) ->
@asyncCallsInProgress = 0
@realPathCache = {}
@rootPath = path.resolve(@rootPath)
@rootPathLength = @rootPath.length
@pathFilter = new PathFilter(@rootPath, @options)
###
Section: Scanning
###
# Public: Begin the scan
scan: ->
@readDir(@rootPath)
readDir: (filePath) ->
@asyncCallStarting()
fs.readdir filePath, (err, files) =>
return @asyncCallDone() unless files
fileCount = files.length
prefix = filePath + DIR_SEP
while fileCount--
file = files.shift()
filename = prefix + file
@processFile(filename)
@asyncCallDone()
relativize: (filePath) ->
len = filePath.length
i = @rootPathLength
while i < len
break unless filePath[i] == DIR_SEP
i++
filePath.slice(i)
processFile: (filePath) ->
relPath = @relativize(filePath)
stat = @stat(filePath)
return unless stat
if stat.isFile() and @pathFilter.isFileAccepted(relPath)
@emit('path-found', filePath)
else if stat.isDirectory() and @pathFilter.isDirectoryAccepted(relPath)
@readDir(filePath)
stat: (filePath) ->
# lstat is SLOW, but what other way to determine if something is a directory or file ?
# also, sync is about 200ms faster than async...
try
stat = fs.lstatSync(filePath)
catch e
return null
if @options.follow and stat.isSymbolicLink()
if @isInternalSymlink(filePath)
return null
try
stat = fs.statSync(filePath)
catch e
return null
stat
isInternalSymlink: (filePath) ->
realPath = null
try
realPath = fs.realpathSync(filePath, @realPathCache)
catch error
; # ignore
realPath?.search(@rootPath) is 0
asyncCallStarting: ->
@asyncCallsInProgress++
asyncCallDone: ->
if --@asyncCallsInProgress is 0
@emit('finished-scanning', this)