/
fs.walker.coffee
146 lines (125 loc) · 3.57 KB
/
fs.walker.coffee
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
fs = require 'fs'
path = require 'path'
_ = require 'underscore'
{EventEmitter} = require 'events'
{basename, dirname, extname, join, existsSync, relative} = path
MAX_INT = 9007199254740992
###
* Walk dirs and files and run callbacks
* @class Walker
###
class WalkerContext
constructor: (@base, @path, @_subpath = '', @depth = 0) ->
@stat = fs.statSync(@path)
isDirectory: -> @stat.isDirectory()
makeSubContext: (file) =>
new WalkerContext(@base
, join(@path, file)
, join(@_subpath, file)
, @depth+1)
enumDir: ->
fs.readdirSync(@path).map(@makeSubContext)
relpath: ->
rel = relative(@base, @path)
return rel if @_subpath is ''
return @_subpath
dirname: -> dirname(@path)
basename: (withExt = yes) ->
if withExt
basename(@path)
else
basename(@path).replace(/(\.[^.\/]*)?$/i, '')
extname: (withDot = yes) ->
return if withDot
then extname(@path)
else extname(@path).slice(1)
subpath: -> dirname(@_subpath)
class Walker
defaults:
relative: null
depth: MAX_INT
###*
* @constructor
* @param {Array|String} dirs
* @param {Object} options
* @param {Boolean} options.relative
* @param {Number} options.depth
* @param {Function} options.on_file
* @param {Function} options.on_dir
###
constructor: (options = {}) ->
@emitter = new EventEmitter()
@emitter.on('dir', options.on_dir) if _.isFunction(options.on_dir)
@emitter.on('file', options.on_file) if _.isFunction(options.on_file)
{@relative, @depth} = _.defaults(options, @defaults)
on: (event, listener) ->
event = event.toLowerCase()
if /^(dir|file)$/.test(event)
@emitter.on(event, listener)
else
throw 'Error: unknown event'
return this
set: (options) ->
@[key] = val for key, val of options when _.has(@defaults,key)
return this
###*
* @public
* @param {Array|String} targets — dirs or files to walk
###
walk: (targets) ->
targets = [ targets ] unless _.isArray(targets)
for path in targets
if @relative?
@base = fs.realpathSync(@relative)
else
stat = fs.statSync(path)
@base = fs.realpathSync(if stat.isDirectory() then path else dirname(path))
@_walk(new WalkerContext(@base, fs.realpathSync(path)))
return this
###*
* @private
* @param {Object} ctx — context
###
_walk: (ctx) ->
return true if ctx.depth > @depth
if ctx.isDirectory()
try @emitter.emit 'dir', ctx.path, ctx
catch err
return false if err is 'break'
return true if err is 'continue'
throw err
for subctx in ctx.enumDir()
return false unless @_walk(subctx)
else
try @emitter.emit 'file', ctx.path, ctx
catch err
return false if err is 'break'
return true if err is 'continue'
throw err
return true
###*
* @api
* @param {Array|String} [targets] - dir or files for scan
* @param {Object} [options] - list of options
*
* @example:
Syntax 1:
walkSync(['/dir', '/dir2'], {
relative: '../',
on_file: function(path, context) {...},
on_dir: function(path, context) {...}
});
Syntax 2:
walkSync()
.set({relative: '../'})
.on('file', function(path, context) {...})
.on('dir', function(path, context) {...})
.walk(['/dir', '/dir2']);
###
walkSync = (targets, options = {}) ->
if arguments.length is 0
return new Walker()
else if arguments.length is 1
return new Walker(arguments[0])
return new Walker(arguments[1]).walk(arguments[0])
exports extends {walkSync, Walker}