Comparing version 0.11.1 to 0.11.2
@@ -0,4 +1,9 @@ | ||
# v0.11.2 | ||
- Add `file.dir` and `file.filename`, which also are kept in sync with `file.path` | ||
- Add support for other encodings, via `file.enc` and the `enc` option when specifying an input directory | ||
# v0.11.1 | ||
- Close FSWatcher instances when their directories are deleted | ||
- Close `FSWatcher` instances when their directories are deleted | ||
@@ -51,7 +56,7 @@ # v0.11.0 | ||
- Do not include directories in `defiler.origFiles`/`defiler.files`/`defiler.origPaths`. | ||
- Do not include directories in `defiler.origFiles`/`defiler.files`/`defiler.origPaths` | ||
# v0.6.1 | ||
- Add second argument to transform and generator callbacks which is a convenience function calling `defiler.get` with the appropriate `dependent`. | ||
- Add second argument to transform and generator callbacks which is a convenience function calling `defiler.get` with the appropriate `dependent` | ||
@@ -58,0 +63,0 @@ # v0.6.0 |
@@ -12,6 +12,13 @@ 'use strict'; | ||
var symbols = new Proxy({}, { get: (_, key) => Symbol(key) }) | ||
let { _path, _dir, _filename, _ext, _enc, _bytes, _text } = symbols; | ||
class File { | ||
constructor(path$$1) { | ||
constructor(path$$1 = '') { | ||
if (typeof path$$1 !== 'string') throw new TypeError('file.path must be a string') | ||
// path of file | ||
this._path = path$$1; | ||
this[_path] = path$$1; | ||
// cached dir/filename/ext values | ||
this[_dir] = this[_filename] = this[_ext] = null; | ||
// all historical paths of file | ||
@@ -21,15 +28,19 @@ this.paths = path$$1 ? [path$$1] : []; | ||
this.stat = null; | ||
// encoding | ||
this[_enc] = 'utf8'; | ||
// Buffer of file contents | ||
this._bytes = null; | ||
this[_bytes] = null; | ||
// string of file contents | ||
this._text = null; | ||
this[_text] = null; | ||
} | ||
get path() { | ||
return this._path | ||
return this[_path] | ||
} | ||
set path(path$$1) { | ||
if (this._path !== path$$1) { | ||
this._path = path$$1; | ||
if (typeof path$$1 !== 'string') throw new TypeError('file.path must be a string') | ||
if (this[_path] !== path$$1) { | ||
this[_path] = path$$1; | ||
this[_dir] = this[_filename] = this[_ext] = null; | ||
this.paths.push(path$$1); | ||
@@ -39,37 +50,75 @@ } | ||
get dir() { | ||
if (this[_dir] == null) { | ||
let p = this[_path].lastIndexOf('/'); | ||
this[_dir] = p > -1 ? this[_path].slice(0, p) : ''; | ||
} | ||
return this[_dir] | ||
} | ||
set dir(dir) { | ||
if (typeof dir !== 'string') throw new TypeError('file.dir must be a string') | ||
this.path = (dir ? dir + '/' : '') + this.filename; | ||
} | ||
get filename() { | ||
if (this[_filename] == null) { | ||
let p = this[_path].lastIndexOf('/'); | ||
this[_filename] = p > -1 ? this[_path].slice(p + 1) : ''; | ||
} | ||
return this[_filename] | ||
} | ||
set filename(filename) { | ||
if (typeof filename !== 'string') throw new TypeError('file.filename must be a string') | ||
let old = this.filename; | ||
this.path = (old ? this[_path].slice(0, -old.length) : this[_path]) + filename; | ||
} | ||
get ext() { | ||
if (this.path && this.path.match) { | ||
let match = this.path.match(/\.[^./\\]+$/); | ||
if (match) return match[0] | ||
if (this[_ext] == null) { | ||
let p1 = this[_path].lastIndexOf('.'); | ||
let p2 = this[_path].lastIndexOf('/'); | ||
this[_ext] = p1 > -1 && p1 > p2 ? this[_path].slice(p1) : ''; | ||
} | ||
return '' | ||
return this[_ext] | ||
} | ||
set ext(ext) { | ||
let oldExt = this.ext; | ||
if (oldExt) { | ||
this.path = this.path.slice(0, -oldExt.length) + ext; | ||
} else { | ||
this.path += ext; | ||
} | ||
if (typeof ext !== 'string') throw new TypeError('file.ext must be a string') | ||
let old = this.ext; | ||
this.path = (old ? this[_path].slice(0, -old.length) : this[_path]) + ext; | ||
} | ||
get enc() { | ||
return this[_enc] | ||
} | ||
set enc(enc) { | ||
if (!Buffer.isEncoding(enc)) throw new TypeError('file.enc must be a supported encoding') | ||
this[_enc] = enc; | ||
} | ||
get bytes() { | ||
if (this._bytes == null && this._text != null) this._bytes = Buffer.from(this._text); | ||
return this._bytes | ||
return this[_bytes] == null && this[_text] != null | ||
? (this[_bytes] = Buffer.from(this[_text], this[_enc])) | ||
: this[_bytes] | ||
} | ||
set bytes(bytes) { | ||
this._bytes = bytes; | ||
this._text = null; | ||
if (!Buffer.isBuffer(bytes)) throw new TypeError('file.bytes must be a Buffer') | ||
this[_bytes] = bytes; | ||
this[_text] = null; | ||
} | ||
get text() { | ||
if (this._text == null && this._bytes != null) this._text = this._bytes.toString(); | ||
return this._text | ||
return this[_text] == null && this[_bytes] != null | ||
? (this[_text] = this[_bytes].toString(this[_enc])) | ||
: this[_text] | ||
} | ||
set text(text) { | ||
this._text = text; | ||
this._bytes = null; | ||
if (typeof text !== 'string') throw new TypeError('file.text must be a string') | ||
this[_text] = text; | ||
this[_bytes] = null; | ||
} | ||
@@ -82,2 +131,16 @@ } | ||
let { | ||
_dir: _dir$1, | ||
_watch, | ||
_debounce, | ||
_dirs, | ||
_files, | ||
_timeouts, | ||
_queue, | ||
_processing, | ||
_recurse, | ||
_handle, | ||
_enqueue, | ||
} = symbols; | ||
class Watcher extends EventEmitter { | ||
@@ -87,17 +150,17 @@ constructor(dir, watch, debounce) { | ||
// directory to recursively watch the contents of | ||
this._dir = dir; | ||
this[_dir$1] = dir; | ||
// whether to actually watch for changes (or just walk and retrieve contents and file stats) | ||
this._watch = watch; | ||
this[_watch] = watch; | ||
// fs.watch event debounce, in milliseconds | ||
this._debounce = debounce; | ||
this[_debounce] = debounce; | ||
// paths of all (recursive) directories -> FSWatcher instances | ||
this._dirs = new Map(); | ||
this[_dirs] = new Map(); | ||
// paths of all (recursive) files -> file stats | ||
this._files = new Map(); | ||
this[_files] = new Map(); | ||
// paths of (recursive) files with pending debounced events -> setTimeout timer ids | ||
this._timeouts = new Map(); | ||
this[_timeouts] = new Map(); | ||
// queue of pending FSWatcher events to handle | ||
this._queue = []; | ||
this[_queue] = []; | ||
// whether some FSWatcher event is currently already in the process of being handled | ||
this._processing = false; | ||
this[_processing] = false; | ||
} | ||
@@ -108,15 +171,15 @@ | ||
async init() { | ||
await this._recurse(this._dir); | ||
return [...this._files.entries()].map(([path$$1, stat$$1]) => ({ path: path$$1, stat: stat$$1 })) | ||
await this[_recurse](this[_dir$1]); | ||
return [...this[_files].entries()].map(([path$$1, stat$$1]) => ({ path: path$$1, stat: stat$$1 })) | ||
} | ||
// recurse a given directory | ||
async _recurse(full) { | ||
let path$$1 = full.slice(this._dir.length + 1); | ||
async [_recurse](full) { | ||
let path$$1 = full.slice(this[_dir$1].length + 1); | ||
let fileStat = await stat(full); | ||
if (fileStat.isFile()) { | ||
this._files.set(path$$1, fileStat); | ||
this[_files].set(path$$1, fileStat); | ||
} else if (fileStat.isDirectory()) { | ||
if (this._watch) this._dirs.set(path$$1, fs.watch(full, this._handle.bind(this, full))); | ||
await Promise.all((await readdir(full)).map(sub => this._recurse(full + '/' + sub))); | ||
if (this[_watch]) this[_dirs].set(path$$1, fs.watch(full, this[_handle].bind(this, full))); | ||
await Promise.all((await readdir(full)).map(sub => this[_recurse](full + '/' + sub))); | ||
} | ||
@@ -126,11 +189,11 @@ } | ||
// handle FSWatcher event for given directory | ||
_handle(dir, event, file) { | ||
[_handle](dir, event, file) { | ||
let full = dir + '/' + file; | ||
if (this._timeouts.has(full)) clearTimeout(this._timeouts.get(full)); | ||
this._timeouts.set( | ||
if (this[_timeouts].has(full)) clearTimeout(this[_timeouts].get(full)); | ||
this[_timeouts].set( | ||
full, | ||
setTimeout(() => { | ||
this._timeouts.delete(full); | ||
this._enqueue(full); | ||
}, this._debounce), | ||
this[_timeouts].delete(full); | ||
this[_enqueue](full); | ||
}, this[_debounce]), | ||
); | ||
@@ -140,9 +203,9 @@ } | ||
// add an FSWatcher event to the queue, and handle queued events | ||
async _enqueue(full) { | ||
this._queue.push(full); | ||
if (this._processing) return | ||
this._processing = true; | ||
while (this._queue.length) { | ||
let full = this._queue.shift(); | ||
let path$$1 = full.slice(this._dir.length + 1); | ||
async [_enqueue](full) { | ||
this[_queue].push(full); | ||
if (this[_processing]) return | ||
this[_processing] = true; | ||
while (this[_queue].length) { | ||
let full = this[_queue].shift(); | ||
let path$$1 = full.slice(this[_dir$1].length + 1); | ||
try { | ||
@@ -152,8 +215,8 @@ let fileStat = await stat(full); | ||
// note the new/changed file | ||
this._files.set(path$$1, fileStat); | ||
this[_files].set(path$$1, fileStat); | ||
this.emit('', { event: '+', path: path$$1, stat: fileStat }); | ||
} else if (fileStat.isDirectory() && !this._dirs.has(path$$1)) { | ||
} else if (fileStat.isDirectory() && !this[_dirs].has(path$$1)) { | ||
// note the new directory: start watching it, and report any files in it | ||
await this._recurse(full); | ||
for (let [newPath, fileStat] of this._files.entries()) { | ||
await this[_recurse](full); | ||
for (let [newPath, fileStat] of this[_files].entries()) { | ||
if (newPath.startsWith(path$$1 + '/')) { | ||
@@ -166,17 +229,17 @@ this.emit('', { event: '+', path: newPath, stat: fileStat }); | ||
// probably this was a deleted file/directory | ||
if (this._files.has(path$$1)) { | ||
if (this[_files].has(path$$1)) { | ||
// note the deleted file | ||
this._files.delete(path$$1); | ||
this[_files].delete(path$$1); | ||
this.emit('', { event: '-', path: path$$1 }); | ||
} else if (this._dirs.has(path$$1)) { | ||
} else if (this[_dirs].has(path$$1)) { | ||
// note the deleted directory: stop watching it, and report any files that were in it | ||
for (let old of this._dirs.keys()) { | ||
for (let old of this[_dirs].keys()) { | ||
if (old === path$$1 || old.startsWith(path$$1 + '/')) { | ||
this._dirs.get(old).close(); | ||
this._dirs.delete(old); | ||
this[_dirs].get(old).close(); | ||
this[_dirs].delete(old); | ||
} | ||
} | ||
for (let old of this._files.keys()) { | ||
for (let old of this[_files].keys()) { | ||
if (old.startsWith(path$$1 + '/')) { | ||
this._files.delete(old); | ||
this[_files].delete(old); | ||
this.emit('', { event: '-', path: old }); | ||
@@ -188,6 +251,33 @@ } | ||
} | ||
this._processing = false; | ||
this[_processing] = false; | ||
} | ||
} | ||
let { | ||
_origFiles, | ||
_files: _files$1, | ||
_status, | ||
_watchers, | ||
_transforms, | ||
_generators, | ||
_done, | ||
_pending, | ||
_waiting, | ||
_available, | ||
_dependents, | ||
_queue: _queue$1, | ||
_processing: _processing$1, | ||
_checkBeforeExec, | ||
_checkAfterExec, | ||
_wait, | ||
_enqueue: _enqueue$1, | ||
_processPhysicalFile, | ||
_processFile, | ||
_transformFile, | ||
_handleGeneratedFile, | ||
_get, | ||
_processDependents, | ||
_found, | ||
} = symbols; | ||
class Defiler extends EventEmitter { | ||
@@ -197,27 +287,27 @@ constructor() { | ||
// original paths -> original files for all physical files | ||
this._origFiles = new Map(); | ||
this[_origFiles] = new Map(); | ||
// original paths -> transformed files for all physical/generated/etc. files | ||
this._files = new Map(); | ||
this[_files$1] = new Map(); | ||
// null = exec not called; false = exec pending; true = exec finished | ||
this._status = null; | ||
this[_status] = null; | ||
// all registered Watcher instances (one per directory) | ||
this._watchers = []; | ||
this[_watchers] = []; | ||
// all registered transforms | ||
this._transforms = []; | ||
this[_transforms] = []; | ||
// paths -> registered generators | ||
this._generators = new Map(); | ||
this[_generators] = new Map(); | ||
// { count, resolve, reject } object for main promise for the first exec wave | ||
this._done = { count: 0, resolve: null, reject: null }; | ||
this[_done] = { count: 0, resolve: null, reject: null }; | ||
// original paths of all files currently undergoing some transform/generator | ||
this._pending = new Set(); | ||
this[_pending] = new Set(); | ||
// original paths -> number of other files they're currently waiting on to exist | ||
this._waiting = new Map(); | ||
this[_waiting] = new Map(); | ||
// original paths -> { promise, resolve } objects for when awaited files become available | ||
this._available = new Map(); | ||
this[_available] = new Map(); | ||
// original paths of dependents -> set of original paths of dependencies, specifying changes to which files should trigger re-processing which other files | ||
this._dependents = new Map(); | ||
this[_dependents] = new Map(); | ||
// queue of pending Watcher events to handle | ||
this._queue = []; | ||
this[_queue$1] = []; | ||
// whether some Watcher event is currently already in the process of being handled | ||
this._processing = false; | ||
this[_processing$1] = false; | ||
} | ||
@@ -228,15 +318,15 @@ | ||
get status() { | ||
return this._status | ||
return this[_status] | ||
} | ||
get origFiles() { | ||
return this._origFiles | ||
return this[_origFiles] | ||
} | ||
get files() { | ||
return this._files | ||
return this[_files$1] | ||
} | ||
get origPaths() { | ||
return [...this._origFiles.keys()].sort() | ||
return [...this[_origFiles].keys()].sort() | ||
} | ||
@@ -248,7 +338,8 @@ | ||
dir(...dirs) { | ||
this._checkBeforeExec('dir'); | ||
for (let { dir, read = true, watch = true, debounce = 10 } of dirs.filter(Boolean)) { | ||
dir = path.resolve(dir).replace(/\\/g, '/'); | ||
this[_checkBeforeExec]('dir'); | ||
for (let config of dirs.filter(Boolean)) { | ||
let { dir, read = true, enc = 'utf8', watch = true, debounce = 10 } = config; | ||
dir = path.resolve(dir); | ||
let watcher = new Watcher(dir, watch, debounce); | ||
this._watchers.push({ watcher, dir, read, watch }); | ||
this[_watchers].push({ watcher, dir, read, enc, watch }); | ||
} | ||
@@ -260,4 +351,4 @@ return this | ||
transform(...transforms) { | ||
this._checkBeforeExec('transform'); | ||
this._transforms.push(...transforms.filter(Boolean)); | ||
this[_checkBeforeExec]('transform'); | ||
this[_transforms].push(...transforms.filter(Boolean)); | ||
return this | ||
@@ -268,5 +359,5 @@ } | ||
generator(generators) { | ||
this._checkBeforeExec('generator'); | ||
this[_checkBeforeExec]('generator'); | ||
for (let [path$$1, generator] of Object.entries(generators)) { | ||
if (path$$1 && generator) this._generators.set(path$$1, generator); | ||
if (path$$1 && generator) this[_generators].set(path$$1, generator); | ||
} | ||
@@ -279,8 +370,8 @@ return this | ||
async exec() { | ||
this._checkBeforeExec('exec'); | ||
this._status = false; | ||
this._processing = true; | ||
this[_checkBeforeExec]('exec'); | ||
this[_status] = false; | ||
this[_processing$1] = true; | ||
let promise = new Promise((res, rej) => { | ||
this._done.resolve = res; | ||
this._done.reject = rej; | ||
this[_done].resolve = res; | ||
this[_done].reject = rej; | ||
}); | ||
@@ -290,24 +381,26 @@ // init all Watcher instances; note that all files have pending transforms | ||
await Promise.all( | ||
this._watchers.map(async ({ watcher, dir, read, watch }) => { | ||
this[_watchers].map(async ({ watcher, dir, read, enc, watch }) => { | ||
if (watch) { | ||
watcher.on('', ({ event, path: path$$1, stat: stat$$1 }) => this._enqueue({ event, dir, path: path$$1, stat: stat$$1, read })); | ||
watcher.on('', ({ event, path: path$$1, stat: stat$$1 }) => { | ||
this[_enqueue$1]({ event, dir, path: path$$1, stat: stat$$1, read, enc }); | ||
}); | ||
} | ||
for (let { path: path$$1, stat: stat$$1 } of await watcher.init()) { | ||
files.push({ dir, path: path$$1, stat: stat$$1, read }); | ||
this._origFiles.set(path$$1, null); | ||
this._pending.add(path$$1); | ||
files.push({ dir, path: path$$1, stat: stat$$1, read, enc }); | ||
this[_origFiles].set(path$$1, null); | ||
this[_pending].add(path$$1); | ||
} | ||
}), | ||
); | ||
for (let path$$1 of this._generators.keys()) this._pending.add(path$$1); | ||
for (let path$$1 of this[_generators].keys()) this[_pending].add(path$$1); | ||
// process each physical file | ||
for (let { dir, path: path$$1, stat: stat$$1, read } of files) { | ||
this._wait(this._processPhysicalFile(dir, path$$1, stat$$1, read)); | ||
for (let { dir, path: path$$1, stat: stat$$1, read, enc } of files) { | ||
this[_wait](this[_processPhysicalFile](dir, path$$1, stat$$1, read, enc)); | ||
} | ||
// process each generated file | ||
for (let path$$1 of this._generators.keys()) this._wait(this._handleGeneratedFile(path$$1)); | ||
for (let path$$1 of this[_generators].keys()) this[_wait](this[_handleGeneratedFile](path$$1)); | ||
// wait and finish up | ||
await promise; | ||
this._status = true; | ||
this._processing = false; | ||
this[_status] = true; | ||
this[_processing$1] = false; | ||
} | ||
@@ -319,10 +412,10 @@ | ||
async file(file) { | ||
this._checkAfterExec('file'); | ||
this[_checkAfterExec]('file'); | ||
let { path: path$$1 } = file; | ||
if (!(file instanceof File)) file = Object.assign(new File(), file); | ||
await this._wait(this._transformFile(file)); | ||
this._files.set(path$$1, file); | ||
await this[_wait](this[_transformFile](file)); | ||
this[_files$1].set(path$$1, file); | ||
this.emit('file', { defiler: this, path: path$$1, file }); | ||
this._found(path$$1); | ||
this._processDependents(path$$1); | ||
this[_found](path$$1); | ||
this[_processDependents](path$$1); | ||
} | ||
@@ -332,7 +425,7 @@ | ||
depend(dependent, path$$1) { | ||
this._checkAfterExec('depend'); | ||
if (this._dependents.has(dependent)) { | ||
this._dependents.get(dependent).add(path$$1); | ||
this[_checkAfterExec]('depend'); | ||
if (this[_dependents].has(dependent)) { | ||
this[_dependents].get(dependent).add(path$$1); | ||
} else { | ||
this._dependents.set(dependent, new Set([path$$1])); | ||
this[_dependents].set(dependent, new Set([path$$1])); | ||
} | ||
@@ -343,15 +436,15 @@ } | ||
_checkBeforeExec(methodName) { | ||
if (this._status !== null) throw new Error(`Cannot call ${methodName} after calling exec`) | ||
[_checkBeforeExec](methodName) { | ||
if (this[_status] !== null) throw new Error(`Cannot call ${methodName} after calling exec`) | ||
} | ||
_checkAfterExec(methodName) { | ||
if (this._status === null) throw new Error(`Cannot call ${methodName} before calling exec`) | ||
[_checkAfterExec](methodName) { | ||
if (this[_status] === null) throw new Error(`Cannot call ${methodName} before calling exec`) | ||
} | ||
// add another promise that must resolve before the initial exec wave can finish | ||
_wait(promise) { | ||
if (!this._status) { | ||
this._done.count++; | ||
promise.then(() => --this._done.count || this._done.resolve(), this._done.reject); | ||
[_wait](promise) { | ||
if (!this[_status]) { | ||
this[_done].count++; | ||
promise.then(() => --this[_done].count || this[_done].resolve(), this[_done].reject); | ||
} | ||
@@ -362,46 +455,45 @@ return promise | ||
// add a Watcher event to the queue, and handle queued events | ||
async _enqueue(event) { | ||
this._queue.push(event); | ||
if (this._processing) return | ||
this._processing = true; | ||
while (this._queue.length) { | ||
let { event, dir, path: path$$1, stat: stat$$1, read } = this._queue.shift(); | ||
async [_enqueue$1](event) { | ||
this[_queue$1].push(event); | ||
if (this[_processing$1]) return | ||
this[_processing$1] = true; | ||
while (this[_queue$1].length) { | ||
let { event, dir, path: path$$1, stat: stat$$1, read, enc } = this[_queue$1].shift(); | ||
if (event === '+') { | ||
await this._processPhysicalFile(dir, path$$1, stat$$1, read); | ||
await this[_processPhysicalFile](dir, path$$1, stat$$1, read, enc); | ||
} else if (event === '-') { | ||
this._origFiles.delete(path$$1); | ||
this._files.delete(path$$1); | ||
this[_origFiles].delete(path$$1); | ||
this[_files$1].delete(path$$1); | ||
this.emit('deleted', { defiler: this, path: path$$1 }); | ||
this._processDependents(path$$1); | ||
this[_processDependents](path$$1); | ||
} | ||
} | ||
this._processing = false; | ||
this[_processing$1] = false; | ||
} | ||
// create a file object for a physical file and process it | ||
async _processPhysicalFile(dir, path$$1, stat$$1, read) { | ||
let origFile = new File(path$$1); | ||
origFile.stat = stat$$1; | ||
async [_processPhysicalFile](dir, path$$1, stat$$1, read, enc) { | ||
let origFile = Object.assign(new File(), { path: path$$1, stat: stat$$1, enc }); | ||
if (read) origFile.bytes = await readFile(dir + '/' + path$$1); | ||
this._origFiles.set(path$$1, origFile); | ||
this[_origFiles].set(path$$1, origFile); | ||
this.emit('origFile', { defiler: this, file: origFile }); | ||
await this._processFile(origFile); | ||
await this[_processFile](origFile); | ||
} | ||
// transform a file, store it, and process dependents | ||
async _processFile(origFile) { | ||
async [_processFile](origFile) { | ||
let file = Object.assign(new File(), origFile); | ||
await this._transformFile(file); | ||
this._files.set(origFile.path, file); | ||
await this[_transformFile](file); | ||
this[_files$1].set(origFile.path, file); | ||
this.emit('file', { defiler: this, path: origFile.path, file }); | ||
this._found(origFile.path); | ||
this._processDependents(origFile.path); | ||
this[_found](origFile.path); | ||
this[_processDependents](origFile.path); | ||
} | ||
// perform all transforms on a file | ||
async _transformFile(file) { | ||
async [_transformFile](file) { | ||
let { path: path$$1 } = file; | ||
this._pending.add(path$$1); | ||
this[_pending].add(path$$1); | ||
try { | ||
for (let transform of this._transforms) { | ||
for (let transform of this[_transforms]) { | ||
await transform({ | ||
@@ -411,3 +503,3 @@ defiler: this, | ||
file, | ||
get: dependency => this._get(path$$1, dependency), | ||
get: dependency => this[_get](path$$1, dependency), | ||
}); | ||
@@ -418,7 +510,7 @@ } | ||
} | ||
this._pending.delete(path$$1); | ||
if (!this._status && [...this._pending].every(path$$1 => this._waiting.get(path$$1))) { | ||
this[_pending].delete(path$$1); | ||
if (!this[_status] && [...this[_pending]].every(path$$1 => this[_waiting].get(path$$1))) { | ||
// all pending files are currently waiting for one or more other files to exist | ||
// break deadlock: assume all files that have not appeared yet will never do so | ||
for (let path$$1 of this._available.keys()) if (!this._pending.has(path$$1)) this._found(path$$1); | ||
for (let path$$1 of this[_available].keys()) if (!this[_pending].has(path$$1)) this[_found](path$$1); | ||
} | ||
@@ -428,10 +520,10 @@ } | ||
// run a generator and transform and add the file | ||
async _handleGeneratedFile(path$$1) { | ||
async [_handleGeneratedFile](path$$1) { | ||
let file; | ||
try { | ||
file = new File(path$$1); | ||
await this._generators.get(path$$1)({ | ||
await this[_generators].get(path$$1)({ | ||
defiler: this, | ||
file, | ||
get: dependency => this._get(path$$1, dependency), | ||
get: dependency => this[_get](path$$1, dependency), | ||
}); | ||
@@ -445,35 +537,35 @@ await this.file(file); | ||
// wait for a file to be available and mark another file as depending on it | ||
async _get(dependent, path$$1) { | ||
if (Array.isArray(path$$1)) return Promise.all(path$$1.map(path$$1 => this._get(dependent, path$$1))) | ||
async [_get](dependent, path$$1) { | ||
if (Array.isArray(path$$1)) return Promise.all(path$$1.map(path$$1 => this[_get](dependent, path$$1))) | ||
this.depend(dependent, path$$1); | ||
if (!this._status && !this._files.has(path$$1)) { | ||
this._waiting.set(dependent, (this._waiting.get(dependent) || 0) + 1); | ||
if (this._available.has(path$$1)) { | ||
await this._available.get(path$$1).promise; | ||
if (!this[_status] && !this[_files$1].has(path$$1)) { | ||
this[_waiting].set(dependent, (this[_waiting].get(dependent) || 0) + 1); | ||
if (this[_available].has(path$$1)) { | ||
await this[_available].get(path$$1).promise; | ||
} else { | ||
let resolve; | ||
let promise = new Promise(res => (resolve = res)); | ||
this._available.set(path$$1, { promise, resolve }); | ||
this[_available].set(path$$1, { promise, resolve }); | ||
await promise; | ||
} | ||
this._waiting.set(dependent, this._waiting.get(dependent) - 1); | ||
this[_waiting].set(dependent, this[_waiting].get(dependent) - 1); | ||
} | ||
return this._files.get(path$$1) | ||
return this[_files$1].get(path$$1) | ||
} | ||
// re-process all files that depend on a particular path | ||
_processDependents(path$$1) { | ||
if (!this._status) return | ||
[_processDependents](path$$1) { | ||
if (!this[_status]) return | ||
let dependents = new Set(); | ||
for (let [dependent, dependencies] of this._dependents.entries()) { | ||
for (let [dependent, dependencies] of this[_dependents].entries()) { | ||
if (dependencies.has(path$$1)) { | ||
dependents.add(dependent); | ||
this._dependents.delete(dependent); | ||
this[_dependents].delete(dependent); | ||
} | ||
} | ||
for (let dependent of dependents) { | ||
if (this._generators.has(dependent)) { | ||
this._handleGeneratedFile(dependent); | ||
} else if (this._origFiles.has(dependent)) { | ||
this._processFile(this._origFiles.get(dependent)); | ||
if (this[_generators].has(dependent)) { | ||
this[_handleGeneratedFile](dependent); | ||
} else if (this[_origFiles].has(dependent)) { | ||
this[_processFile](this[_origFiles].get(dependent)); | ||
} | ||
@@ -484,6 +576,6 @@ } | ||
// mark a given awaited file as being found | ||
_found(path$$1) { | ||
if (!this._status && this._available.has(path$$1)) { | ||
this._available.get(path$$1).resolve(); | ||
this._available.delete(path$$1); | ||
[_found](path$$1) { | ||
if (!this[_status] && this[_available].has(path$$1)) { | ||
this[_available].get(path$$1).resolve(); | ||
this[_available].delete(path$$1); | ||
} | ||
@@ -490,0 +582,0 @@ } |
@@ -6,6 +6,13 @@ import { readdir, readFile, stat, watch } from 'fs'; | ||
var symbols = new Proxy({}, { get: (_, key) => Symbol(key) }) | ||
let { _path, _dir, _filename, _ext, _enc, _bytes, _text } = symbols; | ||
class File { | ||
constructor(path) { | ||
constructor(path = '') { | ||
if (typeof path !== 'string') throw new TypeError('file.path must be a string') | ||
// path of file | ||
this._path = path; | ||
this[_path] = path; | ||
// cached dir/filename/ext values | ||
this[_dir] = this[_filename] = this[_ext] = null; | ||
// all historical paths of file | ||
@@ -15,15 +22,19 @@ this.paths = path ? [path] : []; | ||
this.stat = null; | ||
// encoding | ||
this[_enc] = 'utf8'; | ||
// Buffer of file contents | ||
this._bytes = null; | ||
this[_bytes] = null; | ||
// string of file contents | ||
this._text = null; | ||
this[_text] = null; | ||
} | ||
get path() { | ||
return this._path | ||
return this[_path] | ||
} | ||
set path(path) { | ||
if (this._path !== path) { | ||
this._path = path; | ||
if (typeof path !== 'string') throw new TypeError('file.path must be a string') | ||
if (this[_path] !== path) { | ||
this[_path] = path; | ||
this[_dir] = this[_filename] = this[_ext] = null; | ||
this.paths.push(path); | ||
@@ -33,37 +44,75 @@ } | ||
get dir() { | ||
if (this[_dir] == null) { | ||
let p = this[_path].lastIndexOf('/'); | ||
this[_dir] = p > -1 ? this[_path].slice(0, p) : ''; | ||
} | ||
return this[_dir] | ||
} | ||
set dir(dir) { | ||
if (typeof dir !== 'string') throw new TypeError('file.dir must be a string') | ||
this.path = (dir ? dir + '/' : '') + this.filename; | ||
} | ||
get filename() { | ||
if (this[_filename] == null) { | ||
let p = this[_path].lastIndexOf('/'); | ||
this[_filename] = p > -1 ? this[_path].slice(p + 1) : ''; | ||
} | ||
return this[_filename] | ||
} | ||
set filename(filename) { | ||
if (typeof filename !== 'string') throw new TypeError('file.filename must be a string') | ||
let old = this.filename; | ||
this.path = (old ? this[_path].slice(0, -old.length) : this[_path]) + filename; | ||
} | ||
get ext() { | ||
if (this.path && this.path.match) { | ||
let match = this.path.match(/\.[^./\\]+$/); | ||
if (match) return match[0] | ||
if (this[_ext] == null) { | ||
let p1 = this[_path].lastIndexOf('.'); | ||
let p2 = this[_path].lastIndexOf('/'); | ||
this[_ext] = p1 > -1 && p1 > p2 ? this[_path].slice(p1) : ''; | ||
} | ||
return '' | ||
return this[_ext] | ||
} | ||
set ext(ext) { | ||
let oldExt = this.ext; | ||
if (oldExt) { | ||
this.path = this.path.slice(0, -oldExt.length) + ext; | ||
} else { | ||
this.path += ext; | ||
} | ||
if (typeof ext !== 'string') throw new TypeError('file.ext must be a string') | ||
let old = this.ext; | ||
this.path = (old ? this[_path].slice(0, -old.length) : this[_path]) + ext; | ||
} | ||
get enc() { | ||
return this[_enc] | ||
} | ||
set enc(enc) { | ||
if (!Buffer.isEncoding(enc)) throw new TypeError('file.enc must be a supported encoding') | ||
this[_enc] = enc; | ||
} | ||
get bytes() { | ||
if (this._bytes == null && this._text != null) this._bytes = Buffer.from(this._text); | ||
return this._bytes | ||
return this[_bytes] == null && this[_text] != null | ||
? (this[_bytes] = Buffer.from(this[_text], this[_enc])) | ||
: this[_bytes] | ||
} | ||
set bytes(bytes) { | ||
this._bytes = bytes; | ||
this._text = null; | ||
if (!Buffer.isBuffer(bytes)) throw new TypeError('file.bytes must be a Buffer') | ||
this[_bytes] = bytes; | ||
this[_text] = null; | ||
} | ||
get text() { | ||
if (this._text == null && this._bytes != null) this._text = this._bytes.toString(); | ||
return this._text | ||
return this[_text] == null && this[_bytes] != null | ||
? (this[_text] = this[_bytes].toString(this[_enc])) | ||
: this[_text] | ||
} | ||
set text(text) { | ||
this._text = text; | ||
this._bytes = null; | ||
if (typeof text !== 'string') throw new TypeError('file.text must be a string') | ||
this[_text] = text; | ||
this[_bytes] = null; | ||
} | ||
@@ -76,2 +125,16 @@ } | ||
let { | ||
_dir: _dir$1, | ||
_watch, | ||
_debounce, | ||
_dirs, | ||
_files, | ||
_timeouts, | ||
_queue, | ||
_processing, | ||
_recurse, | ||
_handle, | ||
_enqueue, | ||
} = symbols; | ||
class Watcher extends EventEmitter { | ||
@@ -81,17 +144,17 @@ constructor(dir, watch$$1, debounce) { | ||
// directory to recursively watch the contents of | ||
this._dir = dir; | ||
this[_dir$1] = dir; | ||
// whether to actually watch for changes (or just walk and retrieve contents and file stats) | ||
this._watch = watch$$1; | ||
this[_watch] = watch$$1; | ||
// fs.watch event debounce, in milliseconds | ||
this._debounce = debounce; | ||
this[_debounce] = debounce; | ||
// paths of all (recursive) directories -> FSWatcher instances | ||
this._dirs = new Map(); | ||
this[_dirs] = new Map(); | ||
// paths of all (recursive) files -> file stats | ||
this._files = new Map(); | ||
this[_files] = new Map(); | ||
// paths of (recursive) files with pending debounced events -> setTimeout timer ids | ||
this._timeouts = new Map(); | ||
this[_timeouts] = new Map(); | ||
// queue of pending FSWatcher events to handle | ||
this._queue = []; | ||
this[_queue] = []; | ||
// whether some FSWatcher event is currently already in the process of being handled | ||
this._processing = false; | ||
this[_processing] = false; | ||
} | ||
@@ -102,15 +165,15 @@ | ||
async init() { | ||
await this._recurse(this._dir); | ||
return [...this._files.entries()].map(([path, stat$$1]) => ({ path, stat: stat$$1 })) | ||
await this[_recurse](this[_dir$1]); | ||
return [...this[_files].entries()].map(([path, stat$$1]) => ({ path, stat: stat$$1 })) | ||
} | ||
// recurse a given directory | ||
async _recurse(full) { | ||
let path = full.slice(this._dir.length + 1); | ||
async [_recurse](full) { | ||
let path = full.slice(this[_dir$1].length + 1); | ||
let fileStat = await stat$1(full); | ||
if (fileStat.isFile()) { | ||
this._files.set(path, fileStat); | ||
this[_files].set(path, fileStat); | ||
} else if (fileStat.isDirectory()) { | ||
if (this._watch) this._dirs.set(path, watch(full, this._handle.bind(this, full))); | ||
await Promise.all((await readdir$1(full)).map(sub => this._recurse(full + '/' + sub))); | ||
if (this[_watch]) this[_dirs].set(path, watch(full, this[_handle].bind(this, full))); | ||
await Promise.all((await readdir$1(full)).map(sub => this[_recurse](full + '/' + sub))); | ||
} | ||
@@ -120,11 +183,11 @@ } | ||
// handle FSWatcher event for given directory | ||
_handle(dir, event, file) { | ||
[_handle](dir, event, file) { | ||
let full = dir + '/' + file; | ||
if (this._timeouts.has(full)) clearTimeout(this._timeouts.get(full)); | ||
this._timeouts.set( | ||
if (this[_timeouts].has(full)) clearTimeout(this[_timeouts].get(full)); | ||
this[_timeouts].set( | ||
full, | ||
setTimeout(() => { | ||
this._timeouts.delete(full); | ||
this._enqueue(full); | ||
}, this._debounce), | ||
this[_timeouts].delete(full); | ||
this[_enqueue](full); | ||
}, this[_debounce]), | ||
); | ||
@@ -134,9 +197,9 @@ } | ||
// add an FSWatcher event to the queue, and handle queued events | ||
async _enqueue(full) { | ||
this._queue.push(full); | ||
if (this._processing) return | ||
this._processing = true; | ||
while (this._queue.length) { | ||
let full = this._queue.shift(); | ||
let path = full.slice(this._dir.length + 1); | ||
async [_enqueue](full) { | ||
this[_queue].push(full); | ||
if (this[_processing]) return | ||
this[_processing] = true; | ||
while (this[_queue].length) { | ||
let full = this[_queue].shift(); | ||
let path = full.slice(this[_dir$1].length + 1); | ||
try { | ||
@@ -146,8 +209,8 @@ let fileStat = await stat$1(full); | ||
// note the new/changed file | ||
this._files.set(path, fileStat); | ||
this[_files].set(path, fileStat); | ||
this.emit('', { event: '+', path, stat: fileStat }); | ||
} else if (fileStat.isDirectory() && !this._dirs.has(path)) { | ||
} else if (fileStat.isDirectory() && !this[_dirs].has(path)) { | ||
// note the new directory: start watching it, and report any files in it | ||
await this._recurse(full); | ||
for (let [newPath, fileStat] of this._files.entries()) { | ||
await this[_recurse](full); | ||
for (let [newPath, fileStat] of this[_files].entries()) { | ||
if (newPath.startsWith(path + '/')) { | ||
@@ -160,17 +223,17 @@ this.emit('', { event: '+', path: newPath, stat: fileStat }); | ||
// probably this was a deleted file/directory | ||
if (this._files.has(path)) { | ||
if (this[_files].has(path)) { | ||
// note the deleted file | ||
this._files.delete(path); | ||
this[_files].delete(path); | ||
this.emit('', { event: '-', path }); | ||
} else if (this._dirs.has(path)) { | ||
} else if (this[_dirs].has(path)) { | ||
// note the deleted directory: stop watching it, and report any files that were in it | ||
for (let old of this._dirs.keys()) { | ||
for (let old of this[_dirs].keys()) { | ||
if (old === path || old.startsWith(path + '/')) { | ||
this._dirs.get(old).close(); | ||
this._dirs.delete(old); | ||
this[_dirs].get(old).close(); | ||
this[_dirs].delete(old); | ||
} | ||
} | ||
for (let old of this._files.keys()) { | ||
for (let old of this[_files].keys()) { | ||
if (old.startsWith(path + '/')) { | ||
this._files.delete(old); | ||
this[_files].delete(old); | ||
this.emit('', { event: '-', path: old }); | ||
@@ -182,6 +245,33 @@ } | ||
} | ||
this._processing = false; | ||
this[_processing] = false; | ||
} | ||
} | ||
let { | ||
_origFiles, | ||
_files: _files$1, | ||
_status, | ||
_watchers, | ||
_transforms, | ||
_generators, | ||
_done, | ||
_pending, | ||
_waiting, | ||
_available, | ||
_dependents, | ||
_queue: _queue$1, | ||
_processing: _processing$1, | ||
_checkBeforeExec, | ||
_checkAfterExec, | ||
_wait, | ||
_enqueue: _enqueue$1, | ||
_processPhysicalFile, | ||
_processFile, | ||
_transformFile, | ||
_handleGeneratedFile, | ||
_get, | ||
_processDependents, | ||
_found, | ||
} = symbols; | ||
class Defiler extends EventEmitter { | ||
@@ -191,27 +281,27 @@ constructor() { | ||
// original paths -> original files for all physical files | ||
this._origFiles = new Map(); | ||
this[_origFiles] = new Map(); | ||
// original paths -> transformed files for all physical/generated/etc. files | ||
this._files = new Map(); | ||
this[_files$1] = new Map(); | ||
// null = exec not called; false = exec pending; true = exec finished | ||
this._status = null; | ||
this[_status] = null; | ||
// all registered Watcher instances (one per directory) | ||
this._watchers = []; | ||
this[_watchers] = []; | ||
// all registered transforms | ||
this._transforms = []; | ||
this[_transforms] = []; | ||
// paths -> registered generators | ||
this._generators = new Map(); | ||
this[_generators] = new Map(); | ||
// { count, resolve, reject } object for main promise for the first exec wave | ||
this._done = { count: 0, resolve: null, reject: null }; | ||
this[_done] = { count: 0, resolve: null, reject: null }; | ||
// original paths of all files currently undergoing some transform/generator | ||
this._pending = new Set(); | ||
this[_pending] = new Set(); | ||
// original paths -> number of other files they're currently waiting on to exist | ||
this._waiting = new Map(); | ||
this[_waiting] = new Map(); | ||
// original paths -> { promise, resolve } objects for when awaited files become available | ||
this._available = new Map(); | ||
this[_available] = new Map(); | ||
// original paths of dependents -> set of original paths of dependencies, specifying changes to which files should trigger re-processing which other files | ||
this._dependents = new Map(); | ||
this[_dependents] = new Map(); | ||
// queue of pending Watcher events to handle | ||
this._queue = []; | ||
this[_queue$1] = []; | ||
// whether some Watcher event is currently already in the process of being handled | ||
this._processing = false; | ||
this[_processing$1] = false; | ||
} | ||
@@ -222,15 +312,15 @@ | ||
get status() { | ||
return this._status | ||
return this[_status] | ||
} | ||
get origFiles() { | ||
return this._origFiles | ||
return this[_origFiles] | ||
} | ||
get files() { | ||
return this._files | ||
return this[_files$1] | ||
} | ||
get origPaths() { | ||
return [...this._origFiles.keys()].sort() | ||
return [...this[_origFiles].keys()].sort() | ||
} | ||
@@ -242,7 +332,8 @@ | ||
dir(...dirs) { | ||
this._checkBeforeExec('dir'); | ||
for (let { dir, read = true, watch: watch$$1 = true, debounce = 10 } of dirs.filter(Boolean)) { | ||
dir = resolve(dir).replace(/\\/g, '/'); | ||
this[_checkBeforeExec]('dir'); | ||
for (let config of dirs.filter(Boolean)) { | ||
let { dir, read = true, enc = 'utf8', watch: watch$$1 = true, debounce = 10 } = config; | ||
dir = resolve(dir); | ||
let watcher = new Watcher(dir, watch$$1, debounce); | ||
this._watchers.push({ watcher, dir, read, watch: watch$$1 }); | ||
this[_watchers].push({ watcher, dir, read, enc, watch: watch$$1 }); | ||
} | ||
@@ -254,4 +345,4 @@ return this | ||
transform(...transforms) { | ||
this._checkBeforeExec('transform'); | ||
this._transforms.push(...transforms.filter(Boolean)); | ||
this[_checkBeforeExec]('transform'); | ||
this[_transforms].push(...transforms.filter(Boolean)); | ||
return this | ||
@@ -262,5 +353,5 @@ } | ||
generator(generators) { | ||
this._checkBeforeExec('generator'); | ||
this[_checkBeforeExec]('generator'); | ||
for (let [path, generator] of Object.entries(generators)) { | ||
if (path && generator) this._generators.set(path, generator); | ||
if (path && generator) this[_generators].set(path, generator); | ||
} | ||
@@ -273,8 +364,8 @@ return this | ||
async exec() { | ||
this._checkBeforeExec('exec'); | ||
this._status = false; | ||
this._processing = true; | ||
this[_checkBeforeExec]('exec'); | ||
this[_status] = false; | ||
this[_processing$1] = true; | ||
let promise = new Promise((res, rej) => { | ||
this._done.resolve = res; | ||
this._done.reject = rej; | ||
this[_done].resolve = res; | ||
this[_done].reject = rej; | ||
}); | ||
@@ -284,24 +375,26 @@ // init all Watcher instances; note that all files have pending transforms | ||
await Promise.all( | ||
this._watchers.map(async ({ watcher, dir, read, watch: watch$$1 }) => { | ||
this[_watchers].map(async ({ watcher, dir, read, enc, watch: watch$$1 }) => { | ||
if (watch$$1) { | ||
watcher.on('', ({ event, path, stat: stat$$1 }) => this._enqueue({ event, dir, path, stat: stat$$1, read })); | ||
watcher.on('', ({ event, path, stat: stat$$1 }) => { | ||
this[_enqueue$1]({ event, dir, path, stat: stat$$1, read, enc }); | ||
}); | ||
} | ||
for (let { path, stat: stat$$1 } of await watcher.init()) { | ||
files.push({ dir, path, stat: stat$$1, read }); | ||
this._origFiles.set(path, null); | ||
this._pending.add(path); | ||
files.push({ dir, path, stat: stat$$1, read, enc }); | ||
this[_origFiles].set(path, null); | ||
this[_pending].add(path); | ||
} | ||
}), | ||
); | ||
for (let path of this._generators.keys()) this._pending.add(path); | ||
for (let path of this[_generators].keys()) this[_pending].add(path); | ||
// process each physical file | ||
for (let { dir, path, stat: stat$$1, read } of files) { | ||
this._wait(this._processPhysicalFile(dir, path, stat$$1, read)); | ||
for (let { dir, path, stat: stat$$1, read, enc } of files) { | ||
this[_wait](this[_processPhysicalFile](dir, path, stat$$1, read, enc)); | ||
} | ||
// process each generated file | ||
for (let path of this._generators.keys()) this._wait(this._handleGeneratedFile(path)); | ||
for (let path of this[_generators].keys()) this[_wait](this[_handleGeneratedFile](path)); | ||
// wait and finish up | ||
await promise; | ||
this._status = true; | ||
this._processing = false; | ||
this[_status] = true; | ||
this[_processing$1] = false; | ||
} | ||
@@ -313,10 +406,10 @@ | ||
async file(file) { | ||
this._checkAfterExec('file'); | ||
this[_checkAfterExec]('file'); | ||
let { path } = file; | ||
if (!(file instanceof File)) file = Object.assign(new File(), file); | ||
await this._wait(this._transformFile(file)); | ||
this._files.set(path, file); | ||
await this[_wait](this[_transformFile](file)); | ||
this[_files$1].set(path, file); | ||
this.emit('file', { defiler: this, path, file }); | ||
this._found(path); | ||
this._processDependents(path); | ||
this[_found](path); | ||
this[_processDependents](path); | ||
} | ||
@@ -326,7 +419,7 @@ | ||
depend(dependent, path) { | ||
this._checkAfterExec('depend'); | ||
if (this._dependents.has(dependent)) { | ||
this._dependents.get(dependent).add(path); | ||
this[_checkAfterExec]('depend'); | ||
if (this[_dependents].has(dependent)) { | ||
this[_dependents].get(dependent).add(path); | ||
} else { | ||
this._dependents.set(dependent, new Set([path])); | ||
this[_dependents].set(dependent, new Set([path])); | ||
} | ||
@@ -337,15 +430,15 @@ } | ||
_checkBeforeExec(methodName) { | ||
if (this._status !== null) throw new Error(`Cannot call ${methodName} after calling exec`) | ||
[_checkBeforeExec](methodName) { | ||
if (this[_status] !== null) throw new Error(`Cannot call ${methodName} after calling exec`) | ||
} | ||
_checkAfterExec(methodName) { | ||
if (this._status === null) throw new Error(`Cannot call ${methodName} before calling exec`) | ||
[_checkAfterExec](methodName) { | ||
if (this[_status] === null) throw new Error(`Cannot call ${methodName} before calling exec`) | ||
} | ||
// add another promise that must resolve before the initial exec wave can finish | ||
_wait(promise) { | ||
if (!this._status) { | ||
this._done.count++; | ||
promise.then(() => --this._done.count || this._done.resolve(), this._done.reject); | ||
[_wait](promise) { | ||
if (!this[_status]) { | ||
this[_done].count++; | ||
promise.then(() => --this[_done].count || this[_done].resolve(), this[_done].reject); | ||
} | ||
@@ -356,46 +449,45 @@ return promise | ||
// add a Watcher event to the queue, and handle queued events | ||
async _enqueue(event) { | ||
this._queue.push(event); | ||
if (this._processing) return | ||
this._processing = true; | ||
while (this._queue.length) { | ||
let { event, dir, path, stat: stat$$1, read } = this._queue.shift(); | ||
async [_enqueue$1](event) { | ||
this[_queue$1].push(event); | ||
if (this[_processing$1]) return | ||
this[_processing$1] = true; | ||
while (this[_queue$1].length) { | ||
let { event, dir, path, stat: stat$$1, read, enc } = this[_queue$1].shift(); | ||
if (event === '+') { | ||
await this._processPhysicalFile(dir, path, stat$$1, read); | ||
await this[_processPhysicalFile](dir, path, stat$$1, read, enc); | ||
} else if (event === '-') { | ||
this._origFiles.delete(path); | ||
this._files.delete(path); | ||
this[_origFiles].delete(path); | ||
this[_files$1].delete(path); | ||
this.emit('deleted', { defiler: this, path }); | ||
this._processDependents(path); | ||
this[_processDependents](path); | ||
} | ||
} | ||
this._processing = false; | ||
this[_processing$1] = false; | ||
} | ||
// create a file object for a physical file and process it | ||
async _processPhysicalFile(dir, path, stat$$1, read) { | ||
let origFile = new File(path); | ||
origFile.stat = stat$$1; | ||
async [_processPhysicalFile](dir, path, stat$$1, read, enc) { | ||
let origFile = Object.assign(new File(), { path, stat: stat$$1, enc }); | ||
if (read) origFile.bytes = await readFile$1(dir + '/' + path); | ||
this._origFiles.set(path, origFile); | ||
this[_origFiles].set(path, origFile); | ||
this.emit('origFile', { defiler: this, file: origFile }); | ||
await this._processFile(origFile); | ||
await this[_processFile](origFile); | ||
} | ||
// transform a file, store it, and process dependents | ||
async _processFile(origFile) { | ||
async [_processFile](origFile) { | ||
let file = Object.assign(new File(), origFile); | ||
await this._transformFile(file); | ||
this._files.set(origFile.path, file); | ||
await this[_transformFile](file); | ||
this[_files$1].set(origFile.path, file); | ||
this.emit('file', { defiler: this, path: origFile.path, file }); | ||
this._found(origFile.path); | ||
this._processDependents(origFile.path); | ||
this[_found](origFile.path); | ||
this[_processDependents](origFile.path); | ||
} | ||
// perform all transforms on a file | ||
async _transformFile(file) { | ||
async [_transformFile](file) { | ||
let { path } = file; | ||
this._pending.add(path); | ||
this[_pending].add(path); | ||
try { | ||
for (let transform of this._transforms) { | ||
for (let transform of this[_transforms]) { | ||
await transform({ | ||
@@ -405,3 +497,3 @@ defiler: this, | ||
file, | ||
get: dependency => this._get(path, dependency), | ||
get: dependency => this[_get](path, dependency), | ||
}); | ||
@@ -412,7 +504,7 @@ } | ||
} | ||
this._pending.delete(path); | ||
if (!this._status && [...this._pending].every(path => this._waiting.get(path))) { | ||
this[_pending].delete(path); | ||
if (!this[_status] && [...this[_pending]].every(path => this[_waiting].get(path))) { | ||
// all pending files are currently waiting for one or more other files to exist | ||
// break deadlock: assume all files that have not appeared yet will never do so | ||
for (let path of this._available.keys()) if (!this._pending.has(path)) this._found(path); | ||
for (let path of this[_available].keys()) if (!this[_pending].has(path)) this[_found](path); | ||
} | ||
@@ -422,10 +514,10 @@ } | ||
// run a generator and transform and add the file | ||
async _handleGeneratedFile(path) { | ||
async [_handleGeneratedFile](path) { | ||
let file; | ||
try { | ||
file = new File(path); | ||
await this._generators.get(path)({ | ||
await this[_generators].get(path)({ | ||
defiler: this, | ||
file, | ||
get: dependency => this._get(path, dependency), | ||
get: dependency => this[_get](path, dependency), | ||
}); | ||
@@ -439,35 +531,35 @@ await this.file(file); | ||
// wait for a file to be available and mark another file as depending on it | ||
async _get(dependent, path) { | ||
if (Array.isArray(path)) return Promise.all(path.map(path => this._get(dependent, path))) | ||
async [_get](dependent, path) { | ||
if (Array.isArray(path)) return Promise.all(path.map(path => this[_get](dependent, path))) | ||
this.depend(dependent, path); | ||
if (!this._status && !this._files.has(path)) { | ||
this._waiting.set(dependent, (this._waiting.get(dependent) || 0) + 1); | ||
if (this._available.has(path)) { | ||
await this._available.get(path).promise; | ||
if (!this[_status] && !this[_files$1].has(path)) { | ||
this[_waiting].set(dependent, (this[_waiting].get(dependent) || 0) + 1); | ||
if (this[_available].has(path)) { | ||
await this[_available].get(path).promise; | ||
} else { | ||
let resolve$$1; | ||
let promise = new Promise(res => (resolve$$1 = res)); | ||
this._available.set(path, { promise, resolve: resolve$$1 }); | ||
this[_available].set(path, { promise, resolve: resolve$$1 }); | ||
await promise; | ||
} | ||
this._waiting.set(dependent, this._waiting.get(dependent) - 1); | ||
this[_waiting].set(dependent, this[_waiting].get(dependent) - 1); | ||
} | ||
return this._files.get(path) | ||
return this[_files$1].get(path) | ||
} | ||
// re-process all files that depend on a particular path | ||
_processDependents(path) { | ||
if (!this._status) return | ||
[_processDependents](path) { | ||
if (!this[_status]) return | ||
let dependents = new Set(); | ||
for (let [dependent, dependencies] of this._dependents.entries()) { | ||
for (let [dependent, dependencies] of this[_dependents].entries()) { | ||
if (dependencies.has(path)) { | ||
dependents.add(dependent); | ||
this._dependents.delete(dependent); | ||
this[_dependents].delete(dependent); | ||
} | ||
} | ||
for (let dependent of dependents) { | ||
if (this._generators.has(dependent)) { | ||
this._handleGeneratedFile(dependent); | ||
} else if (this._origFiles.has(dependent)) { | ||
this._processFile(this._origFiles.get(dependent)); | ||
if (this[_generators].has(dependent)) { | ||
this[_handleGeneratedFile](dependent); | ||
} else if (this[_origFiles].has(dependent)) { | ||
this[_processFile](this[_origFiles].get(dependent)); | ||
} | ||
@@ -478,6 +570,6 @@ } | ||
// mark a given awaited file as being found | ||
_found(path) { | ||
if (!this._status && this._available.has(path)) { | ||
this._available.get(path).resolve(); | ||
this._available.delete(path); | ||
[_found](path) { | ||
if (!this[_status] && this[_available].has(path)) { | ||
this[_available].get(path).resolve(); | ||
this[_available].delete(path); | ||
} | ||
@@ -484,0 +576,0 @@ } |
{ | ||
"name": "defiler", | ||
"version": "0.11.1", | ||
"version": "0.11.2", | ||
"description": "A small, strange building block", | ||
@@ -5,0 +5,0 @@ "keywords": ["build", "framework", "async", "watch"], |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
111531
1004