fs-jetpack
Advanced tools
Comparing version 0.5.3 to 0.6.0
@@ -0,1 +1,14 @@ | ||
0.6.0 (2015-03-30) | ||
------------------- | ||
* Lots of code refactoring | ||
* **(breaking change)** `dir()` no longer has `exists` option. | ||
* **(breaking change)** `file()` no longer has `exists` and `empty` options. | ||
* **(breaking change)** `safe` option for `write()` renamed to `atomic` (and uses new algorithm under the hood). | ||
* **(breaking change)** `safe` option for `read()` dropped (`atomic` while writing is enough). | ||
* **(breaking change)** In `copy()` options `only` and `allBut` have been replaced by option `matching`. | ||
* **(breaking change)** In `remove()` options `only` and `allBut` have been dropped (to do the same use `find()`, and then remove). | ||
* **(breaking change)** Default jsonIndent changed form 0 to 2. | ||
* `find()` method added. | ||
* More telling errors when `read()` failed while parsing JSON. | ||
0.5.3 (2015-01-06) | ||
@@ -2,0 +15,0 @@ ------------------- |
251
lib/copy.js
@@ -12,12 +12,51 @@ "use strict"; | ||
var inspector = require('./inspector'); | ||
var fileOps = require('./fileOps'); | ||
var fileOps = require('./file_ops'); | ||
var normalizeOptions = function (options) { | ||
var parseOptions = function (options, from) { | ||
var parsedOptions = {}; | ||
options = options || {}; | ||
if (typeof options.overwrite !== 'boolean') { | ||
options.overwrite = false; | ||
parsedOptions.overwrite = options.overwrite; | ||
if (options.matching) { | ||
parsedOptions.allowedToCopy = parseMatching(options.matching, from); | ||
} else { | ||
parsedOptions.allowedToCopy = function () { | ||
// Default behaviour - copy everything. | ||
return true; | ||
}; | ||
} | ||
return options; | ||
return parsedOptions; | ||
}; | ||
var parseMatching = function (matching, fromPath) { | ||
if (typeof matching === 'string') { | ||
matching = [matching]; | ||
} | ||
var patterns = matching.map(function (pattern) { | ||
// Turn relative matchers into absolute | ||
// (change "./a/b" to "/path_to_copy/a/b") | ||
if (/^\.\//.test(pattern)) { | ||
return pattern.replace(/^\./, fromPath); | ||
} | ||
return pattern; | ||
}); | ||
return matcher.create(patterns); | ||
}; | ||
var generateNoSourceError = function (path) { | ||
var err = new Error("Path to copy doesn't exist " + path); | ||
err.code = 'ENOENT'; | ||
return err; | ||
}; | ||
var generateDestinationExistsError = function (path) { | ||
var err = new Error('Destination path already exists ' + path); | ||
err.code = 'EEXIST'; | ||
return err; | ||
}; | ||
//--------------------------------------------------------- | ||
@@ -27,14 +66,9 @@ // Sync | ||
// Soubroutine which copies a file or tree of files. | ||
var copySync = function (from, to) { | ||
var stat = fs.statSync(from); | ||
if (stat.isDirectory()) { | ||
mkdirp.sync(to, { mode: mode.normalizeFileMode(stat.mode) }); | ||
var list = fs.readdirSync(from); | ||
list.forEach(function (filename) { | ||
copySync(pathUtil.resolve(from, filename), pathUtil.resolve(to, filename)); | ||
}); | ||
} else { | ||
var data = fs.readFileSync(from); | ||
fileOps.write(to, data, { mode: mode.normalizeFileMode(stat.mode) }); | ||
var copySync = function (inspectData, to) { | ||
var mod = mode.normalizeFileMode(inspectData.mode); | ||
if (inspectData.type === 'dir') { | ||
mkdirp.sync(to, { mode: mod }); | ||
} else if (inspectData.type === 'file') { | ||
var data = fs.readFileSync(inspectData.absolutePath); | ||
fileOps.write(to, data, { mode: mod }); | ||
} | ||
@@ -44,38 +78,21 @@ }; | ||
var sync = function (from, to, options) { | ||
options = normalizeOptions(options); | ||
options = parseOptions(options, from); | ||
if (!exists.sync(from)) { | ||
var err = new Error("Path to copy '" + from + "' doesn't exist."); | ||
err.code = 'ENOENT'; | ||
throw err; | ||
throw generateNoSourceError(from); | ||
} | ||
if (exists.sync(to) && options.overwrite === false) { | ||
var err = new Error('Destination path already exists.'); | ||
err.code = 'EEXIST'; | ||
throw err; | ||
if (exists.sync(to) && !options.overwrite) { | ||
throw generateDestinationExistsError(to); | ||
} | ||
if (options.only === undefined && options.allBut === undefined) { | ||
// No special options set, we can just copy the whole thing. | ||
copySync(from, to); | ||
return; | ||
var walker = inspector.createTreeWalkerSync(from); | ||
while (walker.hasNext()) { | ||
var inspectData = walker.getNext(); | ||
var destPath = pathUtil.join(to, inspectData.relativePath); | ||
if (options.allowedToCopy(inspectData.absolutePath)) { | ||
copySync(inspectData, destPath); | ||
} | ||
} | ||
// Figure out what to copy, and what not to copy. | ||
var tree = inspector.tree(from); | ||
var pathsToCopy; | ||
if (options.only) { | ||
pathsToCopy = matcher.treeToWhitelistPaths(from, tree, options.only); | ||
} else if (options.allBut) { | ||
pathsToCopy = matcher.treeToBlacklistPaths(from, tree, options.allBut); | ||
} | ||
// Remove every path which is in array separately. | ||
pathsToCopy.forEach(function (path) { | ||
var internalPart = path.substring(from.length); | ||
copySync(path, pathUtil.join(to, internalPart)); | ||
}); | ||
}; | ||
@@ -87,38 +104,15 @@ | ||
var qStat = Q.denodeify(fs.stat); | ||
var qReaddir = Q.denodeify(fs.readdir); | ||
var qReadFile = Q.denodeify(fs.readFile); | ||
var qMkdirp = Q.denodeify(mkdirp); | ||
var promisedReadFile = Q.denodeify(fs.readFile); | ||
var promisedMkdirp = Q.denodeify(mkdirp); | ||
// Soubroutine which copies a file or tree of files. | ||
var copyAsync = function (from, to) { | ||
var deferred = Q.defer(); | ||
qStat(from).then(function (stat) { | ||
if (stat.isDirectory()) { | ||
// Prepare destination directory | ||
qMkdirp(to, { mode: mode.normalizeFileMode(stat.mode) }) | ||
.then(function () { | ||
// Read all things to copy... | ||
return qReaddir(from); | ||
}) | ||
.then(function (list) { | ||
// ...and just copy them. | ||
var promises = list.map(function (filename) { | ||
return copyAsync(pathUtil.resolve(from, filename), pathUtil.resolve(to, filename)); | ||
}); | ||
return Q.all(promises); | ||
}) | ||
.then(deferred.resolve, deferred.reject); | ||
} else { | ||
// Copy single file | ||
qReadFile(from) | ||
.then(function (data) { | ||
return fileOps.writeAsync(to, data, { mode: mode.normalizeFileMode(stat.mode) }); | ||
}) | ||
.then(deferred.resolve, deferred.reject); | ||
} | ||
}, deferred.reject); | ||
return deferred.promise; | ||
var copyAsync = function (inspectData, to) { | ||
var mod = mode.normalizeFileMode(inspectData.mode); | ||
if (inspectData.type === 'dir') { | ||
return promisedMkdirp(to, { mode: mod }); | ||
} else if (inspectData.type === 'file') { | ||
return promisedReadFile(inspectData.absolutePath) | ||
.then(function (data) { | ||
return fileOps.writeAsync(to, data, { mode: mod }); | ||
}); | ||
} | ||
}; | ||
@@ -128,62 +122,45 @@ | ||
var deferred = Q.defer(); | ||
options = normalizeOptions(options); | ||
options = parseOptions(options, from); | ||
// Checks before copying | ||
exists.async(from) | ||
.then(function (ex) { | ||
if (!ex) { | ||
var err = new Error("Path to copy '" + from + "' doesn't exist."); | ||
err.code = 'ENOENT'; | ||
deferred.reject(err); | ||
.then(function (srcPathExists) { | ||
if (!srcPathExists) { | ||
throw generateNoSourceError(from); | ||
} else { | ||
exists.async(to) | ||
.then(function (ex) { | ||
if (ex && options.overwrite === false) { | ||
var err = new Error('Destination path already exists.'); | ||
err.code = 'EEXIST'; | ||
deferred.reject(err); | ||
} else { | ||
startCopying(); | ||
} | ||
}); | ||
return exists.async(to) | ||
} | ||
}); | ||
var startCopying = function () { | ||
if (options.only === undefined && options.allBut === undefined) { | ||
// No special options set, we can just copy the whole thing. | ||
copyAsync(from, to).then(done, deferred.reject); | ||
}) | ||
.then(function (destPathExists) { | ||
if (destPathExists && !options.overwrite) { | ||
throw generateDestinationExistsError(to); | ||
} else { | ||
figureOutWhatToCopy(); | ||
startCopying(); | ||
} | ||
}; | ||
var figureOutWhatToCopy = function () { | ||
// Figure out what to copy, and what not to copy. | ||
inspector.treeAsync(from) | ||
.then(function (tree) { | ||
var pathsToCopy; | ||
if (options.only) { | ||
pathsToCopy = matcher.treeToWhitelistPaths(from, tree, options.only); | ||
} else if (options.allBut) { | ||
pathsToCopy = matcher.treeToBlacklistPaths(from, tree, options.allBut); | ||
}) | ||
.catch(deferred.reject); | ||
var startCopying = function () { | ||
var copyOne = function (walker) { | ||
if (walker.hasNext()) { | ||
var inspectData = walker.getNext(); | ||
var destPath = pathUtil.join(to, inspectData.relativePath); | ||
if (options.allowedToCopy(inspectData.absolutePath)) { | ||
copyAsync(inspectData, destPath) | ||
.then(function () { | ||
copyOne(walker); | ||
}); | ||
} else { | ||
copyOne(walker); | ||
} | ||
} else { | ||
deferred.resolve(); | ||
} | ||
// Remove every path which is in array separately. | ||
var promises = pathsToCopy.map(function (path) { | ||
var internalPart = path.substring(from.length); | ||
return copyAsync(path, pathUtil.join(to, internalPart)); | ||
}); | ||
return Q.all(promises); | ||
}) | ||
.then(done, deferred.reject); | ||
}; | ||
inspector.createTreeWalkerAsync(from).then(copyOne); | ||
}; | ||
var done = function () { | ||
// Function to make sure we are returning undefined here. | ||
deferred.resolve(); | ||
}; | ||
return deferred.promise; | ||
@@ -190,0 +167,0 @@ }; |
234
lib/dir.js
@@ -15,5 +15,2 @@ "use strict"; | ||
} | ||
if (typeof criteria.exists !== 'boolean') { | ||
criteria.exists = true; | ||
} | ||
if (typeof criteria.empty !== 'boolean') { | ||
@@ -33,9 +30,9 @@ criteria.empty = false; | ||
var sync = function (path, criteria) { | ||
criteria = getCriteriaDefaults(criteria); | ||
try { | ||
var stat = fs.statSync(path); | ||
} catch (err) { | ||
// detection if path exists | ||
// Detection if path already exists | ||
if (err.code !== 'ENOENT') { | ||
@@ -45,27 +42,16 @@ throw err; | ||
} | ||
// check if for sure given path is directory, because this | ||
// is the only thing which interests us here | ||
if (stat && !stat.isDirectory()) { | ||
// this is not a directory, so remove it | ||
rimraf.sync(path); | ||
// clear stat variable to indicate now nothing is there | ||
if (stat && stat.isFile()) { | ||
// This is file, but should be directory. | ||
fs.unlinkSync(path); | ||
// Clear stat variable to indicate now nothing is there | ||
stat = undefined; | ||
} | ||
if (stat) { | ||
// directory already exists | ||
if (criteria.exists === false) { | ||
// path exists, and we want for it not to exist | ||
rimraf.sync(path); | ||
// no need to check more criteria, exists=false is ultimate one | ||
return; | ||
} | ||
// ensure existing directory matches criteria | ||
if (criteria.empty === true) { | ||
// delete everything inside this directory | ||
// Ensure existing directory matches criteria | ||
if (criteria.empty) { | ||
// Delete everything inside this directory | ||
var list = fs.readdirSync(path); | ||
@@ -76,17 +62,12 @@ list.forEach(function (filename) { | ||
} | ||
var mode = modeUtil.normalizeFileMode(stat.mode); | ||
if (criteria.mode !== undefined && criteria.mode !== mode) { | ||
// mode is different than specified in criteria, fix that | ||
// Mode is different than specified in criteria, fix that. | ||
fs.chmodSync(path, criteria.mode); | ||
} | ||
} else { | ||
// directory doesn't exist | ||
if (criteria.exists === true) { | ||
// create this directory | ||
mkdirp.sync(path, { mode: criteria.mode }); | ||
} | ||
// Directory doesn't exist now. Create it. | ||
mkdirp.sync(path, { mode: criteria.mode }); | ||
} | ||
@@ -99,22 +80,30 @@ }; | ||
var qStat = Q.denodeify(fs.stat); | ||
var qChmod = Q.denodeify(fs.chmod); | ||
var qReaddir = Q.denodeify(fs.readdir); | ||
var qRimraf = Q.denodeify(rimraf); | ||
var qMkdirp = Q.denodeify(mkdirp); | ||
var promisedStat = Q.denodeify(fs.stat); | ||
var promisedChmod = Q.denodeify(fs.chmod); | ||
var promisedReaddir = Q.denodeify(fs.readdir); | ||
var promisedRimraf = Q.denodeify(rimraf); | ||
var promisedMkdirp = Q.denodeify(mkdirp); | ||
// delete all files and directores inside a directory, | ||
// but leave the main directory intact | ||
// Delete all files and directores inside given directory | ||
var empty = function (path) { | ||
var deferred = Q.defer(); | ||
qReaddir(path) | ||
promisedReaddir(path) | ||
.then(function (list) { | ||
var promises = list.map(function (filename) { | ||
return qRimraf(pathUtil.resolve(path, filename)); | ||
}); | ||
return Q.all(promises); | ||
var doOne = function (index) { | ||
if (index === list.length) { | ||
deferred.resolve(); | ||
} else { | ||
var subPath = pathUtil.resolve(path, list[index]); | ||
promisedRimraf(subPath).then(function () { | ||
doOne(index + 1); | ||
}); | ||
} | ||
}; | ||
doOne(0); | ||
}) | ||
.then(deferred.resolve, deferred.reject); | ||
.catch(deferred.reject); | ||
return deferred.promise; | ||
@@ -125,79 +114,86 @@ }; | ||
var deferred = Q.defer(); | ||
criteria = getCriteriaDefaults(criteria); | ||
qStat(path) | ||
.then(function (stat) { | ||
// check if for sure given path is directory, because this | ||
// is the only thing which interests us here | ||
if (!stat.isDirectory()) { | ||
// this is not a directory, so remove it | ||
qRimraf(path) | ||
.then(function () { | ||
// clear stat variable to indicate now nothing is there | ||
ensureCriteria(undefined); | ||
}, deferred.reject); | ||
} else { | ||
ensureCriteria(stat); | ||
} | ||
}, function (err) { | ||
// detection if path exists | ||
if (err.code !== 'ENOENT') { | ||
// this is other error that nonexistent path, so end here | ||
deferred.reject(err); | ||
} else { | ||
ensureCriteria(undefined); | ||
} | ||
}); | ||
var ensureCriteria = function (stat) { | ||
if (stat) { | ||
// if stat is defined it means directory exists | ||
if (criteria.exists === false) { | ||
// path exists, and we want for it not to exist | ||
qRimraf(path).then(deferred.resolve, deferred.reject); | ||
// no need to check more criteria, exists=false is ultimate one | ||
return; | ||
var checkWhatWeHaveNow = function () { | ||
var deferred = Q.defer(); | ||
promisedStat(path) | ||
.then(function (stat) { | ||
if (stat.isFile()) { | ||
// This is not a directory, and should be. | ||
promisedRimraf(path) | ||
.then(function () { | ||
// We just deleted that path, so can't | ||
// pass stat further down. | ||
deferred.resolve(undefined); | ||
}) | ||
.catch(deferred.reject); | ||
} else { | ||
deferred.resolve(stat); | ||
} | ||
// ensure existing directory matches criteria | ||
}) | ||
.catch(function (err) { | ||
if (err.code === 'ENOENT') { | ||
// Path doesn't exist | ||
deferred.resolve(undefined); | ||
} else { | ||
// This is other error that nonexistent path, so end here. | ||
deferred.reject(err); | ||
} | ||
}); | ||
return deferred.promise; | ||
}; | ||
var checkWhatShouldBe = function (stat) { | ||
var deferred = Q.defer(); | ||
var needToCheckMoreCriteria = function () { | ||
var checkEmptiness = function () { | ||
if (criteria.empty) { | ||
// Delete everything inside this directory | ||
empty(path) | ||
.then(deferred.resolve, deferred.reject); | ||
} else { | ||
// Everything done! | ||
deferred.resolve(); | ||
} | ||
}; | ||
var checkMode = function () { | ||
var mode = modeUtil.normalizeFileMode(stat.mode); | ||
if (criteria.mode !== undefined && criteria.mode !== mode) { | ||
// mode is different than specified in criteria, fix that | ||
qChmod(path, criteria.mode) | ||
.then(deferred.resolve, deferred.reject); | ||
// Mode is different than specified in criteria, fix that | ||
promisedChmod(path, criteria.mode) | ||
.then(checkEmptiness, deferred.reject); | ||
} else { | ||
// OMG, done! | ||
deferred.resolve(); | ||
checkEmptiness(); | ||
} | ||
}; | ||
if (criteria.empty === true) { | ||
// delete everything inside this directory | ||
empty(path) | ||
.then(checkMode, deferred.reject); | ||
} else { | ||
checkMode(); | ||
} | ||
} else { | ||
// we know directory doesn't exist | ||
if (criteria.exists === true) { | ||
// create this directory | ||
qMkdirp(path, { mode: criteria.mode }) | ||
checkMode(); | ||
}; | ||
var checkExistence = function () { | ||
if (!stat) { | ||
promisedMkdirp(path, { mode: criteria.mode }) | ||
.then(deferred.resolve, deferred.reject); | ||
} else { | ||
deferred.resolve(); | ||
// Directory exists, but we still don't | ||
// know if it matches all criteria. | ||
needToCheckMoreCriteria(); | ||
} | ||
} | ||
}; | ||
checkExistence(); | ||
return deferred.promise; | ||
}; | ||
checkWhatWeHaveNow() | ||
.then(checkWhatShouldBe) | ||
.then(deferred.resolve, deferred.reject); | ||
return deferred.promise; | ||
@@ -211,2 +207,2 @@ }; | ||
module.exports.sync = sync; | ||
module.exports.async = async; | ||
module.exports.async = async; |
@@ -62,2 +62,2 @@ "use strict"; | ||
module.exports.sync = sync; | ||
module.exports.async = async; | ||
module.exports.async = async; |
208
lib/file.js
@@ -9,3 +9,3 @@ "use strict"; | ||
var modeUtils = require('./utils/mode'); | ||
var fileOps = require('./fileOps'); | ||
var fileOps = require('./file_ops'); | ||
@@ -16,8 +16,2 @@ function getCriteriaDefaults(criteria) { | ||
} | ||
if (typeof criteria.exists !== 'boolean') { | ||
criteria.exists = true; | ||
} | ||
if (typeof criteria.empty !== 'boolean') { | ||
criteria.empty = false; | ||
} | ||
if (criteria.mode !== undefined) { | ||
@@ -34,5 +28,5 @@ criteria.mode = modeUtils.normalizeFileMode(criteria.mode); | ||
var sync = function (path, criteria) { | ||
criteria = getCriteriaDefaults(criteria); | ||
try { | ||
@@ -46,3 +40,3 @@ var stat = fs.statSync(path); | ||
} | ||
if (stat && !stat.isFile()) { | ||
@@ -54,18 +48,5 @@ // This have to be file, so if is directory must be removed. | ||
} | ||
if (stat) { | ||
// Ensure existing file | ||
if (criteria.exists === false) { | ||
// Path exists and we want for it not to, so delete and end there. | ||
fs.unlinkSync(path); | ||
return; | ||
} | ||
if (criteria.empty === true) { | ||
// Use truncate because it doesn't change mode of file. | ||
fs.truncateSync(path, 0); | ||
} | ||
// Ensure file mode | ||
@@ -76,11 +57,23 @@ var mode = modeUtils.normalizeFileMode(stat.mode); | ||
} | ||
// Ensure file content | ||
if (criteria.empty === false && criteria.content !== undefined) { | ||
fileOps.write(path, criteria.content, { mode: mode, jsonIndent: criteria.jsonIndent }); | ||
if (criteria.content !== undefined) { | ||
fileOps.write(path, criteria.content, { | ||
mode: mode, | ||
jsonIndent: criteria.jsonIndent | ||
}); | ||
} | ||
} else if (criteria.exists === true) { | ||
// Create new file. | ||
fileOps.write(path, criteria.content || '', { mode: criteria.mode, jsonIndent: criteria.jsonIndent }); | ||
} else { | ||
// File doesn't exist. Create it. | ||
var content = ''; | ||
if (criteria.content !== undefined) { | ||
content = criteria.content; | ||
} | ||
fileOps.write(path, content, { | ||
mode: criteria.mode, | ||
jsonIndent: criteria.jsonIndent | ||
}); | ||
} | ||
@@ -93,74 +86,79 @@ }; | ||
var qUnlink = Q.denodeify(fs.unlink); | ||
var qStat = Q.denodeify(fs.stat); | ||
var qTruncate = Q.denodeify(fs.truncate); | ||
var qChmod = Q.denodeify(fs.chmod); | ||
var qRimraf = Q.denodeify(rimraf); | ||
var promisedStat = Q.denodeify(fs.stat); | ||
var promisedChmod = Q.denodeify(fs.chmod); | ||
var promisedRimraf = Q.denodeify(rimraf); | ||
var async = function (path, criteria) { | ||
var deferred = Q.defer(); | ||
criteria = getCriteriaDefaults(criteria); | ||
qStat(path) | ||
.then(function (stat) { | ||
// Check if for sure given path is a file, because this | ||
// is the only thing which interests us here. | ||
if (!stat.isFile()) { | ||
// This is not a file, so remove it. | ||
qRimraf(path) | ||
.then(function () { | ||
// clear stat variable to indicate now nothing is there | ||
ensureCriteria(undefined); | ||
}, deferred.reject); | ||
var checkWhatWeHaveNow = function () { | ||
var deferred = Q.defer(); | ||
promisedStat(path) | ||
.then(function (stat) { | ||
if (stat.isDirectory()) { | ||
// This is not a file, so remove it. | ||
promisedRimraf(path) | ||
.then(function () { | ||
// Clear stat variable to indicate now nothing is there | ||
deferred.resolve(undefined); | ||
}) | ||
.catch(deferred.reject); | ||
} else { | ||
deferred.resolve(stat); | ||
} | ||
}) | ||
.catch(function (err) { | ||
if (err.code === 'ENOENT') { | ||
// Path doesn't exist. | ||
deferred.resolve(undefined); | ||
} else { | ||
// This is other error. Must end here. | ||
deferred.reject(err); | ||
} | ||
}); | ||
return deferred.promise; | ||
}; | ||
var checkWhatShouldBe = function (stat) { | ||
var deferred = Q.defer(); | ||
if (!stat) { | ||
// Path doesn't exist now. Put file there. | ||
var content = ''; | ||
if (criteria.content !== undefined) { | ||
content = criteria.content; | ||
} | ||
fileOps.writeAsync(path, content, { | ||
mode: criteria.mode, | ||
jsonIndent: criteria.jsonIndent | ||
}) | ||
.then(deferred.resolve, deferred.reject); | ||
} else { | ||
ensureCriteria(stat); | ||
} | ||
}, function (err) { | ||
// Detection if path exists. | ||
if (err.code !== 'ENOENT') { | ||
// This is other error that nonexistent path, so end here. | ||
deferred.reject(err); | ||
} else { | ||
ensureCriteria(undefined); | ||
} | ||
}); | ||
var ensureCriteria = function (stat) { | ||
if (stat) { | ||
// Ensure existing file. | ||
if (criteria.exists === false) { | ||
// Path exists and we want for it not to, so delete and end there. | ||
qUnlink(path).then(deferred.resolve, deferred.reject); | ||
return; | ||
} | ||
// File already exists. Must do more checking. | ||
var mode = modeUtils.normalizeFileMode(stat.mode); | ||
var ensureEmptiness = function () { | ||
if (criteria.empty === true) { | ||
// Use truncate because it doesn't change mode of the file. | ||
qTruncate(path, 0) | ||
.then(ensureMode, deferred.reject); | ||
var checkContent = function () { | ||
if (criteria.content !== undefined) { | ||
fileOps.writeAsync(path, criteria.content, { | ||
mode: mode, | ||
jsonIndent: criteria.jsonIndent | ||
}) | ||
.then(deferred.resolve, deferred.reject); | ||
} else { | ||
ensureMode(); | ||
checkMode(); | ||
} | ||
}; | ||
var ensureMode = function () { | ||
var checkMode = function () { | ||
if (criteria.mode !== undefined && criteria.mode !== mode) { | ||
qChmod(path, criteria.mode) | ||
.then(ensureContent, deferred.reject); | ||
} else { | ||
ensureContent(); | ||
} | ||
}; | ||
var ensureContent = function () { | ||
// This is the last step in this branch! End after that. | ||
if (criteria.empty === false && criteria.content !== undefined) { | ||
fileOps.writeAsync(path, criteria.content, { mode: mode, jsonIndent: criteria.jsonIndent }) | ||
promisedChmod(path, criteria.mode) | ||
.then(deferred.resolve, deferred.reject); | ||
@@ -171,15 +169,13 @@ } else { | ||
}; | ||
// this starts the sequence of asynchronous functions declared above | ||
ensureEmptiness(); | ||
} else if (criteria.exists === true) { | ||
fileOps.writeAsync(path, criteria.content || '', { mode: criteria.mode, jsonIndent: criteria.jsonIndent }) | ||
.then(deferred.resolve, deferred.reject); | ||
} else { | ||
// File doesn't exist and this is the desired condition, end here. | ||
deferred.resolve(); | ||
checkContent(); | ||
} | ||
return deferred.promise; | ||
}; | ||
checkWhatWeHaveNow() | ||
.then(checkWhatShouldBe) | ||
.then(deferred.resolve, deferred.reject); | ||
return deferred.promise; | ||
@@ -193,2 +189,2 @@ }; | ||
module.exports.sync = sync; | ||
module.exports.async = async; | ||
module.exports.async = async; |
@@ -10,5 +10,5 @@ "use strict"; | ||
var obj = {}; | ||
obj.name = pathUtil.basename(path); | ||
if (stat.isFile()) { | ||
@@ -22,11 +22,11 @@ obj.type = 'file'; | ||
} | ||
if (computedChecksum) { | ||
obj[computedChecksum.type] = computedChecksum.value; | ||
} | ||
if (options.mode) { | ||
obj.mode = stat.mode; | ||
} | ||
if (options.times) { | ||
@@ -37,14 +37,16 @@ obj.accessTime = stat.atime; | ||
} | ||
if (options.absolutePath) { | ||
obj.absolutePath = path; | ||
} | ||
return obj; | ||
}; | ||
// Creates checksum of a directory by using | ||
// checksums and names of all its children inside. | ||
var checksumOfDir = function (inspectList, algo) { | ||
if (inspectList.length === 0) { | ||
return null; | ||
} | ||
var hash = crypto.createHash(algo); | ||
inspectList.forEach(function (inspectObj) { | ||
hash.update(inspectObj.name + (inspectObj[algo] || '')); | ||
hash.update(inspectObj.name + inspectObj[algo]); | ||
}); | ||
@@ -54,2 +56,18 @@ return hash.digest('hex'); | ||
// Flattens tree structure to one list of inspectObjects. | ||
var flattenTree = function (tree) { | ||
var treeAsList = []; | ||
var crawl = function (inspectObj) { | ||
treeAsList.push(inspectObj); | ||
if (inspectObj.children) { | ||
inspectObj.children.forEach(crawl); | ||
} | ||
}; | ||
crawl(tree); | ||
return treeAsList; | ||
}; | ||
//--------------------------------------------------------- | ||
@@ -60,9 +78,2 @@ // Sync | ||
var fileChecksum = function (path, stat, algo) { | ||
if (stat.size === 0) { | ||
return { | ||
type: algo, | ||
value: null | ||
}; | ||
} | ||
var hash = crypto.createHash(algo); | ||
@@ -77,5 +88,5 @@ var data = fs.readFileSync(path); | ||
var inspect = function (path, options) { | ||
var inspectSync = function (path, options) { | ||
options = options || {}; | ||
try { | ||
@@ -92,11 +103,11 @@ var stat = fs.statSync(path); | ||
} | ||
if (stat.isFile() && options.checksum) { | ||
var computedChecksum = fileChecksum(path, stat, options.checksum); | ||
} | ||
return createInspectObj(path, options, stat, computedChecksum); | ||
}; | ||
var list = function (path, useInspect) { | ||
var listSync = function (path, useInspect) { | ||
try { | ||
@@ -106,4 +117,5 @@ var simpleList = fs.readdirSync(path); | ||
// Detection if path exists | ||
if (err.code === 'ENOENT') { | ||
// Doesn't exist. Return null instead of throwing. | ||
if (err.code === 'ENOENT' || err.code === 'ENOTDIR') { | ||
// Doesn't exist or is a file, not directory. | ||
// Return null instead of throwing. | ||
return null; | ||
@@ -114,7 +126,7 @@ } else { | ||
} | ||
if (!useInspect) { | ||
return simpleList; | ||
} | ||
var inspectConfig = {}; | ||
@@ -126,29 +138,65 @@ if (typeof useInspect === 'object') { | ||
var inspectedList = simpleList.map(function (filename) { | ||
return inspect(pathUtil.join(path, filename), inspectConfig); | ||
return inspectSync(pathUtil.join(path, filename), inspectConfig); | ||
}); | ||
return inspectedList; | ||
}; | ||
var tree = function (path, options) { | ||
var treeSync = function (path, options) { | ||
options = options || {}; | ||
var treeBranch = inspect(path, options); | ||
if (treeBranch && treeBranch.type === 'dir') { | ||
treeBranch.size = 0; | ||
treeBranch.children = list(path).map(function (filename) { | ||
var treeSubBranch = tree(pathUtil.join(path, filename), options); | ||
treeBranch.size += treeSubBranch.size; | ||
return treeSubBranch; | ||
}); | ||
if (options.checksum) { | ||
treeBranch[options.checksum] = checksumOfDir(treeBranch.children, options.checksum); | ||
return crawlTreeSync(path, options, null); | ||
}; | ||
var crawlTreeSync = function (path, options, parent) { | ||
var treeBranch = inspectSync(path, options); | ||
if (treeBranch) { | ||
if (options.relativePath) { | ||
if (!parent) { | ||
treeBranch.relativePath = '.'; | ||
} else { | ||
treeBranch.relativePath = parent.relativePath + '/' + pathUtil.basename(path); | ||
} | ||
} | ||
if (treeBranch.type === 'dir') { | ||
treeBranch.size = 0; | ||
// Process all children | ||
treeBranch.children = listSync(path).map(function (filename) { | ||
// Go one level deeper with crawling | ||
var treeSubBranch = crawlTreeSync(pathUtil.join(path, filename), options, treeBranch); | ||
// Add together all childrens' size | ||
treeBranch.size += treeSubBranch.size; | ||
return treeSubBranch; | ||
}); | ||
if (options.checksum) { | ||
treeBranch[options.checksum] = checksumOfDir(treeBranch.children, options.checksum); | ||
} | ||
} | ||
} | ||
return treeBranch; | ||
}; | ||
var createTreeWalkerSync = function (startPath) { | ||
var allFiles = flattenTree(treeSync(startPath, { | ||
absolutePath: true, | ||
relativePath: true, | ||
mode: true | ||
})); | ||
return { | ||
hasNext: function () { | ||
return allFiles.length > 0; | ||
}, | ||
getNext: function () { | ||
return allFiles.shift(); | ||
} | ||
} | ||
}; | ||
//--------------------------------------------------------- | ||
@@ -158,28 +206,21 @@ // Async | ||
var qStat = Q.denodeify(fs.stat); | ||
var qReaddir = Q.denodeify(fs.readdir); | ||
var promisedStat = Q.denodeify(fs.stat); | ||
var promisedReaddir = Q.denodeify(fs.readdir); | ||
var fileChecksumAsync = function (path, stat, algo) { | ||
var deferred = Q.defer(); | ||
if (stat.size === 0) { | ||
var hash = crypto.createHash(algo); | ||
var s = fs.createReadStream(path); | ||
s.on('data', function(data) { | ||
hash.update(data); | ||
}); | ||
s.on('end', function() { | ||
deferred.resolve({ | ||
type: algo, | ||
value: null | ||
value: hash.digest('hex') | ||
}); | ||
} else { | ||
var hash = crypto.createHash(algo); | ||
var s = fs.createReadStream(path); | ||
s.on('data', function(data) { | ||
hash.update(data); | ||
}); | ||
s.on('end', function() { | ||
deferred.resolve({ | ||
type: algo, | ||
value: hash.digest('hex') | ||
}); | ||
}); | ||
s.on('error', deferred.reject); | ||
} | ||
}); | ||
s.on('error', deferred.reject); | ||
return deferred.promise; | ||
@@ -190,20 +231,20 @@ }; | ||
var deferred = Q.defer(); | ||
options = options || {}; | ||
qStat(path) | ||
var done = function (stat, computedChecksum) { | ||
deferred.resolve(createInspectObj(path, options, stat, computedChecksum)); | ||
}; | ||
promisedStat(path) | ||
.then(function (stat) { | ||
if (stat.isFile() && options.checksum) { | ||
// Have to count checksum | ||
fileChecksumAsync(path, stat, options.checksum) | ||
.then(function (computedChecksum) { | ||
deferred.resolve(createInspectObj(path, options, stat, computedChecksum)); | ||
}, deferred.reject); | ||
fileChecksumAsync(path, stat, options.checksum).then(function (checksum) { | ||
done(stat, checksum); | ||
}); | ||
} else { | ||
// Finish now | ||
deferred.resolve(createInspectObj(path, options, stat, null)); | ||
done(stat, null); | ||
} | ||
}, function (err) { | ||
}) | ||
.catch(function (err) { | ||
// Detection if path exists | ||
@@ -217,3 +258,3 @@ if (err.code === 'ENOENT') { | ||
}); | ||
return deferred.promise; | ||
@@ -224,43 +265,17 @@ }; | ||
var deferred = Q.defer(); | ||
qReaddir(path) | ||
promisedReaddir(path) | ||
.then(function (simpleList) { | ||
if (!useInspect) { | ||
// we are done! | ||
// Only list of paths is required. We are done. | ||
deferred.resolve(simpleList); | ||
} else { | ||
var inspectConfig = {}; | ||
if (typeof useInspect === 'object') { | ||
// useInspec contains config | ||
inspectConfig = useInspect; | ||
} | ||
// have to call inspect for every item in array | ||
var inspectedList = simpleList.concat(); | ||
var countDone = 0; | ||
simpleList.forEach(function (filename) { | ||
inspectAsync(pathUtil.join(path, filename), inspectConfig) | ||
.then(function (inspectObj) { | ||
var index = inspectedList.indexOf(filename); | ||
inspectedList[index] = inspectObj; | ||
countDone += 1; | ||
if (countDone === simpleList.length) { | ||
// we are done! | ||
deferred.resolve(inspectedList); | ||
} | ||
}, deferred.reject); | ||
}); | ||
turnSimpleListIntoInspectObjectsList(simpleList); | ||
} | ||
}, function (err) { | ||
}) | ||
.catch(function (err) { | ||
// Detection if path exists | ||
if (err.code === 'ENOENT') { | ||
// Doesn't exist. Return null instead of throwing. | ||
if (err.code === 'ENOENT' || err.code === 'ENOTDIR') { | ||
// Doesn't exist or is a file, not directory. | ||
// Return null instead of throwing. | ||
deferred.resolve(null); | ||
@@ -271,3 +286,27 @@ } else { | ||
}); | ||
var turnSimpleListIntoInspectObjectsList = function (pathsList) { | ||
var inspectConfig = {}; | ||
if (typeof useInspect === 'object') { | ||
// useInspec contains config | ||
inspectConfig = useInspect; | ||
} | ||
var doOne = function (index) { | ||
if (index === pathsList.length) { | ||
deferred.resolve(pathsList); | ||
} else { | ||
var itemPath = pathUtil.join(path, pathsList[index]); | ||
inspectAsync(itemPath, inspectConfig) | ||
.then(function (inspectObj) { | ||
pathsList[index] = inspectObj; | ||
doOne(index + 1); | ||
}) | ||
.catch(deferred.reject); | ||
} | ||
}; | ||
doOne(0); | ||
}; | ||
return deferred.promise; | ||
@@ -277,56 +316,96 @@ }; | ||
var treeAsync = function (path, options) { | ||
options = options || {}; | ||
return crawlTreeAsync(path, options); | ||
}; | ||
var crawlTreeAsync = function (path, options, parent) { | ||
var deferred = Q.defer(); | ||
options = options || {}; | ||
inspectAsync(path, options) | ||
.then(function (treeBranch) { | ||
if (treeBranch && treeBranch.type === 'dir') { | ||
// traverse recursively all levels down the directory structure | ||
listAsync(path) | ||
.then(function (children) { | ||
// now children is just an array of filename strings | ||
treeBranch.children = children; | ||
treeBranch.size = 0; | ||
var countDone = 0; | ||
var areWeDone = function () { | ||
if (countDone === children.length) { | ||
if (options.checksum) { | ||
// We are done, but still have to calculate checksum of whole directory. | ||
treeBranch[options.checksum] = checksumOfDir(treeBranch.children, options.checksum); | ||
} | ||
deferred.resolve(treeBranch); | ||
} | ||
}; | ||
// maybe the directory is emtpy, so we are done here | ||
areWeDone(); | ||
// otherwise replace every item with full tree branch object | ||
children.forEach(function (filename, index) { | ||
treeAsync(pathUtil.join(path, filename), options) | ||
if (!treeBranch) { | ||
// Given path doesn't exist, or it is a file. We are done. | ||
deferred.resolve(treeBranch); | ||
return; | ||
} | ||
if (options.relativePath) { | ||
if (!parent) { | ||
treeBranch.relativePath = '.'; | ||
} else { | ||
treeBranch.relativePath = parent.relativePath + '/' + pathUtil.basename(path); | ||
} | ||
} | ||
if (treeBranch.type !== 'dir') { | ||
// This is a file, we are done here. | ||
deferred.resolve(treeBranch); | ||
return; | ||
} | ||
// Ok, this is a directory, must inspect all its children as well. | ||
listAsync(path) | ||
.then(function (children) { | ||
treeBranch.children = children; | ||
treeBranch.size = 0; | ||
var done = function () { | ||
if (options.checksum) { | ||
// We are done, but still have to calculate checksum of whole directory. | ||
treeBranch[options.checksum] = checksumOfDir(treeBranch.children, options.checksum); | ||
} | ||
deferred.resolve(treeBranch); | ||
}; | ||
var doOne = function (index) { | ||
if (index === children.length) { | ||
done(); | ||
} else { | ||
var subPath = pathUtil.join(path, children[index]); | ||
crawlTreeAsync(subPath, options, treeBranch) | ||
.then(function (treeSubBranch) { | ||
children[index] = treeSubBranch; | ||
treeBranch.size += treeSubBranch.size; | ||
countDone += 1; | ||
areWeDone(); | ||
}, deferred.reject); | ||
}); | ||
}); | ||
} else { | ||
// it's just a file, so we are done | ||
deferred.resolve(treeBranch); | ||
} | ||
}, deferred.reject); | ||
doOne(index + 1); | ||
}) | ||
.catch(deferred.reject); | ||
} | ||
}; | ||
doOne(0); | ||
}); | ||
}) | ||
.catch(deferred.reject); | ||
return deferred.promise; | ||
}; | ||
var createTreeWalkerAsync = function (startPath) { | ||
var deferred = Q.defer(); | ||
treeAsync(startPath, { | ||
absolutePath: true, | ||
relativePath: true, | ||
mode: true | ||
}) | ||
.then(function (wholeTree) { | ||
var allFiles = flattenTree(wholeTree); | ||
deferred.resolve({ | ||
hasNext: function () { | ||
return allFiles.length > 0; | ||
}, | ||
getNext: function () { | ||
return allFiles.shift(); | ||
} | ||
}); | ||
}); | ||
return deferred.promise; | ||
}; | ||
//--------------------------------------------------------- | ||
@@ -336,8 +415,14 @@ // API | ||
module.exports.inspect = inspect; | ||
module.exports.list = list; | ||
module.exports.tree = tree; | ||
module.exports.inspect = inspectSync; | ||
module.exports.list = listSync; | ||
module.exports.tree = treeSync; | ||
module.exports.createTreeWalkerSync = createTreeWalkerSync; | ||
module.exports.inspectAsync = inspectAsync; | ||
module.exports.listAsync = listAsync; | ||
module.exports.treeAsync = treeAsync; | ||
module.exports.treeAsync = treeAsync; | ||
module.exports.createTreeWalkerAsync = createTreeWalkerAsync; | ||
module.exports.utils = { | ||
flattenTree: flattenTree | ||
}; |
@@ -8,6 +8,7 @@ // The main thing. Here everything starts. | ||
var fileOps = require('./fileOps'); | ||
var fileOps = require('./file_ops'); | ||
var inspector = require('./inspector'); | ||
var dir = require('./dir'); | ||
var file = require('./file'); | ||
var find = require('./find'); | ||
var copy = require('./copy'); | ||
@@ -19,5 +20,5 @@ var exists = require('./exists'); | ||
// This is Jetpack Context object. | ||
// It provides API which resolves all paths regarding to passed cwdPath, | ||
// or default process.cwd() if cwdPath was not specified. | ||
// The Jetpack Context object. | ||
// It provides the public API, and resolves all paths regarding to | ||
// passed cwdPath, or default process.cwd() if cwdPath was not specified. | ||
var jetpackContext = function (cwdPath) { | ||
@@ -28,3 +29,3 @@ | ||
} | ||
var cwd = function () { | ||
@@ -40,3 +41,3 @@ // return current CWD if no arguments specified... | ||
} | ||
// resolves path to inner CWD path of this jetpack instance | ||
@@ -46,3 +47,3 @@ var resolvePath = function (path) { | ||
} | ||
var path = function () { | ||
@@ -59,3 +60,3 @@ // add CWD base path as first element of arguments array | ||
path: path, | ||
append: function (path, data, options) { | ||
@@ -67,3 +68,3 @@ fileOps.append(resolvePath(path), data, options); | ||
}, | ||
copy: function (from, to, options) { | ||
@@ -75,3 +76,3 @@ copy.sync(resolvePath(from), resolvePath(to), options); | ||
}, | ||
createWriteStream: function (path, options) { | ||
@@ -83,46 +84,46 @@ return streams.createWriteStream(resolvePath(path), options); | ||
}, | ||
dir: function (path, criteria) { | ||
var normalizedPath = pathUtil.resolve(getCwdPath(), path); | ||
var normalizedPath = resolvePath(path); | ||
dir.sync(normalizedPath, criteria); | ||
if (criteria !== undefined && criteria.exists === false) { | ||
return undefined; | ||
} | ||
return cwd(normalizedPath); | ||
}, | ||
dirAsync: function (path, criteria) { | ||
var qd = Q.defer(); | ||
var normalizedPath = pathUtil.resolve(getCwdPath(), path); | ||
var deferred = Q.defer(); | ||
var normalizedPath = resolvePath(path); | ||
dir.async(normalizedPath, criteria) | ||
.then(function () { | ||
if (criteria !== undefined && criteria.exists === false) { | ||
qd.resolve(undefined); | ||
} else { | ||
qd.resolve(cwd(normalizedPath)); | ||
} | ||
}, qd.reject); | ||
return qd.promise; | ||
deferred.resolve(cwd(normalizedPath)); | ||
}, deferred.reject); | ||
return deferred.promise; | ||
}, | ||
exists: function (path) { | ||
return exists.sync(pathUtil.resolve(getCwdPath(), path)); | ||
return exists.sync(resolvePath(path)); | ||
}, | ||
existsAsync: function (path) { | ||
return exists.async(pathUtil.resolve(getCwdPath(), path)); | ||
return exists.async(resolvePath(path)); | ||
}, | ||
file: function (path, criteria) { | ||
file.sync(pathUtil.resolve(getCwdPath(), path), criteria); | ||
file.sync(resolvePath(path), criteria); | ||
return this; | ||
}, | ||
fileAsync: function (path, criteria) { | ||
var qd = Q.defer(); | ||
var deferred = Q.defer(); | ||
var that = this; | ||
file.async(resolvePath(path), criteria) | ||
.then(function () { | ||
qd.resolve(that); | ||
}, qd.reject); | ||
return qd.promise; | ||
deferred.resolve(that); | ||
}, deferred.reject); | ||
return deferred.promise; | ||
}, | ||
find: function (startPath, options, returnAs) { | ||
return find.sync(resolvePath(startPath), options, returnAs); | ||
}, | ||
findAsync: function (startPath, options, returnAs) { | ||
return find.async(resolvePath(startPath), options, returnAs); | ||
}, | ||
inspect: function (path, fieldsToInclude) { | ||
@@ -134,2 +135,3 @@ return inspector.inspect(resolvePath(path), fieldsToInclude); | ||
}, | ||
inspectTree: function (path, options) { | ||
@@ -141,3 +143,3 @@ return inspector.tree(resolvePath(path), options); | ||
}, | ||
list: function (path, useInspect) { | ||
@@ -149,3 +151,3 @@ return inspector.list(resolvePath(path), useInspect); | ||
}, | ||
move: function (from, to) { | ||
@@ -157,17 +159,17 @@ move.sync(resolvePath(from), resolvePath(to)); | ||
}, | ||
read: function (path, returnAs, options) { | ||
return fileOps.read(resolvePath(path), returnAs, options); | ||
read: function (path, returnAs) { | ||
return fileOps.read(resolvePath(path), returnAs); | ||
}, | ||
readAsync: function (path, returnAs, options) { | ||
return fileOps.readAsync(resolvePath(path), returnAs, options); | ||
readAsync: function (path, returnAs) { | ||
return fileOps.readAsync(resolvePath(path), returnAs); | ||
}, | ||
remove: function (path, options) { | ||
remove.sync(pathUtil.resolve(getCwdPath(), path), options); | ||
remove: function (path) { | ||
remove.sync(resolvePath(path)); | ||
}, | ||
removeAsync: function (path, options) { | ||
return remove.async(pathUtil.resolve(getCwdPath(), path), options) | ||
removeAsync: function (path) { | ||
return remove.async(resolvePath(path)) | ||
}, | ||
rename: function (path, newName) { | ||
@@ -183,3 +185,3 @@ path = resolvePath(path) | ||
}, | ||
write: function (path, data, options) { | ||
@@ -194,2 +196,2 @@ fileOps.write(resolvePath(path), data, options); | ||
module.exports = jetpackContext; | ||
module.exports = jetpackContext; |
@@ -6,3 +6,11 @@ "use strict"; | ||
var Q = require('q'); | ||
var mkdirp = require('mkdirp'); | ||
var exists = require('./exists'); | ||
var generateSourceDoesntExistError = function (path) { | ||
var err = new Error("Path to move doesn't exist " + path); | ||
err.code = 'ENOENT'; | ||
return err; | ||
}; | ||
//--------------------------------------------------------- | ||
@@ -13,3 +21,22 @@ // Sync | ||
var sync = function (from, to) { | ||
fs.renameSync(from, to); | ||
try { | ||
fs.renameSync(from, to); | ||
} catch(err) { | ||
if (err.code !== 'ENOENT') { | ||
// We can't make sense of this error. Rethrow it. | ||
throw err; | ||
} else { | ||
// Ok, source or destination path doesn't exist. | ||
// Must do more investigation. | ||
if (!exists.sync(from)) { | ||
throw generateSourceDoesntExistError(from); | ||
} | ||
if (!exists.sync(to)) { | ||
var destDir = pathUtil.dirname(to); | ||
mkdirp.sync(destDir); | ||
// Retry the attempt | ||
fs.renameSync(from, to); | ||
} | ||
} | ||
} | ||
}; | ||
@@ -21,6 +48,54 @@ | ||
var qRename = Q.denodeify(fs.rename); | ||
var promisedRename = Q.denodeify(fs.rename); | ||
var promisedMkdirp = Q.denodeify(mkdirp); | ||
var ensureDestinationPathExistsAsync = function (to) { | ||
var deferred = Q.defer(); | ||
var destDir = pathUtil.dirname(to); | ||
exists.async(destDir) | ||
.then(function (dstExists) { | ||
if (!dstExists) { | ||
promisedMkdirp(destDir) | ||
.then(deferred.resolve, deferred.reject); | ||
} else { | ||
// Hah, no idea. | ||
deferred.reject(err); | ||
} | ||
}) | ||
.catch(deferred.reject); | ||
return deferred.promise; | ||
}; | ||
var async = function (from, to) { | ||
return qRename(from, to); | ||
var deferred = Q.defer(); | ||
promisedRename(from, to) | ||
.then(deferred.resolve) | ||
.catch(function (err) { | ||
if (err.code !== 'ENOENT') { | ||
// Something unknown. Rethrow original error. | ||
deferred.reject(err); | ||
} else { | ||
// Ok, source or destination path doesn't exist. | ||
// Must do more investigation. | ||
exists.async(from) | ||
.then(function (srcExists) { | ||
if (!srcExists) { | ||
deferred.reject(generateSourceDoesntExistError(from)); | ||
} else { | ||
ensureDestinationPathExistsAsync(to) | ||
.then(function () { | ||
// Retry the attempt | ||
return promisedRename(from, to); | ||
}) | ||
.then(deferred.resolve, deferred.reject); | ||
} | ||
}) | ||
.catch(deferred.reject); | ||
} | ||
}); | ||
return deferred.promise; | ||
}; | ||
@@ -27,0 +102,0 @@ |
"use strict"; | ||
var pathUtil = require('path'); | ||
var Q = require('q'); | ||
var rimraf = require('rimraf'); | ||
var matcher = require('./utils/matcher'); | ||
var inspector = require('./inspector'); | ||
//--------------------------------------------------------- | ||
@@ -14,25 +10,4 @@ // Sync | ||
var sync = function (path, options) { | ||
options = options || {}; | ||
if (options.only === undefined && options.allBut === undefined) { | ||
// No special options set, we can just remove the bastard. | ||
rimraf.sync(path); | ||
return; | ||
} | ||
// Figure out what to remove, and what not to remove. | ||
var tree = inspector.tree(path); | ||
var pathsToRemove; | ||
if (options.only) { | ||
pathsToRemove = matcher.treeToWhitelistPaths(path, tree, options.only); | ||
} else if (options.allBut) { | ||
pathsToRemove = matcher.treeToBlacklistPaths(path, tree, options.allBut); | ||
} | ||
// Remove every path which is in array separately. | ||
pathsToRemove.forEach(function (path) { | ||
rimraf.sync(path); | ||
}); | ||
var sync = function (path) { | ||
rimraf.sync(path); | ||
}; | ||
@@ -47,34 +22,3 @@ | ||
var async = function(path, options) { | ||
var deferred = Q.defer(); | ||
options = options || {}; | ||
if (options.only === undefined && options.allBut === undefined) { | ||
// No special options set, we can just remove the bastard. | ||
qRimraf(path) | ||
.then(deferred.resolve, deferred.reject); | ||
} else { | ||
// Figure out what to remove, and what not to remove. | ||
inspector.treeAsync(path) | ||
.then(function (tree) { | ||
var pathsToRemove; | ||
if (options.only) { | ||
pathsToRemove = matcher.treeToWhitelistPaths(path, tree, options.only); | ||
} else if (options.allBut) { | ||
pathsToRemove = matcher.treeToBlacklistPaths(path, tree, options.allBut); | ||
} | ||
// Remove every path which is in array separately. | ||
var promises = pathsToRemove.map(function (path) { | ||
return qRimraf(path); | ||
}); | ||
return Q.all(promises); | ||
}) | ||
.then(function () { | ||
// Wrapped into function to prevent returning array from Q.all | ||
deferred.resolve(); | ||
}, deferred.reject); | ||
} | ||
return deferred.promise; | ||
return qRimraf(path); | ||
} | ||
@@ -87,2 +31,2 @@ | ||
module.exports.sync = sync; | ||
module.exports.async = async; | ||
module.exports.async = async; |
@@ -6,2 +6,2 @@ "use strict"; | ||
module.exports.createWriteStream = fs.createWriteStream; | ||
module.exports.createReadStream = fs.createReadStream; | ||
module.exports.createReadStream = fs.createReadStream; |
@@ -1,2 +0,2 @@ | ||
// Logic for the .gitignore style pattern matching. | ||
// Matcher for glob patterns (e.g. *.txt, /a/b/**/z) | ||
@@ -8,31 +8,17 @@ "use strict"; | ||
// Creates minimatch matcher. | ||
// Params: | ||
// masks - array of patterns | ||
// excludePart - (optional) absolute path which is some parent directory | ||
// of `path` and should be excluded from matching | ||
var create = function (patterns, excludePart) { | ||
var create = function (patterns) { | ||
if (typeof patterns === 'string') { | ||
patterns = [patterns]; | ||
} | ||
var matchers = patterns.map(function (pattern) { | ||
// if the pattern contains any slashes should be treated as "absolute" pattern, | ||
// so make sure it starts with slash | ||
if (pattern.indexOf('/') !== -1 && pattern.charAt(0) !== '/') { | ||
pattern = '/' + pattern; | ||
} | ||
return new Minimatch(pattern, { matchBase: true, nonegate: false, nocomment: true }); | ||
return new Minimatch(pattern, { | ||
matchBase: true, | ||
nonegate: false, | ||
nocomment: true | ||
}); | ||
}); | ||
// Params: | ||
// path - absolute path to match against | ||
var match = function (path) { | ||
// if... | ||
// path = '/a/b/c' | ||
// excludePart = '/a/b' | ||
// ...then the real path we are testing against is '/c' | ||
if (typeof excludePart === 'string') { | ||
// throw out the excludePart from left side of path | ||
path = pathUtil.sep + pathUtil.relative(excludePart, path); | ||
} | ||
return function (path) { | ||
for (var i = 0; i < matchers.length; i += 1) { | ||
@@ -45,103 +31,4 @@ if (matchers[i].match(path)) { | ||
}; | ||
return match; | ||
}; | ||
// Gets tree as returned by jetpack.tree(), and adds to every element properties | ||
// if this element (or any parent of this element) matches any of given patterns. | ||
var markTree = function (tree, patterns) { | ||
var matcher = create(patterns); | ||
var traverse = function (branch, parentPath, parentMatches) { | ||
// This is the path we was | ||
branch.matchPath = parentPath + '/' + branch.name; | ||
// If parent matches mark all of its children as well. | ||
branch.matches = parentMatches || matcher(branch.matchPath); | ||
if (branch.type === 'dir') { | ||
branch.children.forEach(function (child) { | ||
traverse(child, branch.matchPath, branch.matches); | ||
}); | ||
} | ||
}; | ||
traverse(tree, '', false); | ||
}; | ||
// Creates list of all paths to branches inside a tree, which are | ||
// matched by patterns (whitelisting). | ||
// Params: | ||
// basePath - path to drectory which is the tree root | ||
// tree - object as returned by inspector.tree() | ||
// patterns - array of .gitignore-like patterns | ||
var treeToWhitelistPaths = function (basePath, tree, patterns) { | ||
var rootPath = pathUtil.dirname(basePath); | ||
var paths = []; | ||
markTree(tree, patterns); | ||
var traverse = function (branch) { | ||
if (branch.matches) { | ||
paths.push(rootPath + branch.matchPath); | ||
} else if (branch.type === 'dir') { | ||
branch.children.forEach(function (child) { | ||
traverse(child); | ||
}); | ||
} | ||
}; | ||
traverse(tree); | ||
return paths; | ||
}; | ||
// Creates list of all paths to branches inside a tree, excluding parts | ||
// of tree matched by patterns (blacklisting). | ||
// Params: | ||
// basePath - path to drectory which is the tree root | ||
// tree - object as returned by inspector.tree() | ||
// patterns - array of .gitignore-like patterns | ||
var treeToBlacklistPaths = function (basePath, tree, patterns) { | ||
var rootPath = pathUtil.dirname(basePath); | ||
var paths = []; | ||
markTree(tree, patterns); | ||
var anyChildMatches = function (branch) { | ||
if (branch.matches) { | ||
return true; | ||
} | ||
if (branch.type === 'dir') { | ||
for (var i = 0; i < branch.children.length; i += 1) { | ||
if (anyChildMatches(branch.children[i])) { | ||
return true; | ||
} | ||
} | ||
} | ||
return false; | ||
}; | ||
var traverse = function (branch) { | ||
if (!anyChildMatches(branch)) { | ||
paths.push(rootPath + branch.matchPath); | ||
} else if (branch.type === 'dir') { | ||
branch.children.forEach(function (child) { | ||
traverse(child); | ||
}); | ||
} | ||
}; | ||
traverse(tree); | ||
return paths; | ||
}; | ||
//--------------------------------------------------------- | ||
// API | ||
//--------------------------------------------------------- | ||
module.exports.create = create; | ||
module.exports.markTree = markTree; | ||
module.exports.treeToWhitelistPaths = treeToWhitelistPaths; | ||
module.exports.treeToBlacklistPaths = treeToBlacklistPaths; |
@@ -5,2 +5,2 @@ "use strict"; | ||
module.exports = jetpack(); | ||
module.exports = jetpack(); |
{ | ||
"name": "fs-jetpack", | ||
"description": "Higher level API for 'fs' library", | ||
"version": "0.5.3", | ||
"description": "Better file system API", | ||
"version": "0.6.0", | ||
"author": "Jakub Szwacz <jakub@szwacz.com>", | ||
"dependencies": { | ||
"minimatch": "0.3.0", | ||
"minimatch": "2.0.1", | ||
"mkdirp": "0.5.0", | ||
@@ -13,7 +13,10 @@ "q": "1.0.1", | ||
"devDependencies": { | ||
"jasmine-node": "1.14.x", | ||
"fs-extra": "0.9.1" | ||
"coveralls": "^2.11.2", | ||
"fs-extra": "^0.16.3", | ||
"istanbul": "^0.3.6", | ||
"jasmine": "^2.2.1" | ||
}, | ||
"scripts": { | ||
"test": "node_modules/.bin/jasmine-node spec" | ||
"test": "node_modules/.bin/jasmine", | ||
"coveralls": "node_modules/.bin/istanbul cover node_modules/.bin/jasmine && node_modules/.bin/coveralls < coverage/lcov.info" | ||
}, | ||
@@ -26,3 +29,7 @@ "main": "main.js", | ||
}, | ||
"license": "MIT" | ||
"license": "MIT", | ||
"keywords": [ | ||
"fs", | ||
"file system" | ||
] | ||
} |
397
README.md
@@ -1,7 +0,9 @@ | ||
fs-jetpack | ||
fs-jetpack [![Build Status](https://travis-ci.org/szwacz/fs-jetpack.svg?branch=master)](https://travis-ci.org/szwacz/fs-jetpack) [![Coverage Status](https://coveralls.io/repos/szwacz/fs-jetpack/badge.svg)](https://coveralls.io/r/szwacz/fs-jetpack) | ||
========== | ||
Motivation: node's [fs library](http://nodejs.org/api/fs.html) is very low level API, what makes it too often painful/inconvenient to use. This project is an attempt to build comprehensive, higher level API on top of that library. See ["Neat tricks fs-jetpack knows"](#how-fun) as a starter. | ||
Node's [fs library](http://nodejs.org/api/fs.html) API is very low level, and because of that painful to use. We need higher layer of abstraction over it. That's what **fs-jetpack** aspires to be. | ||
### Installation | ||
#### [Jump to API Docs](#api) | ||
## Installation | ||
``` | ||
@@ -11,3 +13,3 @@ npm install fs-jetpack | ||
### Usage | ||
## Usage | ||
```javascript | ||
@@ -17,46 +19,139 @@ var jetpack = requite('fs-jetpack'); | ||
# API | ||
API has the same set of synchronous and asynchronous methods. All async methods are promise based (so no callbacks folks, [promises](https://github.com/kriskowal/q) instead). | ||
Commonly used naming convention in node world is reversed in this library. Asynchronous methods are those with "Async" suffix, all methods without "Async" in the name are synchronous. Reason behind this is that it gives very nice look to blocking API, and promise based non-blocking code is verbose anyway, so one more word is not much of a difference. Also it just feels right to me. When you see "Async" word you are 100% sure this method is returning promise, and when you don't see it, you are 100% sure this method retuns immediately (and possibly blocks). | ||
#What's cool about jetpack? | ||
```javascript | ||
// Usage of blocking API | ||
try { | ||
jetpack.dir('foo'); | ||
} catch (err) { | ||
// Something went wrong | ||
} | ||
## Promises instead of callbacks | ||
API has the same set of synchronous and asynchronous methods. All async methods are promise based. | ||
// Usage of non-blocking API | ||
jetpack.dirAsync('foo') | ||
.then(function () { | ||
// Done! | ||
}, function (err) { | ||
// Something went wrong | ||
Commonly used naming convention in Node world is reversed in this library (no 'method' and 'methodSync' naming). Asynchronous methods are those with 'Async' suffix, all methods without 'Async' in the name are synchronous. Reason behind this is that it gives very nice look to blocking API, and promise based non-blocking code is verbose anyway, so one more word is not much of a difference. | ||
Thanks to that the API is also coherent... | ||
```js | ||
// If method has no 'Async' suffix it gives you answer right away. | ||
var data = jetpack.read('file.txt'); | ||
console.log(data); | ||
// Want to make that call asnychronous? Just add the word "Async" | ||
// and it will give you promise instead of ready value. | ||
jetpack.readAsync('file.txt') | ||
.then(function (data) { | ||
console.log(data); | ||
}); | ||
``` | ||
## Every jetpack instance has its internal CWD | ||
You can create many jetpack objects with different internal working directories (which are independent of `process.cwd()`) and work on directories in a little more object-oriented manner. | ||
```js | ||
var src = jetpack.cwd('path/to/source'); | ||
var dest = jetpack.cwd('path/to/destination'); | ||
src.copy('foo.txt', dest.path('bar.txt')); | ||
``` | ||
## JSON is a first class citizen | ||
You can write JavaScript object directly to disk and it will be transformed to JSON automatically. | ||
```js | ||
var obj = { greet: "Hello World!" }; | ||
jetpack.write('file.json', obj); | ||
``` | ||
Then you can get your object back just by telling read method that it's a JSON. | ||
```js | ||
var obj = jetpack.read('file.json', 'json'); | ||
``` | ||
## Throws errors at you as the last resort | ||
Everyone who did something with files for sure seen *"ENOENT, no such file or directory"* error. Jetpack tries to recover from that error if possible. | ||
1. For wrte/creation operations, if any of parent directories doesn't exist jetpack will just create them as well. | ||
2. For read/inspect operations, if file or directory doesn't exist `null` is returned instead of throwing. | ||
## This is just a powerful API | ||
All methods play nicely with each other. Here are few examples what it can give you. | ||
**Note:** All examples are synchronous. Unfortunately asynchronous versions of them will be uglier :) | ||
#### Files creation in declarative style | ||
```js | ||
// To create structure... | ||
// (CWD path) | ||
// |- greets | ||
// |- greet.txt | ||
// |- greet.json | ||
// |- greets-i18n | ||
// |- polish.txt | ||
jetpack | ||
.dir('greets') | ||
.file('greet.txt', { content: 'Hello World!' }) | ||
.file('greet.json', { content: { greet: 'Hello World!' } }) | ||
.cwd('..') | ||
.dir('greets-i18n') | ||
.file('polish.txt', { content: 'Cześć!' }); | ||
``` | ||
#### Delete all tmp files inside directory tree | ||
```js | ||
jetpack.find('my-dir', { | ||
matching: '*.tmp' | ||
}) | ||
.forEach(jetpack.remove); | ||
``` | ||
#### Check if two files have the same content | ||
```js | ||
var file1 = jetpack.inspect('file1', { checksum: 'md5' }); | ||
var file2 = jetpack.inspect('file2', { checksum: 'md5' }); | ||
var areTheSame = (file1.md5 === file2.md5); | ||
``` | ||
#### Great for build scripts | ||
```js | ||
var src = jetpack.cwd('path/to/source'); | ||
var dest = jetpack.dir('path/to/destination', { empty: true }); | ||
src.copy('.', dest.path(), { | ||
matching: ['./vendor/**', '*.html', '*.png', '*.jpg'] | ||
}); | ||
var config = src.read('config.json', 'json'); | ||
config.env = 'production'; | ||
dest.write('config.json', config); | ||
``` | ||
# <a name="api"></a> API Docs | ||
API methods have blocking and non-blocking equivalents: | ||
```js | ||
// Synchronous call | ||
var data = jetpack.read('file.txt'); | ||
console.log(data); | ||
// Asynchronous call | ||
jetpack.readAsync('file.txt') | ||
.then(function (data) { | ||
console.log(data); | ||
}); | ||
``` | ||
**Methods:** | ||
* [append(path, data, [options])](#append) | ||
* [copy(from, to, [options])](#copy) | ||
* [createReadStream(path, [options])](#create-read-stream) | ||
* [createWriteStream(path, [options])](#create-write-stream) | ||
* [cwd([path...])](#cwd) | ||
* [dir(path, [criteria])](#dir) | ||
* [exists(path)](#exists) | ||
* [file(path, [criteria])](#file) | ||
* [inspect(path, [options])](#inspect) | ||
* [inspectTree(path, [options])](#inspect-tree) | ||
* [list(path, [useInspect])](#list) | ||
* [move(from, to)](#move) | ||
* [path(parts...)](#path) | ||
* [read(path, [returnAs], [options])](#read) | ||
* [remove(path, [options])](#remove) | ||
* [rename(path, newName)](#rename) | ||
* [write(path, data, [options])](#write) | ||
* [append](#append) | ||
* [copy](#copy) | ||
* [createReadStream](#create-read-stream) | ||
* [createWriteStream](#create-write-stream) | ||
* [cwd](#cwd) | ||
* [dir](#dir) | ||
* [exists](#exists) | ||
* [file](#file) | ||
* [find](#find) | ||
* [inspect](#inspect) | ||
* [inspectTree](#inspect-tree) | ||
* [list](#list) | ||
* [move](#move) | ||
* [path](#path) | ||
* [read](#read) | ||
* [remove](#remove) | ||
* [rename](#rename) | ||
* [write](#write) | ||
## <a name="append"></a> append(path, data, [options]) | ||
also **appendAsync(path, data, [options])** | ||
asynchronous: **appendAsync(path, data, [options])** | ||
@@ -76,3 +171,3 @@ Appends given data to the end of file. If file (or any parent directory) doesn't exist, creates it (or them). | ||
## <a name="copy"></a> copy(from, to, [options]) | ||
also **copyAsync(from, to, [options])** | ||
asynchronous: **copyAsync(from, to, [options])** | ||
@@ -86,4 +181,3 @@ Copies given file or directory (with everything inside). | ||
* `overwrite` (default: `false`) Whether to overwrite destination path if it exists. For directories, source directory is merged with destination directory, so files in destination which are not present in the source, will remain intact. | ||
* `only` (`Array` of globs) will copy **only** items matching any of specified glob patterns [(read more)](#matching-paths). | ||
* `allBut` (`Array` of globs) will copy **everything except** items matching any of specified glob patterns [(read more)](#matching-paths). If `only` was also specified this field is ignored. | ||
* `matching` if defined will actually copy **only** items matching any of specified glob patterns and omit everything else (see examples below). | ||
@@ -98,7 +192,11 @@ **returns:** | ||
// Copies only ".jpg" files from my_dir | ||
jetpack.copy('my_dir', 'somewhere/my_dir', { only: ['*.jpg'] }); | ||
// Copies only .md files from my-dir to somewhere/my-dir | ||
jetpack.copy('my-dir', 'somewhere/my-dir', { matching: '*.md' }); | ||
// Copies everything except "logs" directory inside my_dir | ||
jetpack.copy('my_dir', 'somewhere/my_dir', { allBut: ['my_dir/logs'] }); | ||
// When glob pattern starts with './' it means it is anchored to specified | ||
// directory to copy. Here will be copied only .jpg files from my-dir/images | ||
// and .md files from my-dir/articles | ||
jetpack.copy('my_dir', 'somewhere/my_dir', { | ||
matching: ['./images/**/*.jpg', './articles/**/*.md' ] | ||
}); | ||
``` | ||
@@ -153,5 +251,5 @@ | ||
## <a name="dir"></a> dir(path, [criteria]) | ||
also **dirAsync(path, [criteria])** | ||
asynchronous: **dirAsync(path, [criteria])** | ||
Ensures that directory on given path meets given criteria. If any criterium is not met it will be after this call. | ||
Ensures that directory on given path exists and meets given criteria. If any criterium is not met it will be after this call. | ||
@@ -161,4 +259,3 @@ **parameters:** | ||
`criteria` (optional) criteria to be met by the directory. Is an `Object` with possible fields: | ||
* `exists` (default: `true`) whether directory should exist or not. If set to `true` and `path` contains many nested, nonexistent directories all of them will be created. | ||
* `empty` (default: `false`) whether directory should be empty (no other files or directories inside). If set to `true` and directory contains any files or subdirectories all of them will be deleted. If `exists = false` this field is ignored. | ||
* `empty` (default: `false`) whether directory should be empty (no other files or directories inside). If set to `true` and directory contains any files or subdirectories all of them will be deleted. | ||
* `mode` ensures directory has specified mode. If not set and directory already exists, current mode will be preserved. Value could be number (eg. `0700`) or string (eg. `'700'`). | ||
@@ -168,3 +265,2 @@ | ||
New CWD context with directory specified in `path` as CWD. | ||
Or `undefined` if `exists` was set to `false`. | ||
@@ -174,9 +270,6 @@ **examples:** | ||
// Creates directory if doesn't exist | ||
jetpack.dir('new_dir'); | ||
jetpack.dir('new-dir'); | ||
// Makes sure that directory does NOT exist | ||
jetpack.dir('/my_stuff/some_dir', { exists: false }); | ||
// Makes sure directory mode is 0700 and that it's empty | ||
jetpack.dir('empty_dir', { empty: true, mode: '700' }); | ||
jetpack.dir('empty-dir', { empty: true, mode: '700' }); | ||
@@ -186,4 +279,4 @@ // Because dir returns new CWD context pointing to just | ||
jetpack | ||
.dir('main_dir') // creates 'main_dir' | ||
.dir('sub_dir'); // creates 'main_dir/sub_dir' | ||
.dir('main-dir') // creates 'main-dir' | ||
.dir('sub-dir'); // creates 'main-dir/sub-dir' | ||
``` | ||
@@ -193,3 +286,3 @@ | ||
## <a name="exists"></a> exists(path) | ||
also **existsAsync(path)** | ||
asynchronous: **existsAsync(path)** | ||
@@ -206,5 +299,5 @@ Checks whether something exists on given `path`. This method returns values more specyfic than `true/false` to protect from errors like "I was expecting directory, but it was a file". | ||
## <a name="file"></a> file(path, [criteria]) | ||
also **fileAsync(path, [criteria])** | ||
asynchronous: **fileAsync(path, [criteria])** | ||
Ensures that file meets given criteria. If any criterium is not met it will be after this call. | ||
Ensures that file exists and meets given criteria. If any criterium is not met it will be after this call. | ||
@@ -214,6 +307,4 @@ **parameters:** | ||
`criteria` (optional) criteria to be met by the file. Is an `Object` with possible fields: | ||
* `exists` (default: `true`) whether file should exist or not. | ||
* `empty` (default: `false`) whether file should be empty. If `exists = false` this field is ignored. | ||
* `content` (`String`, `Buffer`, `Object` or `Array`) sets file content. If `Object` or `Array` given to this parameter the output will be JSON. If `exists = false`, or `empty = true` this field is ignored. | ||
* `jsonIndent` (defaults to 0) if writing JSON data this tells how many spaces should one indentation have. | ||
* `content` sets file content. Could be `String`, `Buffer`, `Object` or `Array`. If `Object` or `Array` given to this parameter data will be written as JSON. | ||
* `jsonIndent` (defaults to 2) if writing JSON data this tells how many spaces should one indentation have. | ||
* `mode` ensures file has specified mode. If not set and file already exists, current mode will be preserved. Value could be number (eg. `0700`) or string (eg. `'700'`). | ||
@@ -229,5 +320,2 @@ | ||
// Ensures file does NOT exist (if exists will be deleted) | ||
jetpack.file('not_something.txt', { exists: false }); | ||
// Creates file with mode '777' and content 'Hello World!' | ||
@@ -238,4 +326,31 @@ jetpack.file('hello.txt', { mode: '777', content: 'Hello World!' }); | ||
## <a name="find"></a> find(path, searchOptions, [returnAs]) | ||
asynchronous: **findAsync(path, searchOptions, [returnAs])** | ||
Finds in directory specified by `path` all files fulfilling `searchOptions`. | ||
**parameters:** | ||
`path` path to start search in (all subdirectories will be searched). | ||
`searchOptions` is an `Object` with possible fields: | ||
* `matching` glob patterns of files you would like to find. | ||
`returnAs` (optional) how the results should be returned. Could be one of: | ||
* `'absolutePath'` (default) returns array of absolute paths. | ||
* `'relativePath'` returns array of relative paths. The paths are relative to `path` you started search in. | ||
* `'inspect'` returns array of objects like you called [inspect](#inspect) on every of those files. | ||
**returns:** | ||
`Array` of found files. | ||
**examples:** | ||
```javascript | ||
// Finds all files or directories which has 2015 in the name | ||
jetpack.find('my-work', { matching: '*2015*' }); | ||
// Finds all jpg and png files and gives you back the list of inspect objects | ||
// (like you called jetpack.inspect on every of those paths) | ||
jetpack.find('my-work', { matching: ['*.jpg', '*.png'] }, 'inspect'); | ||
``` | ||
## <a name="inspect"></a> inspect(path, [options]) | ||
also **inspectAsync(path, [options])** | ||
asynchronous: **inspectAsync(path, [options])** | ||
@@ -247,5 +362,6 @@ Inspects given path (replacement for `fs.stat`). Returned object by default contains only very basic, not platform-dependent properties (so you have something e.g. your unit tests can rely on), you can enable more properties through options object. | ||
`options` (optional). Possible values: | ||
* `checksum` if specified will return checksum of inspected file. Possible values are strings `'md5'` or `'sha1'`. If given path is directory this field is ignored. | ||
* `checksum` if specified will return checksum of inspected file. Possible values are strings `'md5'`, `'sha1'` or `'sha256'`. If given path is directory this field is ignored. | ||
* `mode` (default `false`) if set to `true` will add file mode (unix file permissions) value. | ||
* `times` (default `false`) if set to `true` will add atime, mtime and ctime fields (here called `accessTime`, `modifyTime` and `changeTime`). | ||
* `absolutePath` (dafault `false`) if set to `true` will add absolute path to this resource. | ||
@@ -273,3 +389,3 @@ **returns:** | ||
## <a name="inspect-tree"></a> inspectTree(path, [options]) | ||
also **inspectTreeAsync(path, [options])** | ||
asynchronous: **inspectTreeAsync(path, [options])** | ||
@@ -281,3 +397,4 @@ Calls [inspect](#inspect) recursively on given path so it creates tree of all directories and sub-directories inside it. | ||
`options` (optional). Possible values: | ||
* `checksum` if specified will also calculate checksum of every item in the tree. Possible values are strings `'md5'` or `'sha1'`. Checksums for directories are calculated as checksum of all children' checksums plus their filenames (see example below). | ||
* `checksum` if specified will also calculate checksum of every item in the tree. Possible values are strings `'md5'`, `'sha1'` or `'sha256'`. Checksums for directories are calculated as checksum of all children' checksums plus their filenames (see example below). | ||
* `relativePath` if set to `true` every tree node will have relative path anchored to root inspected folder. | ||
@@ -292,2 +409,3 @@ **returns:** | ||
size: 123, // this is combined size of all items in this directory | ||
relativePath: '.', | ||
md5: '11c68d9ad988ff4d98768193ab66a646', | ||
@@ -301,2 +419,3 @@ // checksum of this directory was calculated as: | ||
size: 0, // the directory is empty | ||
relativePath: './dir', | ||
md5: null, // can't calculate checksum of empty directory | ||
@@ -308,2 +427,3 @@ children: [] | ||
size: 123, | ||
relativePath: './file.txt', | ||
md5: '900150983cd24fb0d6963f7d28e17f72' | ||
@@ -317,3 +437,3 @@ } | ||
## <a name="list"></a> list(path, [useInspect]) | ||
also **listAsync(path, [useInspect])** | ||
asynchronous: **listAsync(path, [useInspect])** | ||
@@ -325,3 +445,3 @@ Lists the contents of directory. | ||
`useInspect` (optional) the type of data this call should return. Possible values: | ||
* `false` (default) returns just a list of filenames (the same as `fs.readdir()`) | ||
* `false` (default) returns just a list of filenames (the same as `fs.readdir`) | ||
* `true` performs [inspect](#inspect) on every item in directory, and returns array of those objects | ||
@@ -335,3 +455,3 @@ * `object` if object has been passed to this parameter, it is treated as `options` parameter for [inspect](#inspect) method, and will alter returned inspect objects | ||
## <a name="move"></a> move(from, to) | ||
also **moveAsync(from, to)** | ||
asynchronous: **moveAsync(from, to)** | ||
@@ -366,6 +486,6 @@ Moves given path to new location. | ||
## <a name="read"></a> read(path, [returnAs], [options]) | ||
also **readAsync(path, [returnAs], [options])** | ||
## <a name="read"></a> read(path, [returnAs]) | ||
asynchronous: **readAsync(path, [returnAs])** | ||
Reads content of file. If file on given path doesn't exist returns `null` instead of throwing `ENOENT` error. | ||
Reads content of file. If file on given path doesn't exist returns `null` instead of throwing error. | ||
@@ -380,5 +500,2 @@ **parameters:** | ||
`options` (optional) is an object with possible fields: | ||
* `safe` if set to `true` the file will be read in ["safe mode"](#safe-mode). | ||
**returns:** | ||
@@ -388,12 +505,9 @@ File content in specified format, or `null` if file doesn't exist. | ||
## <a name="remove"></a> remove(path, [options]) | ||
also **removeAsync(path, [options])** | ||
## <a name="remove"></a> remove(path) | ||
asynchronous: **removeAsync(path)** | ||
Deletes given path, no matter what it is (file or directory). | ||
Deletes given path, no matter what it is (file or directory). If path already doesn't exist ends without throwing, so you can use it as 'ensure path doesn't exist'. | ||
**parameters:** | ||
`path` path to file or directory you want to remove. | ||
`options` (optional) additional conditions to removal process. Is an object with possible fields: | ||
* `only` (`Array` of globs) will delete **only** items matching any of specified glob patterns [(read more on that)](#matching-paths). | ||
* `allBut` (`Array` of globs) will delete **everything except** items matching any of specified glob patterns [(read more on that)](#matching-paths). If `only` was also specified this field is ignored. | ||
@@ -410,10 +524,2 @@ **returns:** | ||
jetpack.remove('my_work/important_stuff'); | ||
// Will delete any ".log" file, and any folder or file named "temp" inside "my_app", | ||
// but will leave all other files intact. | ||
jetpack.remove('my_app', { only: [ '*.log', 'temp' ] }); | ||
// Will delete everything inside "my_app" directory, | ||
// but will leave directory or file "my_app/user_data" intact. | ||
jetpack.remove('my_app', { allBut: [ 'my_app/user_data' ] }); | ||
``` | ||
@@ -423,3 +529,3 @@ | ||
## <a name="rename"></a> rename(path, newName) | ||
also **renameAsync(path, newName)** | ||
asynchronous: **renameAsync(path, newName)** | ||
@@ -437,3 +543,3 @@ Renames given file or directory. | ||
## <a name="write"></a> write(path, data, [options]) | ||
also **writeAsync(path, data, [options])** | ||
asynchronous: **writeAsync(path, data, [options])** | ||
@@ -446,101 +552,6 @@ Writes data to file. | ||
`options` (optional) `Object` with possible fields: | ||
* `jsonIndent` (defaults to 0) if writing JSON data this tells how many spaces should one indentation have. | ||
* `safe` if set to `true` the file will be written in ["safe mode"](#safe-mode). | ||
* `atomic` (default `false`) if set to `true` the file will be written using strategy which is much more resistant to data loss. The trick is very simple, [read this to get the concept](http://stackoverflow.com/questions/17047994/transactionally-writing-files-in-node-js). | ||
* `jsonIndent` (defaults to 2) if writing JSON data this tells how many spaces should one indentation have. | ||
**returns:** | ||
Nothing. | ||
# <a name="how-fun"></a> Neat tricks fs-jetpack knows | ||
### Every jetpack instance has its independent, internal CWD | ||
So you can create many jetpack objects and work on directories in a little more object-oriented fashion. | ||
```javascript | ||
var src = jetpack.cwd('path/to/source'); | ||
var dest = jetpack.cwd('path/to/destination'); | ||
src.copy('foo.txt', dest.path('bar.txt')); | ||
``` | ||
### Files creation in declarative style | ||
You can create whole tree of directories and files in declarative style. | ||
```javascript | ||
// Synchronous style | ||
jetpack | ||
.dir('foo') | ||
.file('foo.txt', { content: 'Hello...' }) | ||
.file('bar.txt', { content: '...world!' }) | ||
.cwd('..') | ||
.dir('bar') | ||
.file('foo.txt', { content: 'Wazup?' }); | ||
// Asynchronous style (unfortunately not that pretty) | ||
jetpack | ||
.dirAsync('foo') | ||
.then(function (dir) { | ||
return dir.fileAsync('foo.txt', { content: 'Hello...' }); | ||
}) | ||
.then(function (dir) { | ||
return dir.fileAsync('bar.txt', { content: '...world!' }); | ||
.then(function (dir) { | ||
return dir.cwd('..').dirAsync('bar'); | ||
}) | ||
.then(function (dir) { | ||
dir.fileAsync('foo.txt', { content: 'Wazup?' }); | ||
}); | ||
``` | ||
### Hides ENOENT from you as much as possible | ||
*"ENOENT, no such file or directory"* is the most annoying error when working with file system, and fs-jetpack does 2 things to save you the hassle: | ||
1. For wrte/creation operations, if any of parent directories doesn't exist, jetpack will just create them as well. | ||
2. For read/inspect operations, if file or directory doesn't exist, `null` is returned instead of throwing. | ||
### <a name="matching-paths"></a> Filtering things to copy/remove with "globs" | ||
[Copy](#copy) and [remove](#remove) have option for blacklisting and whitelisting things inside directory which will be affected by the operation. For instance: | ||
```javascript | ||
// Let's assume we have folder structure: | ||
// foo | ||
// |- a.jpg | ||
// |- b.pdf | ||
// |- c.txt | ||
// |- bar | ||
// |- a.pdf | ||
jetpack.copy('foo', 'foo1', { allBut: ['*.pdf'] }); | ||
// Will give us folder: | ||
// foo1 | ||
// |- a.jpg | ||
// |- c.txt | ||
// |- bar | ||
jetpack.copy('foo', 'foo2', { only: ['*.pdf'] }); | ||
// Will give us folder: | ||
// foo2 | ||
// |- b.pdf | ||
// |- bar | ||
// |- a.pdf | ||
``` | ||
### <a name="safe-mode"></a> "Safe" file overwriting | ||
It is not fully safe to just overwrite existing file with new content. If your process crashes during this operation you are srewed. The old file content is lost, because you were writing to the same place new stuff, and the new stuff is lost totally or written only partially. Fs-jetpack has built-in "safe mode", which helps you deal with this issue. Under the hood it works as follows... | ||
Let's assume there's already `file.txt` with content `Hello world!` on disk, and we want to update it to `Hello universe!`. | ||
```javascript | ||
jetpack.write('file.txt', 'Hello universe!', { safe: true }); | ||
``` | ||
Above line will perform tasks as follows: | ||
1. Write `Hello universe!` to `file.txt.__new__` (so we didn't owerwrite the original file). | ||
2. Move `file.txt` (with `Hello world!`) to `file.txt.__bak__`, so it can serve as a backup. | ||
3. Move `file.txt.__new__` to `file.txt`, where we wanted it to be on the first place. | ||
4. Delete `file.txt.__bak__`, because it is no longer needed. | ||
Thanks to that the backup of old data is reachable all the time, until we are 100% sure the new data has been successfuly written to disk. | ||
For this to work, read operation have to be aware of the backup file. | ||
```javascript | ||
jetpack.read('file.txt', { safe: true }); | ||
``` | ||
Above read will do: | ||
1. Read `file.txt` | ||
2. If step 1 failed, try to read `file.txt.__bak__`. | ||
3. If step 2 failed as well, we are sure there is no such file. | ||
The whole process is performed automatically for you by simply adding `safe: true` to call options of [write](#write) and [read](#read) methods. |
"use strict"; | ||
describe('append |', function () { | ||
var fse = require('fs-extra'); | ||
var pathUtil = require('path'); | ||
var helper = require('./helper'); | ||
var helper = require('./support/spec_helper'); | ||
var jetpack = require('..'); | ||
var path = 'file.txt'; | ||
beforeEach(helper.beforeEach); | ||
afterEach(helper.afterEach); | ||
it('appends String to file', function (done) { | ||
var preparations = function () { | ||
fse.writeFileSync(path, 'abc'); | ||
helper.clearWorkingDir(); | ||
fse.writeFileSync('file.txt', 'abc'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.readFileSync(path, 'utf8')).toBe('abcxyz'); | ||
expect('file.txt').toBeFileWithContent('abcxyz'); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.append(path, 'xyz'); | ||
jetpack.append('file.txt', 'xyz'); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.appendAsync(path, 'xyz') | ||
jetpack.appendAsync('file.txt', 'xyz') | ||
.then(function () { | ||
@@ -40,26 +37,25 @@ expectations(); | ||
}); | ||
it('appends Buffer to file', function (done) { | ||
var preparations = function () { | ||
fse.writeFileSync(path, new Buffer([11])); | ||
helper.clearWorkingDir(); | ||
fse.writeFileSync('file.txt', new Buffer([11])); | ||
}; | ||
var expectations = function () { | ||
var buf = fse.readFileSync(path); | ||
expect(buf.length).toBe(2); | ||
var buf = fse.readFileSync('file.txt'); | ||
expect(buf[0]).toBe(11); | ||
expect(buf[1]).toBe(22); | ||
expect(buf.length).toBe(2); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.append(path, new Buffer([22])); | ||
jetpack.append('file.txt', new Buffer([22])); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.appendAsync(path, new Buffer([22])) | ||
jetpack.appendAsync('file.txt', new Buffer([22])) | ||
.then(function () { | ||
@@ -70,17 +66,21 @@ expectations(); | ||
}); | ||
it("if file doesn't exist creates it", function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
}; | ||
var expectations = function () { | ||
expect(fse.readFileSync(path, 'utf8')).toBe('xyz'); | ||
expect('file.txt').toBeFileWithContent('xyz'); | ||
}; | ||
// SYNC | ||
jetpack.append(path, 'xyz'); | ||
preparations(); | ||
jetpack.append('file.txt', 'xyz'); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
jetpack.appendAsync(path, 'xyz') | ||
preparations(); | ||
jetpack.appendAsync('file.txt', 'xyz') | ||
.then(function () { | ||
@@ -91,19 +91,21 @@ expectations(); | ||
}); | ||
it("if parent directory doesn't exist creates it as well", function (done) { | ||
var path = 'dir/dir/file.txt'; | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
}; | ||
var expectations = function () { | ||
expect(fse.readFileSync(path, 'utf8')).toBe('xyz'); | ||
expect('dir/dir/file.txt').toBeFileWithContent('xyz'); | ||
}; | ||
// SYNC | ||
jetpack.append(path, 'xyz'); | ||
preparations(); | ||
jetpack.append('dir/dir/file.txt', 'xyz'); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
jetpack.appendAsync(path, 'xyz') | ||
preparations(); | ||
jetpack.appendAsync('dir/dir/file.txt', 'xyz') | ||
.then(function () { | ||
@@ -114,46 +116,58 @@ expectations(); | ||
}); | ||
it("returns undefined", function (done) { | ||
it("respects internal CWD of jetpack instance", function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('a/b.txt', 'abc'); | ||
}; | ||
var expectations = function () { | ||
expect('a/b.txt').toBeFileWithContent('abcxyz'); | ||
}; | ||
var jetContext = jetpack.cwd('a'); | ||
// SYNC | ||
var ret = jetpack.append('file.txt', 'xyz'); | ||
expect(ret).toBe(undefined); | ||
helper.clearWorkingDir(); | ||
preparations(); | ||
jetContext.append('b.txt', 'xyz'); | ||
expectations(); | ||
// ASYNC | ||
jetpack.appendAsync('file.txt', 'xyz') | ||
.then(function (ret) { | ||
expect(ret).toBe(undefined); | ||
preparations(); | ||
jetContext.appendAsync('b.txt', 'xyz') | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
if (process.platform !== 'win32') { | ||
describe('*nix specyfic |', function () { | ||
it('sets file mode on created file', function (done) { | ||
var expectations = function () { | ||
expect(fse.statSync('file.txt').mode.toString(8)).toBe('100711'); | ||
}; | ||
// SYNC | ||
jetpack.append('file.txt', 'abc', { mode: '711' }); | ||
describe('*nix specyfic |', function () { | ||
if (process.platform === 'win32') { | ||
return; | ||
} | ||
it('sets file mode on created file', function (done) { | ||
var expectations = function () { | ||
expect('file.txt').toHaveMode('711'); | ||
}; | ||
// SYNC | ||
jetpack.append('file.txt', 'abc', { mode: '711' }); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// AYNC | ||
jetpack.appendAsync('file.txt', 'abc', { mode: '711' }) | ||
.then(function () { | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// AYNC | ||
jetpack.appendAsync('file.txt', 'abc', { mode: '711' }) | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
done(); | ||
}); | ||
}); | ||
} | ||
}); | ||
}); |
"use strict"; | ||
describe('copy |', function () { | ||
var fse = require('fs-extra'); | ||
var pathUtil = require('path'); | ||
var helper = require('./helper'); | ||
var helper = require('./support/spec_helper'); | ||
var jetpack = require('..'); | ||
beforeEach(helper.beforeEach); | ||
afterEach(helper.afterEach); | ||
it("copies a file", function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('file.txt', 'abc'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.readFileSync('file.txt', 'utf8')).toBe('abc'); | ||
expect(fse.readFileSync('file_1.txt', 'utf8')).toBe('abc'); | ||
expect('file.txt').toBeFileWithContent('abc'); | ||
expect('file_1.txt').toBeFileWithContent('abc'); | ||
}; | ||
// SYNC | ||
@@ -28,5 +29,3 @@ preparations(); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
@@ -40,14 +39,15 @@ preparations(); | ||
}); | ||
it("can copy file to nonexistent directory (will create directory)", function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('file.txt', 'abc'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.readFileSync('file.txt', 'utf8')).toBe('abc'); | ||
expect(fse.readFileSync('dir/dir/file.txt', 'utf8')).toBe('abc'); | ||
expect('file.txt').toBeFileWithContent('abc'); | ||
expect('dir/dir/file.txt').toBeFileWithContent('abc'); | ||
}; | ||
// SYNC | ||
@@ -57,5 +57,3 @@ preparations(); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
@@ -69,13 +67,14 @@ preparations(); | ||
}); | ||
it("copies empty directory", function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
fse.mkdirsSync('dir'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.statSync('a/dir').isDirectory()).toBe(true); | ||
expect('a/dir').toBeDirectory(); | ||
}; | ||
// SYNC | ||
@@ -85,5 +84,3 @@ preparations(); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
@@ -97,6 +94,7 @@ preparations(); | ||
}); | ||
it("copies a tree of files", function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('a/f1.txt', 'abc'); | ||
@@ -106,9 +104,9 @@ fse.outputFileSync('a/b/f2.txt', '123'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.readFileSync('dir/a/f1.txt', 'utf8')).toBe('abc'); | ||
expect(fse.existsSync('dir/a/b/c')).toBe(true); | ||
expect(fse.readFileSync('dir/a/b/f2.txt', 'utf8')).toBe('123'); | ||
expect('dir/a/f1.txt').toBeFileWithContent('abc'); | ||
expect('dir/a/b/c').toBeDirectory(); | ||
expect('dir/a/b/f2.txt').toBeFileWithContent('123'); | ||
}; | ||
// SYNC | ||
@@ -118,5 +116,3 @@ preparations(); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
@@ -130,76 +126,100 @@ preparations(); | ||
}); | ||
it("returns undefined", function (done) { | ||
var preparations = function () { | ||
fse.outputFileSync('a/f1.txt', 'abc'); | ||
it("generates nice error if source path doesn't exist", function (done) { | ||
var expectations = function (err) { | ||
expect(err.code).toBe('ENOENT'); | ||
expect(err.message).toMatch(/^Path to copy doesn't exist/); | ||
}; | ||
// SYNC | ||
preparations(); | ||
var ret = jetpack.copy('a', 'dir/a'); | ||
expect(ret).toBe(undefined); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.copyAsync('a', 'dir/a') | ||
.then(function (ret) { | ||
expect(ret).toBe(undefined); | ||
done(); | ||
}); | ||
}); | ||
it("throws if source path doesn't exist", function (done) { | ||
// SYNC | ||
try { | ||
jetpack.copy('a', 'b', { allBut: ['c'] }); // allBut used because then jetpack code follows more comlicated path | ||
jetpack.copy('a', 'b'); | ||
throw "to make sure this code throws" | ||
} catch (err) { | ||
expect(err.code).toBe('ENOENT'); | ||
expectations(err); | ||
} | ||
// ASYNC | ||
jetpack.copyAsync('a', 'b', { allBut: ['c'] }) | ||
jetpack.copyAsync('a', 'b') | ||
.catch(function (err) { | ||
expect(err.code).toBe('ENOENT'); | ||
expectations(err); | ||
done(); | ||
}); | ||
}); | ||
it("respects internal CWD of jetpack instance", function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('a/b.txt', 'abc'); | ||
}; | ||
var expectations = function () { | ||
expect('a/b.txt').toBeFileWithContent('abc'); | ||
expect('a/x.txt').toBeFileWithContent('abc'); | ||
}; | ||
var jetContext = jetpack.cwd('a'); | ||
// SYNC | ||
preparations(); | ||
jetContext.copy('b.txt', 'x.txt'); | ||
expectations(); | ||
// ASYNC | ||
preparations(); | ||
jetContext.copyAsync('b.txt', 'x.txt') | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
describe('overwriting behaviour', function () { | ||
it("does not overwrite by default", function (done) { | ||
fse.outputFileSync('a/file.txt', 'abc'); | ||
fse.mkdirsSync('b'); | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('a/file.txt', 'abc'); | ||
fse.mkdirsSync('b'); | ||
}; | ||
var expectations = function (err) { | ||
expect(err.code).toBe('EEXIST'); | ||
expect(err.message).toMatch(/^Destination path already exists/); | ||
}; | ||
// SYNC | ||
preparations(); | ||
try { | ||
jetpack.copy('a', 'b'); | ||
throw "to make sure this code throws" | ||
throw "to make sure this code throws"; | ||
} catch (err) { | ||
expect(err.code).toBe('EEXIST'); | ||
expectations(err); | ||
} | ||
// ASYNC | ||
preparations(); | ||
jetpack.copyAsync('a', 'b') | ||
.catch(function (err) { | ||
expect(err.code).toBe('EEXIST'); | ||
expectations(err); | ||
done(); | ||
}); | ||
}); | ||
it("overwrites if it was specified", function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('a/file.txt', 'abc'); | ||
fse.mkdirsSync('b'); | ||
fse.outputFileSync('b/file.txt', 'xyz'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.readFileSync('a/file.txt', 'utf8')).toBe('abc'); | ||
expect(fse.readFileSync('b/file.txt', 'utf8')).toBe('abc'); | ||
expect('a/file.txt').toBeFileWithContent('abc'); | ||
expect('b/file.txt').toBeFileWithContent('abc'); | ||
}; | ||
// SYNC | ||
@@ -209,5 +229,3 @@ preparations(); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
@@ -221,35 +239,36 @@ preparations(); | ||
}); | ||
}); | ||
describe('mask matching', function () { | ||
it("copies ONLY", function (done) { | ||
describe('filter what to copy |', function () { | ||
it("copies only paths matching", function (done) { | ||
var preparations = function () { | ||
fse.outputFileSync('dir/f.txt', 'abc'); | ||
fse.outputFileSync('dir/f.doc', 'xyz'); | ||
fse.outputFileSync('dir/b/f.txt', '123'); | ||
fse.mkdirsSync('dir/a/b/c'); | ||
fse.mkdirsSync('dir/z'); | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('dir/file.txt', '1'); | ||
fse.outputFileSync('dir/file.md', 'm1'); | ||
fse.outputFileSync('dir/a/file.txt', '2'); | ||
fse.outputFileSync('dir/a/file.md', 'm2'); | ||
fse.outputFileSync('dir/a/b/file.txt', '3'); | ||
fse.outputFileSync('dir/a/b/file.md', 'm3'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.existsSync('copy/f.txt')).toBe(true); | ||
expect(fse.existsSync('copy/f.doc')).toBe(false); | ||
expect(fse.existsSync('copy/b/f.txt')).toBe(true); | ||
expect(fse.existsSync('copy/a/b/c')).toBe(true); | ||
expect(fse.existsSync('copy/z')).toBe(false); | ||
expect('copy/file.txt').toBeFileWithContent('1'); | ||
expect('copy/file.md').not.toExist(); | ||
expect('copy/a/file.txt').toBeFileWithContent('2'); | ||
expect('copy/a/file.md').not.toExist(); | ||
expect('copy/a/b/file.txt').toBeFileWithContent('3'); | ||
expect('copy/a/b/file.md').not.toExist(); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.copy('dir', 'copy', { only: ['*.txt', 'dir/a/b'] }); | ||
jetpack.copy('dir', 'copy', { matching: '*.txt' }); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.copyAsync('dir', 'copy', { only: ['*.txt', 'dir/a/b'] }) | ||
jetpack.copyAsync('dir', 'copy', { matching: '*.txt' }) | ||
.then(function () { | ||
@@ -260,26 +279,26 @@ expectations(); | ||
}); | ||
it("ONLY works also for files", function (done) { | ||
// this is not especially useful, but logical | ||
// continuation of how this feature works on directories | ||
it("copies only paths matching and anchored to ./", function (done) { | ||
var preparations = function () { | ||
fse.outputFileSync('a', '123'); | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('dir/file.txt', '1'); | ||
fse.outputFileSync('dir/a/file.txt', '2'); | ||
fse.outputFileSync('dir/a/b/file.txt', '3'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.existsSync('b')).toBe(true); | ||
expect('copy/file.txt').not.toExist(); | ||
expect('copy/a/file.txt').toBeFileWithContent('2'); | ||
expect('copy/a/b/file.txt').not.toExist(); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.copy('a', 'b', { only: ['a'] }); | ||
jetpack.copy('dir', 'copy', { matching: './a/*.txt' }); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.copyAsync('a', 'b', { only: ['a'] }) | ||
jetpack.copyAsync('dir', 'copy', { matching: './a/*.txt' }) | ||
.then(function () { | ||
@@ -290,31 +309,22 @@ expectations(); | ||
}); | ||
it("copies ALLBUT", function (done) { | ||
it("works also if copying single file", function (done) { | ||
var preparations = function () { | ||
fse.outputFileSync('a/f.txt', 'abc'); | ||
fse.outputFileSync('a/f.doc', 'xyz'); | ||
fse.outputFileSync('a/b/f.txt', '123'); | ||
fse.mkdirsSync('a/b/c/d'); | ||
fse.mkdirsSync('a/c'); | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('a', '1'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.existsSync('a1/f.txt')).toBe(false); | ||
expect(fse.existsSync('a1/b/f.txt')).toBe(false); | ||
expect(fse.existsSync('a1/b/c')).toBe(false); | ||
expect(fse.existsSync('a1/c')).toBe(true); | ||
expect(fse.existsSync('a1/f.doc')).toBe(true); | ||
expect('b').not.toExist(); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.copy('a', 'a1', { allBut: ['*.txt', 'a/b/c'] }); | ||
jetpack.copy('a', 'b', { matching: 'x' }); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.copyAsync('a', 'a1', { allBut: ['*.txt', 'a/b/c'] }) | ||
jetpack.copyAsync('a', 'b', { matching: 'x' }) | ||
.then(function () { | ||
@@ -325,26 +335,22 @@ expectations(); | ||
}); | ||
it("ALLBUT works also for files", function () { | ||
// this is not especially useful, but logical | ||
// continuation of how this feature works on directories | ||
it('can copy empty directory', function (done) { | ||
var preparations = function () { | ||
fse.outputFileSync('a', '123'); | ||
helper.clearWorkingDir(); | ||
fse.mkdirsSync('dir/a/b'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.existsSync('b')).toBe(false); | ||
expect('copy/a/b').toBeDirectory(); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.copy('a', 'b', { allBut: ['a'] }); | ||
jetpack.copy('dir', 'copy', { matching: 'b' }); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.copyAsync('a', 'b', { allBut: ['a'] }) | ||
jetpack.copyAsync('dir', 'copy', { matching: 'b' }) | ||
.then(function () { | ||
@@ -355,25 +361,33 @@ expectations(); | ||
}); | ||
it("ONLY takes precedence over ALLBUT", function () { | ||
}); | ||
describe('*nix specyfic |', function () { | ||
if (process.platform === 'win32') { | ||
return; | ||
} | ||
it('copies also file permissions', function (done) { | ||
var preparations = function () { | ||
fse.outputFileSync('dir/a.txt', 'abc'); | ||
fse.outputFileSync('dir/b.txt', 'abc'); | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('a/b/c.txt', 'abc'); | ||
fse.chmodSync('a/b', '700'); | ||
fse.chmodSync('a/b/c.txt', '711'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.existsSync('copy/a.txt')).toBe(true); | ||
expect(fse.existsSync('copy/b.txt')).toBe(false); | ||
expect('x/b').toHaveMode('700'); | ||
expect('x/b/c.txt').toHaveMode('711'); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.copy('dir', 'copy', { only: ['a.*'], allBut: ['b.*'] }); | ||
jetpack.copy('a', 'x'); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
// AYNC | ||
preparations(); | ||
jetpack.copyAsync('dir', 'copy', { only: ['a.*'], allBut: ['b.*'] }) | ||
jetpack.copyAsync('a', 'x') | ||
.then(function () { | ||
@@ -384,42 +398,5 @@ expectations(); | ||
}); | ||
}); | ||
if (process.platform !== 'win32') { | ||
describe('*nix specyfic |', function () { | ||
it('copies also file permissions', function (done) { | ||
var preparations = function () { | ||
fse.outputFileSync('a/b/c.txt', 'abc'); | ||
fse.chmodSync('a/b', '700'); | ||
fse.chmodSync('a/b/c.txt', '711'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.statSync('a1/b').mode.toString(8)).toBe('40700'); | ||
expect(fse.statSync('a1/b/c.txt').mode.toString(8)).toBe('100711'); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.copy('a', 'a1'); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// AYNC | ||
preparations(); | ||
jetpack.copyAsync('a', 'a1') | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
} | ||
}); |
"use strict"; | ||
describe('cwd', function () { | ||
var pathUtil = require('path'); | ||
var jetpack = require('..'); | ||
it('returns the same path as process.cwd for main instance of jetpack', function () { | ||
expect(jetpack.cwd()).toBe(process.cwd()); | ||
}); | ||
it('can create new context with different cwd', function () { | ||
var jetCwd = jetpack.cwd('/'); // absolute path | ||
expect(jetCwd.cwd()).toBe(pathUtil.resolve(process.cwd(), '/')); | ||
jetCwd = jetpack.cwd('../..'); // relative path | ||
expect(jetCwd.cwd()).toBe(pathUtil.resolve(process.cwd(), '../..')); | ||
expect(jetpack.cwd()).toBe(process.cwd()); // cwd of main lib should be intact | ||
}); | ||
it('cwd contexts can be created recursively', function () { | ||
var jetCwd1, jetCwd2; | ||
jetCwd1 = jetpack.cwd('..'); | ||
expect(jetCwd1.cwd()).toBe(pathUtil.resolve(process.cwd(), '..')); | ||
jetCwd2 = jetCwd1.cwd('..'); | ||
expect(jetCwd2.cwd()).toBe(pathUtil.resolve(process.cwd(), '../..')); | ||
}); | ||
it('cwd can join path parts', function () { | ||
@@ -36,3 +36,3 @@ var jetCwd = jetpack.cwd('a', 'b', 'c'); | ||
}); | ||
}); |
"use strict"; | ||
describe('dir |', function () { | ||
var fse = require('fs-extra'); | ||
var pathUtil = require('path'); | ||
var helper = require('./helper'); | ||
var helper = require('./support/spec_helper'); | ||
var jetpack = require('..'); | ||
beforeEach(helper.beforeEach); | ||
afterEach(helper.afterEach); | ||
describe('ensure dir exists |', function () { | ||
it("creates dir if it doesn't exist", function (done) { | ||
var expectations = function (jetpackContext) { | ||
expect(fse.existsSync('x')).toBe(true); | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
}; | ||
var expectations = function () { | ||
expect('x').toBeDirectory(); | ||
}; | ||
// SYNC | ||
var jetpackContext = jetpack.dir('x'); | ||
expectations(jetpackContext); | ||
helper.clearWorkingDir(); | ||
preparations(); | ||
jetpack.dir('x'); | ||
expectations(); | ||
//ASYNC | ||
preparations(); | ||
jetpack.dirAsync('x') | ||
.then(function (jetpackContext) { | ||
expectations(jetpackContext); | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
it('does nothing if dir already exists', function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
fse.mkdirsSync('x'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.existsSync('x')).toBe(true); | ||
expect('x').toBeDirectory(); | ||
}; | ||
// SYNC | ||
@@ -49,5 +54,3 @@ preparations(); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
//ASYNC | ||
@@ -61,67 +64,21 @@ preparations(); | ||
}); | ||
it('creates nested directories if necessary', function (done) { | ||
var path = 'a/b/c'; | ||
var expectations = function () { | ||
expect(fse.existsSync(path)).toBe(true); | ||
}; | ||
// SYNC | ||
jetpack.dir(path); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
jetpack.dirAsync(path) | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('ensure dir does not exist |', function () { | ||
it("dir exists and it shouldn't", function (done) { | ||
var preparations = function () { | ||
fse.mkdirsSync('x'); | ||
helper.clearWorkingDir(); | ||
}; | ||
var expectations = function (jetpackContext) { | ||
expect(fse.existsSync('x')).toBe(false); | ||
var expectations = function () { | ||
expect('a/b/c').toBeDirectory(); | ||
}; | ||
// SYNC | ||
preparations(); | ||
var jetpackContext = jetpack.dir('x', { exists: false }); | ||
expectations(jetpackContext); | ||
helper.clearWorkingDir(); | ||
jetpack.dir('a/b/c'); | ||
expectations(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.dirAsync('x', { exists: false }) | ||
.then(function (jetpackContext) { | ||
expectations(jetpackContext); | ||
done(); | ||
}); | ||
}); | ||
it("dir already doesn't exist", function (done) { | ||
var expectations = function () { | ||
expect(fse.existsSync('x')).toBe(false); | ||
}; | ||
// SYNC | ||
jetpack.dir('x', { exists: false }); | ||
expectations(); | ||
// ASYNC | ||
jetpack.dirAsync('x', { exists: false }) | ||
jetpack.dirAsync('a/b/c') | ||
.then(function () { | ||
@@ -132,17 +89,18 @@ expectations(); | ||
}); | ||
}); | ||
describe('ensures dir empty |', function () { | ||
it('not bothers about emptiness if not specified', function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
fse.mkdirsSync('a/b'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.existsSync('a/b')).toBe(true); | ||
expect('a/b').toExist(); | ||
}; | ||
// SYNC | ||
@@ -152,5 +110,3 @@ preparations(); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
@@ -164,13 +120,15 @@ preparations(); | ||
}); | ||
it('makes dir empty', function (done) { | ||
var preparations = function () { | ||
fse.mkdirsSync('a/b'); | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('a/b/file.txt', 'abc'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.existsSync('a/b')).toBe(false); | ||
expect('a/b/file.txt').not.toExist(); | ||
expect('a').toExist(); | ||
}; | ||
// SYNC | ||
@@ -180,5 +138,3 @@ preparations(); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
@@ -192,51 +148,50 @@ preparations(); | ||
}); | ||
}); | ||
it('EXISTS=false takes precendence over EMPTY', function (done) { | ||
it('if given path is file, deletes it and places dir instead', function (done) { | ||
var preparations = function () { | ||
fse.mkdirsSync('a/b'); | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('a', 'abc'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.existsSync('a')).toBe(false); | ||
expect('a').toBeDirectory(); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.dir('a', { exists: false, empty: true }); | ||
jetpack.dir('a'); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.dirAsync('a', { exists: false, empty: true }) | ||
jetpack.dirAsync('a') | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}) | ||
}); | ||
}); | ||
it('if given path is file, deletes it and places dir instead', function (done) { | ||
it("respects internal CWD of jetpack instance", function (done) { | ||
var preparations = function () { | ||
fse.outputFileSync('a', 'abc'); | ||
helper.clearWorkingDir(); | ||
}; | ||
var expectations = function () { | ||
expect(fse.statSync('a').isDirectory()).toBe(true); | ||
expect('a/b').toBeDirectory(); | ||
}; | ||
var jetContext = jetpack.cwd('a'); | ||
// SYNC | ||
preparations(); | ||
jetpack.dir('a'); | ||
jetContext.dir('b'); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.dirAsync('a') | ||
jetContext.dirAsync('b') | ||
.then(function () { | ||
@@ -247,175 +202,183 @@ expectations(); | ||
}); | ||
describe("returns", function () { | ||
it("returns jetack instance pointing on this directory if EXISTS=true", function (done) { | ||
it("returns jetack instance pointing on this directory", function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
}; | ||
var expectations = function (ret) { | ||
expect(ret.cwd()).toBe(pathUtil.resolve('a')); | ||
}; | ||
// SYNC | ||
preparations(); | ||
var ret = jetpack.dir('a'); | ||
expectations(ret); | ||
// ASYNC | ||
preparations(); | ||
jetpack.dirAsync('a') | ||
.then(function (ret) { | ||
expectations(ret); | ||
done(); | ||
}); | ||
}); | ||
describe('windows specyfic |', function () { | ||
if (process.platform !== 'win32') { | ||
return; | ||
} | ||
it('specyfying mode have no effect, and throws no error', function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
}; | ||
var expectations = function (ret) { | ||
expect(ret.cwd()).toBe(pathUtil.resolve('a')); | ||
expect('x').toBeDirectory(); | ||
}; | ||
// SYNC | ||
var ret = jetpack.dir('a'); | ||
expectations(ret); | ||
helper.clearWorkingDir(); | ||
preparations(); | ||
jetpack.dir('x', { mode: '511' }); | ||
expectations(); | ||
// ASYNC | ||
jetpack.dirAsync('a') | ||
.then(function (ret) { | ||
expectations(ret); | ||
preparations(); | ||
jetpack.dirAsync('x', { mode: '511' }) | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
it("returns undefined if EXISTS=false", function (done) { | ||
var expectations = function (ret) { | ||
expect(ret).toBe(undefined); | ||
}); | ||
describe('*nix specyfic |', function () { | ||
if (process.platform === 'win32') { | ||
return; | ||
} | ||
// Tests assume umask is not greater than 022 | ||
it('sets mode to newly created directory', function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
}; | ||
var expectations = function () { | ||
expect('a').toHaveMode('511'); | ||
}; | ||
// SYNC | ||
var ret = jetpack.dir('a', { exists: false }); | ||
expectations(ret); | ||
helper.clearWorkingDir(); | ||
// mode as string | ||
preparations(); | ||
jetpack.dir('a', { mode: '511' }); | ||
expectations(); | ||
// mode as number | ||
preparations(); | ||
jetpack.dir('a', { mode: parseInt('511', 8) }); | ||
expectations(); | ||
// ASYNC | ||
jetpack.dirAsync('a', { exists: false }) | ||
.then(function (ret) { | ||
expectations(ret); | ||
// mode as string | ||
preparations(); | ||
jetpack.dirAsync('a', { mode: '511' }) | ||
.then(function () { | ||
expectations(); | ||
// mode as number | ||
preparations(); | ||
return jetpack.dirAsync('a', { mode: parseInt('511', 8) }); | ||
}) | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
if (process.platform === 'win32') { | ||
describe('windows specyfic |', function () { | ||
it('specyfying mode have no effect, and throws no error', function (done) { | ||
// SYNC | ||
jetpack.dir('x', { mode: '511' }); | ||
it('sets that mode to every created directory', function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
jetpack.dirAsync('x', { mode: '511' }) | ||
.then(function () { | ||
done(); | ||
}); | ||
}; | ||
var expectations = function () { | ||
expect('a').toHaveMode('711'); | ||
expect('a/b').toHaveMode('711'); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.dir('a/b', { mode: '711' }); | ||
expectations(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.dirAsync('a/b', { mode: '711' }) | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
} else { | ||
describe('*nix specyfic |', function () { | ||
// tests assume umask is not greater than 022 | ||
it('sets mode to newly created directory', function (done) { | ||
var expectations = function () { | ||
expect(fse.statSync('a').mode.toString(8)).toBe('40511'); | ||
}; | ||
// SYNC | ||
// mode as string | ||
jetpack.dir('a', { mode: '511' }); | ||
expectations(); | ||
it('changes mode of existing directory to desired', function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
// mode as number | ||
jetpack.dir('a', { mode: parseInt('511', 8) }); | ||
fse.mkdirSync('a', '777'); | ||
}; | ||
var expectations = function () { | ||
expect('a').toHaveMode('511'); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.dir('a', { mode: '511' }); | ||
expectations(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.dirAsync('a', { mode: '511' }) | ||
.then(function () { | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
// mode as string | ||
jetpack.dirAsync('a', { mode: '511' }) | ||
.then(function () { | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// mode as number | ||
return jetpack.dirAsync('a', { mode: parseInt('511', 8) }); | ||
}) | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
done(); | ||
}); | ||
it('sets that mode to every created directory', function (done) { | ||
var expectations = function () { | ||
expect(fse.statSync('a').mode.toString(8)).toBe('40711'); | ||
expect(fse.statSync('a/b').mode.toString(8)).toBe('40711'); | ||
}; | ||
// SYNC | ||
jetpack.dir('a/b', { mode: '711' }); | ||
expectations(); | ||
}); | ||
it('leaves mode of directory intact if this option was not specified', function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
jetpack.dirAsync('a/b', { mode: '711' }) | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
it('changes mode of existing directory to desired', function (done) { | ||
var preparations = function () { | ||
fse.mkdirSync('a', '777'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.statSync('a').mode.toString(8)).toBe('40511'); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.dir('a', { mode: '511' }); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.dirAsync('a', { mode: '511' }) | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
it('leaves mode of directory intact if not specified', function (done) { | ||
var expectations = function () { | ||
expect(fse.statSync('a').mode.toString(8)).toBe('40700'); | ||
}; | ||
fse.mkdirSync('a', '700'); | ||
// SYNC | ||
jetpack.dir('a'); | ||
}; | ||
var expectations = function () { | ||
expect('a').toHaveMode('700'); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.dir('a'); | ||
expectations(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.dirAsync('a') | ||
.then(function () { | ||
expectations(); | ||
// ASYNC | ||
jetpack.dirAsync('x') | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
done(); | ||
}); | ||
}); | ||
} | ||
}); | ||
}); |
@@ -7,3 +7,3 @@ "use strict"; | ||
var pathUtil = require('path'); | ||
var helper = require('./helper'); | ||
var helper = require('./support/spec_helper'); | ||
var jetpack = require('..'); | ||
@@ -18,3 +18,3 @@ | ||
expect(exists).toBe(false); | ||
// ASYNC | ||
@@ -30,7 +30,7 @@ jetpack.existsAsync('file.txt') | ||
fse.mkdirsSync('a'); | ||
// SYNC | ||
var exists = jetpack.exists('a'); | ||
expect(exists).toBe('dir'); | ||
// ASYNC | ||
@@ -46,7 +46,7 @@ jetpack.existsAsync('a') | ||
fse.outputFileSync('text.txt', 'abc'); | ||
// SYNC | ||
var exists = jetpack.exists('text.txt'); | ||
expect(exists).toBe('file'); | ||
// ASYNC | ||
@@ -60,2 +60,19 @@ jetpack.existsAsync('text.txt') | ||
it("respects internal CWD of jetpack instance", function (done) { | ||
fse.outputFileSync('a/text.txt', 'abc'); | ||
var jetContext = jetpack.cwd('a'); | ||
// SYNC | ||
var exists = jetContext.exists('text.txt'); | ||
expect(exists).toBe('file'); | ||
// ASYNC | ||
jetContext.existsAsync('text.txt') | ||
.then(function (exists) { | ||
expect(exists).toBe('file'); | ||
done(); | ||
}); | ||
}); | ||
}); |
"use strict"; | ||
describe('file |', function () { | ||
var fse = require('fs-extra'); | ||
var pathUtil = require('path'); | ||
var helper = require('./helper'); | ||
var helper = require('./support/spec_helper'); | ||
var jetpack = require('..'); | ||
beforeEach(helper.beforeEach); | ||
afterEach(helper.afterEach); | ||
describe('ensure file exists |', function () { | ||
it("file doesn't exists", function (done) { | ||
it("file doesn't exist before call", function (done) { | ||
var prepartions = function () { | ||
helper.clearWorkingDir(); | ||
}; | ||
var expectations = function () { | ||
expect(fse.existsSync('file.txt')).toBe(true); | ||
expect('file.txt').toBeFileWithContent(''); | ||
}; | ||
// SYNC | ||
prepartions(); | ||
jetpack.file('file.txt'); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
prepartions(); | ||
jetpack.fileAsync('file.txt') | ||
@@ -34,13 +38,14 @@ .then(function () { | ||
}); | ||
it("file already exists", function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('file.txt', 'abc'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.existsSync('file.txt')).toBe(true); | ||
expect('file.txt').toBeFileWithContent('abc'); | ||
}; | ||
// SYNC | ||
@@ -50,5 +55,3 @@ preparations(); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
@@ -62,43 +65,25 @@ preparations(); | ||
}); | ||
}); | ||
describe('ensure file does not exist |', function () { | ||
it('file already does not exist', function (done) { | ||
var expectations = function () { | ||
expect(fse.existsSync('file.txt')).toBe(false); | ||
}; | ||
// SYNC | ||
jetpack.file('file.txt', { exists: false }); | ||
expectations(); | ||
// ASYNC | ||
jetpack.fileAsync('file.txt', { exists: false }) | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
it('file already does not exist', function (done) { | ||
describe('ensures file content |', function () { | ||
it("from string", function (done) { | ||
var preparations = function () { | ||
fse.outputFileSync('file.txt', 'abc'); | ||
helper.clearWorkingDir(); | ||
}; | ||
var expectations = function () { | ||
expect(fse.existsSync('file.txt')).toBe(false); | ||
expect('file.txt').toBeFileWithContent('ąbć'); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.file('file.txt', { exists: false }); | ||
jetpack.file('file.txt', { content: 'ąbć' }); | ||
expectations(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.fileAsync('file.txt', { exists: false }) | ||
jetpack.fileAsync('file.txt', { content: 'ąbć' }) | ||
.then(function () { | ||
@@ -109,90 +94,23 @@ expectations(); | ||
}); | ||
}); | ||
describe('ensures file empty |', function () { | ||
it('not bothers about file emptiness if not explicitly specified', function (done) { | ||
var expectations = function () { | ||
expect(fse.readFileSync('file.txt', 'utf8')).toBe('abc'); | ||
}; | ||
fse.outputFileSync('file.txt', 'abc'); | ||
// SYNC | ||
jetpack.file('file.txt'); | ||
expectations(); | ||
// ASYNC | ||
jetpack.fileAsync('file.txt') | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
it('makes sure file is empty if specified', function (done) { | ||
it("from buffer", function (done) { | ||
var preparations = function () { | ||
fse.outputFileSync('file.txt', 'abc'); | ||
helper.clearWorkingDir(); | ||
}; | ||
var expectations = function () { | ||
expect(fse.readFileSync('file.txt', 'utf8')).toBe(''); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.file('file.txt', { empty: true }); | ||
expectations(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.fileAsync('file.txt', { empty: true }) | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('ensures file content |', function () { | ||
it("from string", function (done) { | ||
var expectations = function () { | ||
expect(fse.readFileSync('file.txt', 'utf8')).toBe('ąbć'); | ||
}; | ||
// SYNC | ||
jetpack.file('file.txt', { content: 'ąbć' }); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
jetpack.fileAsync('file.txt', { content: 'ąbć' }) | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
it("from buffer", function (done) { | ||
var expectations = function () { | ||
var buf = fse.readFileSync('file'); | ||
expect(buf[0]).toBe(11); | ||
expect(buf[1]).toBe(22); | ||
expect(buf.length).toBe(2); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.file('file', { content: new Buffer([11, 22]) }); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.fileAsync('file', { content: new Buffer([11, 22]) }) | ||
@@ -204,7 +122,11 @@ .then(function () { | ||
}); | ||
it("from object (json)", function (done) { | ||
var obj = { a: "abc", b: 123 }; | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
}; | ||
var expectations = function () { | ||
@@ -214,10 +136,10 @@ var data = JSON.parse(fse.readFileSync('file.txt', 'utf8')); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.file('file.txt', { content: obj }); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.fileAsync('file.txt', { content: obj }) | ||
@@ -229,26 +151,36 @@ .then(function () { | ||
}); | ||
it('written JSON data can be indented', function (done) { | ||
var obj = { a: "abc", b: 123 }; | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
}; | ||
var expectations = function (content) { | ||
var sizeA = fse.statSync('a.json').size; | ||
var sizeB = fse.statSync('b.json').size; | ||
var sizeC = fse.statSync('c.json').size; | ||
expect(sizeB).toBeGreaterThan(sizeA); | ||
expect(sizeC).toBeGreaterThan(sizeB); | ||
}; | ||
// SYNC | ||
jetpack.file('a.json', { content: obj }); | ||
jetpack.file('b.json', { content: obj, jsonIndent: 4 }); | ||
preparations(); | ||
jetpack.file('a.json', { content: obj, jsonIndent: 0 }); | ||
jetpack.file('b.json', { content: obj }); // Default indent = 2 | ||
jetpack.file('c.json', { content: obj, jsonIndent: 4 }); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
jetpack.fileAsync('a.json', { content: obj }) | ||
preparations(); | ||
jetpack.fileAsync('a.json', { content: obj, jsonIndent: 0 }) | ||
.then(function () { | ||
return jetpack.fileAsync('b.json', { content: obj, jsonIndent: 4 }); | ||
return jetpack.fileAsync('b.json', { content: obj }); // Default indent = 2 | ||
}) | ||
.then(function () { | ||
return jetpack.fileAsync('c.json', { content: obj, jsonIndent: 4 }); | ||
}) | ||
.then(function () { | ||
expectations(); | ||
@@ -258,13 +190,14 @@ done(); | ||
}); | ||
it("replaces content of already existing file", function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
fse.writeFileSync('file.txt', 'abc'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.readFileSync('file.txt', 'utf8')).toBe('123'); | ||
expect('file.txt').toBeFileWithContent('123'); | ||
}; | ||
// SYNC | ||
@@ -274,5 +207,3 @@ preparations(); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
@@ -286,16 +217,17 @@ preparations(); | ||
}); | ||
}); | ||
it('if given path is directory, should delete it and place file instead', function (done) { | ||
var preparations = function () { | ||
// create nested directories to be sure we can delete non-empty dir | ||
helper.clearWorkingDir(); | ||
// Create nested directories to be sure we can delete non-empty dir. | ||
fse.outputFileSync('a/b/c.txt', 'abc'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.statSync('a').isFile()).toBe(true); | ||
expect('a').toBeFileWithContent(''); | ||
}; | ||
// SYNC | ||
@@ -305,5 +237,3 @@ preparations(); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
@@ -317,16 +247,20 @@ preparations(); | ||
}); | ||
it("if directory for file doesn't exist creates it too", function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
}; | ||
var expectations = function () { | ||
expect(fse.existsSync('a/b/c.txt')).toBe(true); | ||
expect('a/b/c.txt').toBeFileWithContent(''); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.file('a/b/c.txt'); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.fileAsync('a/b/c.txt') | ||
@@ -338,8 +272,8 @@ .then(function () { | ||
}); | ||
it('returns jetpack object', function (done) { | ||
it('returns currently used jetpack instance', function (done) { | ||
// SYNC | ||
var jetpackContext = jetpack.file('file.txt'); | ||
expect(jetpackContext).toBe(jetpack); | ||
// ASYNC | ||
@@ -352,51 +286,115 @@ jetpack.fileAsync('file.txt') | ||
}); | ||
describe('parameters importance |', function () { | ||
it('EXISTS=false takes precedence over EMPTY and CONTENT', function (done) { | ||
it("respects internal CWD of jetpack instance", function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
}; | ||
var expectations = function () { | ||
expect('a/b.txt').toBeFileWithContent(''); | ||
}; | ||
var jetContext = jetpack.cwd('a'); | ||
// SYNC | ||
preparations(); | ||
jetContext.file('b.txt'); | ||
expectations(); | ||
// ASYNC | ||
preparations(); | ||
jetContext.fileAsync('b.txt') | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
describe('windows specyfic |', function () { | ||
if (process.platform !== 'win32') { | ||
return; | ||
} | ||
it('specyfying mode should have no effect, and throw no error', function (done) { | ||
// SYNC | ||
jetpack.file('file.txt', { mode: '511' }); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
jetpack.fileAsync('file.txt', { mode: '511' }) | ||
.then(function () { | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('*nix specyfic |', function () { | ||
if (process.platform === 'win32') { | ||
return; | ||
} | ||
// Tests assume umask is not greater than 022 | ||
it('sets mode of newly created file', function (done) { | ||
var preparations = function () { | ||
fse.writeFileSync('file.txt', 'abc'); | ||
helper.clearWorkingDir(); | ||
}; | ||
var expectations = function () { | ||
expect(fse.existsSync('file.txt')).toBe(false); | ||
expect('file.txt').toHaveMode('511'); | ||
}; | ||
// SYNC | ||
// mode as string | ||
preparations(); | ||
jetpack.file('file.txt', { exists: false, empty: true, content: '123' }); | ||
jetpack.file('file.txt', { mode: '511' }); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
// mode as number | ||
preparations(); | ||
jetpack.fileAsync('file.txt', { exists: false, empty: true, content: '123' }) | ||
jetpack.file('file.txt', { mode: parseInt('511', 8) }); | ||
expectations(); | ||
// AYNC | ||
// mode as string | ||
preparations(); | ||
jetpack.fileAsync('file.txt', { mode: '511' }) | ||
.then(function () { | ||
expectations(); | ||
// mode as number | ||
preparations(); | ||
return jetpack.fileAsync('file.txt', { mode: parseInt('511', 8) }); | ||
}) | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
it('EMPTY=true takes precedence over CONTENT', function (done) { | ||
it("changes mode of existing file if doesn't match", function (done) { | ||
var preparations = function () { | ||
fse.writeFileSync('file.txt', 'abc'); | ||
helper.clearWorkingDir(); | ||
fse.writeFileSync('file.txt', 'abc', { mode: '700' }); | ||
}; | ||
var expectations = function () { | ||
expect(fse.readFileSync('file.txt', 'utf8')).toBe(''); | ||
expect('file.txt').toHaveMode('511'); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.file('file.txt', { empty: true, content: '123' }); | ||
jetpack.file('file.txt', { mode: '511' }); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.fileAsync('file.txt', { empty: true, content: '123' }) | ||
jetpack.fileAsync('file.txt', { mode: '511' }) | ||
.then(function () { | ||
@@ -407,135 +405,51 @@ expectations(); | ||
}); | ||
}); | ||
if (process.platform === 'win32') { | ||
describe('windows specyfic |', function () { | ||
it('specyfying mode should have no effect, and throw no error', function (done) { | ||
// SYNC | ||
jetpack.file('file.txt', { mode: '511' }); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
jetpack.fileAsync('file.txt', { mode: '511' }) | ||
.then(function () { | ||
done(); | ||
}); | ||
}); | ||
}); | ||
} else { | ||
describe('*nix specyfic |', function () { | ||
// tests assume umask is not greater than 022 | ||
it('sets mode of created file', function (done) { | ||
var expectations = function () { | ||
expect(fse.statSync('file.txt').mode.toString(8)).toBe('100511'); | ||
}; | ||
// SYNC | ||
// mode as string | ||
jetpack.file('file.txt', { mode: '511' }); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// mode as number | ||
jetpack.file('file.txt', { mode: parseInt('511', 8) }); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// AYNC | ||
// mode as string | ||
jetpack.fileAsync('file.txt', { mode: '511' }) | ||
.then(function () { | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// mode as number | ||
return jetpack.fileAsync('file.txt', { mode: parseInt('511', 8) }); | ||
}) | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
it("changes mode of existing file if doesn't match", function (done) { | ||
var preparations = function () { | ||
fse.writeFileSync('file.txt', 'abc', { mode: '700' }); | ||
}; | ||
var expectations = function () { | ||
expect(fse.statSync('file.txt').mode.toString(8)).toBe('100511'); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.file('file.txt', { mode: '511' }); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.fileAsync('file.txt', { mode: '511' }) | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
it('leaves mode of file intact if not explicitly specified', function (done) { | ||
var expectations = function () { | ||
expect(fse.statSync('file.txt').mode.toString(8)).toBe('100700'); | ||
}; | ||
it('leaves mode of file intact if not explicitly specified', function (done) { | ||
var preparations = function () { | ||
fse.writeFileSync('file.txt', 'abc', { mode: '700' }); | ||
// SYNC | ||
// ensure exists | ||
jetpack.file('file.txt'); | ||
}; | ||
var expectations = function () { | ||
expect('file.txt').toHaveMode('700'); | ||
}; | ||
preparations(); | ||
// SYNC | ||
// ensure exists | ||
jetpack.file('file.txt'); | ||
expectations(); | ||
// make file empty | ||
jetpack.file('file.txt', { empty: true }); | ||
expectations(); | ||
// set file content | ||
jetpack.file('file.txt', { content: '123' }); | ||
expectations(); | ||
// AYNC | ||
// ensure exists | ||
jetpack.fileAsync('file.txt') | ||
.then(function () { | ||
expectations(); | ||
// make file empty | ||
jetpack.file('file.txt', { empty: true }); | ||
return jetpack.fileAsync('file.txt', { empty: true }); | ||
}) | ||
.then(function () { | ||
expectations(); | ||
// set file content | ||
jetpack.file('file.txt', { content: '123' }); | ||
return jetpack.fileAsync('file.txt', { content: '123' }); | ||
}) | ||
.then(function () { | ||
expectations(); | ||
// AYNC | ||
// ensure exists | ||
jetpack.fileAsync('file.txt') | ||
.then(function () { | ||
expectations(); | ||
// make file empty | ||
return jetpack.fileAsync('file.txt', { empty: true }); | ||
}) | ||
.then(function () { | ||
expectations(); | ||
// set file content | ||
return jetpack.fileAsync('file.txt', { content: '123' }); | ||
}) | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
done(); | ||
}); | ||
}); | ||
} | ||
}); | ||
}); |
@@ -7,3 +7,3 @@ "use strict"; | ||
var pathUtil = require('path'); | ||
var helper = require('./helper'); | ||
var helper = require('./support/spec_helper'); | ||
var jetpack = require('..'); | ||
@@ -15,12 +15,13 @@ | ||
it("moves file", function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('a/b.txt', 'abc'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.existsSync('a/b.txt')).toBe(false); | ||
expect(fse.readFileSync('c.txt', 'utf8')).toBe('abc'); | ||
expect('a/b.txt').not.toExist(); | ||
expect('c.txt').toBeFileWithContent('abc'); | ||
}; | ||
// SYNC | ||
@@ -30,5 +31,3 @@ preparations(); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
@@ -42,15 +41,16 @@ preparations(); | ||
}); | ||
it("moves directory", function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('a/b/c.txt', 'abc'); | ||
fse.mkdirsSync('x'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.existsSync('a')).toBe(false); | ||
expect(fse.readFileSync('x/y/b/c.txt', 'utf8')).toBe('abc'); | ||
expect('a').not.toExist(); | ||
expect('x/y/b/c.txt').toBeFileWithContent('abc'); | ||
}; | ||
// SYNC | ||
@@ -60,5 +60,3 @@ preparations(); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
@@ -72,21 +70,25 @@ preparations(); | ||
}); | ||
it("returns undefined", function (done) { | ||
it("creates nonexistent directories for destination path", function (done) { | ||
var preparations = function () { | ||
fse.outputFileSync('file.txt', 'abc'); | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('a.txt', 'abc'); | ||
}; | ||
var expectations = function () { | ||
expect('a.txt').not.toExist(); | ||
expect('a/b/z.txt').toBeFileWithContent('abc'); | ||
}; | ||
// SYNC | ||
preparations(); | ||
var ret = jetpack.move('file.txt', 'fiole.txt'); | ||
expect(ret).toBe(undefined); | ||
helper.clearWorkingDir(); | ||
jetpack.move('a.txt', 'a/b/z.txt'); | ||
expectations(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.moveAsync('file.txt', 'fiole.txt') | ||
.then(function (ret) { | ||
expect(ret).toBe(undefined); | ||
jetpack.moveAsync('a.txt', 'a/b/z.txt') | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
@@ -96,2 +98,53 @@ }); | ||
it("generates nice error when source path doesn't exist", function (done) { | ||
var expectations = function (err) { | ||
expect(err.code).toBe('ENOENT'); | ||
expect(err.message).toMatch(/^Path to move doesn't exist/); | ||
}; | ||
// SYNC | ||
try { | ||
jetpack.move('a', 'b'); | ||
throw "to make sure this code throws" | ||
} catch (err) { | ||
expectations(err); | ||
} | ||
// ASYNC | ||
jetpack.moveAsync('a', 'b') | ||
.catch(function (err) { | ||
expectations(err); | ||
done(); | ||
}); | ||
}); | ||
it("respects internal CWD of jetpack instance", function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('a/b.txt', 'abc'); | ||
}; | ||
var expectations = function () { | ||
expect('a/b.txt').not.toExist(); | ||
expect('a/x.txt').toBeFileWithContent('abc'); | ||
}; | ||
var jetContext = jetpack.cwd('a'); | ||
// SYNC | ||
preparations(); | ||
jetContext.move('b.txt', 'x.txt'); | ||
expectations(); | ||
// ASYNC | ||
preparations(); | ||
jetContext.moveAsync('b.txt', 'x.txt') | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
}); |
"use strict"; | ||
describe('path', function () { | ||
var pathUtil = require('path'); | ||
var jetpack = require('..'); | ||
it('if empty returns same path as cwd()', function () { | ||
@@ -13,12 +13,16 @@ expect(jetpack.path()).toBe(jetpack.cwd()); | ||
}); | ||
it('is absolute if prepending slash present', function () { | ||
expect(jetpack.path('/blah')).toBe(pathUtil.resolve('/blah')); | ||
}); | ||
it('resolves to cwd value', function () { | ||
var blah = pathUtil.join(jetpack.cwd(), 'blah'); | ||
expect(jetpack.path('blah')).toBe(blah); | ||
it('resolves to CWD path of this jetpack instance', function () { | ||
var a = pathUtil.join(jetpack.cwd(), 'a'); | ||
expect(jetpack.path('a')).toBe(a); | ||
// Create jetpack instance with other CWD | ||
var jetpackSubdir = jetpack.cwd('subdir'); | ||
var b = pathUtil.join(jetpack.cwd(), 'subdir', 'b'); | ||
expect(jetpackSubdir.path('b')).toBe(b); | ||
}); | ||
it('can take unlimited number of arguments as path parts', function () { | ||
@@ -28,3 +32,3 @@ var abc = pathUtil.join(jetpack.cwd(), 'a', 'b', 'c'); | ||
}); | ||
}); |
"use strict"; | ||
describe('remove', function () { | ||
var fse = require('fs-extra'); | ||
var pathUtil = require('path'); | ||
var helper = require('./helper'); | ||
var helper = require('./support/spec_helper'); | ||
var jetpack = require('..'); | ||
beforeEach(helper.beforeEach); | ||
afterEach(helper.afterEach); | ||
it("doesn't throw if path already doesn't exist", function (done) { | ||
// SYNC | ||
jetpack.remove('dir'); | ||
// ASYNC | ||
@@ -23,31 +23,46 @@ jetpack.removeAsync('dir') | ||
}); | ||
it("should delete file", function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('file.txt', 'abc'); | ||
}; | ||
var expectations = function () { | ||
expect('file.txt').not.toExist(); | ||
}; | ||
// SYNC | ||
fse.outputFileSync('file.txt', 'abc'); | ||
preparations(); | ||
jetpack.remove('file.txt'); | ||
expect(fse.existsSync('file.txt')).toBe(false); | ||
expectations(); | ||
// ASYNC | ||
fse.outputFileSync('file.txt', 'abc'); | ||
preparations(); | ||
jetpack.removeAsync('file.txt') | ||
.then(function () { | ||
expect(fse.existsSync('file.txt')).toBe(false); | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
it("removes directory with stuff inside", function (done) { | ||
function preparations() { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
fse.mkdirsSync('a/b/c'); | ||
fse.outputFileSync('a/f.txt', 'abc'); | ||
fse.outputFileSync('a/b/f.txt', '123'); | ||
} | ||
}; | ||
var expectations = function () { | ||
expect('a').not.toExist(); | ||
}; | ||
// SYNC | ||
preparations(); | ||
jetpack.remove('a'); | ||
expect(fse.existsSync('a')).toBe(false); | ||
expectations(); | ||
// ASYNC | ||
@@ -57,154 +72,35 @@ preparations(); | ||
.then(function () { | ||
expect(fse.existsSync('a')).toBe(false); | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
it("returns undefined", function (done) { | ||
it("respects internal CWD of jetpack instance", function (done) { | ||
var preparations = function () { | ||
fse.outputFileSync('file.txt', 'abc'); | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('a/b/c.txt', '123'); | ||
}; | ||
var expectations = function () { | ||
expect('a').toExist(); | ||
expect('a/b').not.toExist(); | ||
}; | ||
var jetContext = jetpack.cwd('a'); | ||
// SYNC | ||
preparations(); | ||
var ret = jetpack.remove('file.txt'); | ||
expect(ret).toBe(undefined); | ||
jetContext.remove('b') | ||
expectations(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.removeAsync('file.txt') | ||
.then(function (context) { | ||
expect(ret).toBe(undefined); | ||
jetContext.removeAsync('b') | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
describe('mask matching', function () { | ||
it("deletes ONLY", function (done) { | ||
function preparations() { | ||
fse.outputFileSync('a/f.txt', 'abc'); | ||
fse.outputFileSync('a/f.doc', 'abc'); | ||
fse.outputFileSync('a/b/f.txt', 'abc'); | ||
fse.mkdirsSync('a/b/tmp'); | ||
fse.mkdirsSync('a/tmp/c'); | ||
} | ||
function expectations() { | ||
expect(fse.existsSync('a/b/tmp')).toBe(false); | ||
expect(fse.existsSync('a/b')).toBe(true); | ||
expect(fse.existsSync('a/tmp')).toBe(false); | ||
expect(fse.existsSync('a/f.doc')).toBe(true); | ||
expect(fse.existsSync('a/f.txt')).toBe(false); | ||
expect(fse.existsSync('a/b/f.txt')).toBe(false); | ||
} | ||
// SYNC | ||
preparations(); | ||
jetpack.remove('a', { only: ['*.txt', 'tmp'] }); | ||
expectations(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.removeAsync('a', { only: ['*.txt', 'tmp'] }) | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
it("tests ONLY also against root path", function (done) { | ||
// SYNC | ||
fse.mkdirSync('a'); | ||
jetpack.remove('a', { only: ['a'] }); | ||
expect(fse.existsSync('a')).toBe(false); | ||
// ASYNC | ||
fse.mkdirSync('a'); | ||
jetpack.removeAsync('a', { only: ['a'] }) | ||
.then(function () { | ||
expect(fse.existsSync('a')).toBe(false); | ||
done(); | ||
}); | ||
}); | ||
it("deletes ALLBUT", function (done) { | ||
function preparations() { | ||
fse.mkdirsSync('a/b/tmp'); | ||
fse.mkdirsSync('a/tmp/c'); | ||
fse.writeFileSync('a/f.txt', 'abc'); | ||
fse.writeFileSync('a/f.doc', 'abc'); | ||
fse.writeFileSync('a/b/f.txt', 'abc'); | ||
fse.mkdirsSync('a/x/y'); | ||
} | ||
function expectations() { | ||
expect(fse.existsSync('a/b/tmp')).toBe(true); | ||
expect(fse.existsSync('a/tmp/c')).toBe(true); | ||
expect(fse.existsSync('a/f.doc')).toBe(false); | ||
expect(fse.existsSync('a/f.txt')).toBe(true); | ||
expect(fse.existsSync('a/b/f.txt')).toBe(true); | ||
expect(fse.existsSync('a/x')).toBe(false); | ||
} | ||
// SYNC | ||
preparations(); | ||
jetpack.remove('a', { allBut: ['*.txt', 'tmp'] }); | ||
expectations(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.removeAsync('a', { allBut: ['*.txt', 'tmp'] }) | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
it("tests ALLBUT also agains root path", function (done) { | ||
fse.mkdirSync('a'); | ||
// SYNC | ||
jetpack.remove('a', { allBut: ['a'] }); | ||
expect(fse.existsSync('a')).toBe(true); | ||
// ASYNC | ||
jetpack.removeAsync('a', { allBut: ['a'] }) | ||
.then(function () { | ||
expect(fse.existsSync('a')).toBe(true); | ||
done(); | ||
}); | ||
}); | ||
it("ONLY takes precedence over ALLBUT", function (done) { | ||
function preparations() { | ||
fse.outputFileSync('a/f.txt', 'abc'); | ||
fse.outputFileSync('a/f.doc', 'abc'); | ||
} | ||
function expectations() { | ||
expect(fse.existsSync('a/f.txt')).toBe(true); | ||
expect(fse.existsSync('a/f.doc')).toBe(false); | ||
} | ||
// SYNC | ||
preparations(); | ||
jetpack.remove('a', { only: ['f.doc'], allBut: ['f.txt'] }); | ||
expectations(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.removeAsync('a', { only: ['f.doc'], allBut: ['f.txt'] }) | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -7,3 +7,3 @@ "use strict"; | ||
var pathUtil = require('path'); | ||
var helper = require('./helper'); | ||
var helper = require('./support/spec_helper'); | ||
var jetpack = require('..'); | ||
@@ -13,14 +13,15 @@ | ||
afterEach(helper.afterEach); | ||
it("renames file", function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('a/b.txt', 'abc'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.existsSync('a/b.txt')).toBe(false); | ||
expect(fse.readFileSync('a/x.txt', 'utf8')).toBe('abc'); | ||
expect('a/b.txt').not.toExist(); | ||
expect('a/x.txt').toBeFileWithContent('abc'); | ||
}; | ||
// SYNC | ||
@@ -30,5 +31,3 @@ preparations(); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
@@ -42,14 +41,15 @@ preparations(); | ||
}); | ||
it("renames directory", function (done) { | ||
var preparations = function () { | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('a/b/c.txt', 'abc'); | ||
}; | ||
var expectations = function () { | ||
expect(fse.existsSync('a/b')).toBe(false); | ||
expect(fse.existsSync('a/x')).toBe(true); | ||
expect('a/b').not.toExist(); | ||
expect('a/x').toBeDirectory(); | ||
}; | ||
// SYNC | ||
@@ -59,5 +59,3 @@ preparations(); | ||
expectations(); | ||
helper.clearWorkingDir(); | ||
// ASYNC | ||
@@ -71,21 +69,27 @@ preparations(); | ||
}); | ||
it("returns undefined", function (done) { | ||
it("respects internal CWD of jetpack instance", function (done) { | ||
var preparations = function () { | ||
fse.outputFileSync('file.txt', 'abc'); | ||
helper.clearWorkingDir(); | ||
fse.outputFileSync('a/b/c.txt', 'abc'); | ||
}; | ||
var expectations = function () { | ||
expect('a/b').not.toExist(); | ||
expect('a/x').toBeDirectory(); | ||
}; | ||
var jetContext = jetpack.cwd('a'); | ||
// SYNC | ||
preparations(); | ||
var ret = jetpack.rename('file.txt', 'fiole.txt'); | ||
expect(ret).toBe(undefined); | ||
helper.clearWorkingDir(); | ||
jetContext.rename('b', 'x'); | ||
expectations(); | ||
// ASYNC | ||
preparations(); | ||
jetpack.renameAsync('file.txt', 'fiole.txt') | ||
.then(function (ret) { | ||
expect(ret).toBe(undefined); | ||
jetContext.renameAsync('b', 'x') | ||
.then(function () { | ||
expectations(); | ||
done(); | ||
@@ -92,0 +96,0 @@ }); |
@@ -7,3 +7,3 @@ "use strict"; | ||
var pathUtil = require('path'); | ||
var helper = require('./helper'); | ||
var helper = require('./support/spec_helper'); | ||
var jetpack = require('..'); | ||
@@ -13,10 +13,10 @@ | ||
afterEach(helper.afterEach); | ||
it("exposes vanilla stream methods", function (done) { | ||
fse.outputFileSync('a.txt', 'abc'); | ||
var output = jetpack.createWriteStream('b.txt'); | ||
var input = jetpack.createReadStream('a.txt'); | ||
output.on('finish', function () { | ||
expect(fse.readFileSync('b.txt', 'utf8')).toBe('abc'); | ||
expect('b.txt').toBeFileWithContent('abc'); | ||
done(); | ||
@@ -26,5 +26,6 @@ }); | ||
}); | ||
it("stream methods respect jetpack internal CWD", function (done) { | ||
fse.outputFileSync('dir/a.txt', 'abc'); | ||
var dir = jetpack.cwd('dir'); | ||
@@ -34,3 +35,3 @@ var output = dir.createWriteStream('b.txt'); | ||
output.on('finish', function () { | ||
expect(fse.readFileSync('dir/b.txt', 'utf8')).toBe('abc'); | ||
expect('dir/b.txt').toBeFileWithContent('abc'); | ||
done(); | ||
@@ -40,3 +41,3 @@ }); | ||
}); | ||
}); |
"use strict"; | ||
describe('matcher |', function () { | ||
var matcher = require('../../lib/utils/matcher'); | ||
it("can test against many masks", function () { | ||
it("can test against one pattern passed as a string", function () { | ||
var test = matcher.create('a'); | ||
expect(test('/a')).toBe(true); | ||
expect(test('/b')).toBe(false); | ||
}); | ||
it("can test against many patterns passed as an array", function () { | ||
var test = matcher.create(['a', 'b']); | ||
expect(test('/a')).toBe(true); | ||
expect(test('/b')).toBe(true); | ||
expect(test('/c')).toBe(false); | ||
}); | ||
describe('different mask types |', function () { | ||
it("without any slash", function () { | ||
// then just the last element on right has to match the mask | ||
var test = matcher.create(['b']); | ||
expect(test('/b')).toBe(true); | ||
expect(test('/a/b')).toBe(true); | ||
expect(test('/a/b/c')).toBe(false); | ||
}); | ||
it("with any slash inside", function () { | ||
// starts matching only from the left side | ||
var test = matcher.create(['a/b']); | ||
expect(test('/a/b')).toBe(true); | ||
expect(test('/x/a/b')).toBe(false); | ||
expect(test('/a/b/c')).toBe(false); | ||
test = matcher.create(['/a/b']); | ||
expect(test('/a/b')).toBe(true); | ||
expect(test('/x/a/b')).toBe(false); | ||
expect(test('/a/b/c')).toBe(false); | ||
}); | ||
}); | ||
describe('exclude part of path from matching |', function () { | ||
// if we are in dir "/a/b" and want to test contents inside | ||
// "b" then we have to exclude this "/a/b" part from matching, | ||
// otherwise it could mess up the match | ||
it("without slash in front", function () { | ||
var test = matcher.create(['a/**'], '/'); | ||
expect(test('/a/b/c')).toBe(true); | ||
test = matcher.create(['a/**'], '/a'); | ||
expect(test('/a/b/c')).toBe(false); | ||
test = matcher.create(['c'], '/'); | ||
expect(test('/a/b/c')).toBe(true); | ||
test = matcher.create(['c'], '/a/b'); | ||
expect(test('/a/b/c')).toBe(true); | ||
test = matcher.create(['c'], '/a/b/c'); | ||
expect(test('/a/b/c')).toBe(false); | ||
}); | ||
it("with slash in front", function () { | ||
var test = matcher.create(['/a/**'], '/'); | ||
expect(test('/a/b/c')).toBe(true); | ||
test = matcher.create(['/a/**'], '/a'); | ||
expect(test('/a/b/c')).toBe(false); | ||
test = matcher.create(['/c'], '/a'); | ||
expect(test('/a/b/c')).toBe(false); | ||
test = matcher.create(['/c'], '/a/b'); | ||
expect(test('/a/b/c')).toBe(true); | ||
test = matcher.create(['/c'], '/a/b/c'); | ||
expect(test('/a/b/c')).toBe(false); | ||
}); | ||
}); | ||
describe('possible mask tokens |', function () { | ||
it("*", function () { | ||
@@ -86,3 +26,3 @@ var test = matcher.create(['*']); | ||
expect(test('/a/b.txt')).toBe(true); | ||
test = matcher.create(['a*b']); | ||
@@ -93,3 +33,3 @@ expect(test('/ab')).toBe(true); | ||
}); | ||
it("/*", function () { | ||
@@ -100,3 +40,3 @@ var test = matcher.create(['/*']); | ||
}); | ||
it("**", function () { | ||
@@ -106,4 +46,4 @@ var test = matcher.create(['**']); | ||
expect(test('/a/b')).toBe(true); | ||
test = matcher.create(['a/**/d']); | ||
test = matcher.create(['/a/**/d']); | ||
expect(test('/a/d')).toBe(true); | ||
@@ -115,5 +55,5 @@ expect(test('/a/b/d')).toBe(true); | ||
}); | ||
it("**/", function () { | ||
var test = matcher.create(['**/a']); | ||
it("/**/something", function () { | ||
var test = matcher.create(['/**/a']); | ||
expect(test('/a')).toBe(true); | ||
@@ -124,3 +64,3 @@ expect(test('/x/a')).toBe(true); | ||
}); | ||
it("+(option1|option2)", function () { | ||
@@ -132,3 +72,3 @@ var test = matcher.create(['*.+(txt|md)']); | ||
}); | ||
it("?", function () { | ||
@@ -140,3 +80,3 @@ var test = matcher.create(['a?c']); | ||
}); | ||
it("characters #! have NO special meaning", function () { | ||
@@ -146,61 +86,9 @@ // these characters have meaning in .gitignore, but here should have not | ||
expect(test('/!a')).toBe(true); | ||
test = matcher.create(['#a']); | ||
expect(test('/#a')).toBe(true); | ||
}); | ||
}); | ||
it("can mark tree branches", function () { | ||
var tree = { | ||
name: 'a', | ||
type: 'dir', | ||
children: [ | ||
{ | ||
name: 'a', | ||
type: 'dir', | ||
children: [ | ||
{ | ||
name: 'a.txt', | ||
type: 'file', | ||
} | ||
] | ||
},{ | ||
name: 'b.txt', | ||
type: 'file', | ||
},{ | ||
name: 'c', | ||
type: 'dir', | ||
children: [ | ||
{ | ||
name: 'a.txt', | ||
type: 'file', | ||
} | ||
] | ||
} | ||
] | ||
}; | ||
var patterns = ['a/a', 'b.txt']; | ||
matcher.markTree(tree, patterns); | ||
expect(tree.matchPath).toBe('/a'); | ||
expect(tree.matches).toBe(false); | ||
expect(tree.children[0].matchPath).toBe('/a/a'); | ||
expect(tree.children[0].matches).toBe(true); | ||
expect(tree.children[0].children[0].matchPath).toBe('/a/a/a.txt'); | ||
expect(tree.children[0].children[0].matches).toBe(true); | ||
expect(tree.children[1].matchPath).toBe('/a/b.txt'); | ||
expect(tree.children[1].matches).toBe(true); | ||
expect(tree.children[2].matchPath).toBe('/a/c'); | ||
expect(tree.children[2].matches).toBe(false); | ||
expect(tree.children[2].children[0].matchPath).toBe('/a/c/a.txt'); | ||
expect(tree.children[2].children[0].matches).toBe(false); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
43
4343
527
172851
4
26
1
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbrace-expansion@1.1.11(transitive)
+ Addedconcat-map@0.0.1(transitive)
+ Addedminimatch@2.0.1(transitive)
- Removedlru-cache@2.7.3(transitive)
- Removedminimatch@0.3.0(transitive)
- Removedsigmund@1.0.1(transitive)
Updatedminimatch@2.0.1