Walk towards finishing FsResolver.

This commit is contained in:
André Cruz
2013-04-29 15:21:33 +01:00
parent b5054a7a7f
commit f11b2f6782
13 changed files with 527 additions and 117 deletions

View File

@@ -152,18 +152,9 @@ Resolver.prototype._applyPkgMeta = function (meta) {
if (meta.ignore && meta.ignore.length) {
return Q.nfcall(glob, '**/*', { cwd: this._tempDir, dot: true, mark: true })
.then(function (files) {
var filter = this._createIgnoreFilter(meta.ignore),
promises = [];
var promises = [];
// For each file that passes the ignore filter,
// rimraf it
files.forEach(function (file) {
if (filter(file)) {
promises.push(Q.nfcall(rimraf, file));
}
});
// Wait for all the rimraf's to finish
// TODO
return Q.all(promises);
}.bind(this))
.then(function () {
@@ -183,12 +174,4 @@ Resolver.prototype._savePkgMeta = function (meta) {
}.bind(this));
};
Resolver.prototype._createIgnoreFilter = function (ignore) {
var list = pathspec.RelPathList.parse(ignore);
return function (filename) {
return list.matches(filename);
};
};
module.exports = Resolver;

View File

@@ -1,33 +1,80 @@
var util = require('util');
var fs = require('fs');
var path = require('path');
var mout = require('mout');
var Q = require('q');
var Resolver = require('../Resolver');
var copy = require('../../util/copy');
var extract = require('../../util/extract');
var createError = require('../../util/createError');
var FsResolver = function (source, options) {
// Ensure absolute path
source = path.resolve(source);
Resolver.call(this, source, options);
};
util.inherits(FsResolver, Resolver);
mout.object.mixIn(FsResolver, Resolver);
// -----------------
FsResolver.prototype.hasNew = function (canonicalPkg) {
// If target was specified, simply reject the promise
if (this._target !== '*') {
return Q.reject(createError('File system sources can\'t resolve targets ("' + this._target + '")', 'ENORESTARGET'));
}
// TODO: should we store latest modified files in the resolution and compare?
return Q.resolve(true);
};
FsResolver.prototype._resolveSelf = function () {
return this._copy()
// If target was specified, simply reject the promise
if (this._target !== '*') {
return Q.reject(createError('File system sources can\'t resolve targets ("' + this._target + '")', 'ENORESTARGET'));
}
return this._readJson(this._source)
.then(this._copy.bind(this))
.then(this._extract.bind(this));
};
// -----------------
FsResolver.prototype._copy = function () {
FsResolver.prototype._copy = function (meta) {
return Q.nfcall(fs.stat, this._source)
.then(function (stat) {
var dstFile,
copyOpts;
// Pass in the ignore to the copy options to avoid copying ignore files
// Also, pass in the mode to avoid additional stat calls when copying
copyOpts = {
mode: stat.mode,
ignore: meta.ignore
};
// If it's a folder
if (stat.isDirectory()) {
return copy.copyDir(this._source, this._tempDir, copyOpts);
}
// If it's a file
// We pass the mode to avoid additional stat calls
dstFile = path.join(this._tempDir, path.basename(this._source));
return copy.copyFile(this._source, dstFile, copyOpts);
}.bind(this));
};
FsResolver.prototype._extract = function () {
return extract.canExtract(this._source)
.then(function (canExtract) {
if (canExtract) {
return extract(this._tempDir);
}
});
};
module.exports = FsResolver;

View File

@@ -2,8 +2,8 @@ var util = require('util');
var fs = require('fs');
var Q = require('q');
var mout = require('mout');
var ncp = require('ncp');
var GitResolver = require('./GitResolver');
var copy = require('../../util/copy');
var cmd = require('../../util/cmd');
var path = require('path');
@@ -20,15 +20,7 @@ mout.object.mixIn(GitFsResolver, GitResolver);
// -----------------
GitFsResolver.prototype._copy = function () {
// Copy folder permissions
return Q.nfcall(fs.stat, this._source)
.then(function (stat) {
return Q.nfcall(fs.chmod, this._tempDir, stat.mode);
}.bind(this))
// Copy folder contents
.then(function () {
return Q.nfcall(ncp, this._source, this._tempDir);
}.bind(this));
return copy.copyDir(this._source, this._tempDir);
};
// Override the checkout function to work with the local copy

117
lib/util/copy.js Normal file
View File

@@ -0,0 +1,117 @@
var fstream = require('fstream');
var fstreamIgnore = require('fstream-ignore');
var fs = require('fs');
var Q = require('q');
function copy(reader, writer) {
var deferred = Q.defer(),
ignore,
finish;
finish = function (err) {
writer.removeAllListeners();
reader.removeAllListeners();
// If we got an error, simply reject the deferred
if (err) {
return deferred.reject(err);
}
return deferred.resolve();
};
// Reader
if (reader.type === 'Directory' && reader.ignore) {
ignore = reader.ignore;
reader = fstreamIgnore(reader);
reader.addIgnoreRules(ignore);
} else {
reader = fstream.Reader(reader);
}
reader.on('error', finish);
// Writer
writer = fstream.Writer(writer)
.on('error', finish)
.on('close', finish);
// Finally pipe reader to writer
reader.pipe(writer);
return deferred.promise;
}
function copyMode(src, dst) {
return Q.nfcall(fs.stat, src)
.then(function (stat) {
return Q.nfcall(fs.chmod, dst, stat.mode);
});
}
function parseOptions(opts) {
opts = opts || {};
if (opts.mode != null) {
opts.copyMode = false;
} else if (opts.copyMode == null) {
opts.copyMode = true;
}
return opts;
}
// ---------------------
// Available options:
// - mode: force final mode of dest
// - copyMode: copy mode of src to dest (only if mode is not specified)
function copyFile(src, dst, opts) {
var promise;
opts = parseOptions(opts);
promise = copy({
path: src,
type: 'File'
}, {
path: dst,
mode: opts.mode,
type: 'File'
});
if (opts.copyMode) {
promise = promise.then(copyMode.bind(copyMode, src, dst));
}
return promise;
}
// Available options:
// - ignore: array of patterns to be ignored
// - mode: force final mode of dest
// - copyMode: copy mode of src to dest (only if mode is not specified)
function copyDir(src, dst, opts) {
var promise;
opts = parseOptions(opts);
promise = copy({
path: src,
type: 'Directory',
ignore: opts.ignore
}, {
path: dst,
mode: opts.mode,
type: 'Directory'
});
if (opts.copyMode) {
promise = promise.then(copyMode.bind(copyMode, src, dst));
}
return promise;
}
module.exports.copyDir = copyDir;
module.exports.copyFile = copyFile;

151
lib/util/extract.js Normal file
View File

@@ -0,0 +1,151 @@
var path = require('path');
var fs = require('fs');
var zlib = require('zlib');
var unzip = require('unzip');
var tar = require('tar');
var Q = require('Q');
var mout = require('mout');
var extractors = {
'.zip': extractZip,
'.tar': extractTar,
'.tar.gz': extractTarGz
};
var extractorTypes = Object.keys(extractors);
function extractZip(archive, dest) {
var deferred = Q.defer();
fs.createReadStream(archive)
.pipe(unzip.Extract({ path: this.path }))
.on('error', deferred.reject)
.on('close', deferred.resolve.bind(deferred, dest));
return deferred.promise;
}
function extractTar(archive, dest) {
var deferred = Q.defer();
fs.createReadStream(archive)
.pipe(tar.Extract({ path: this.path }))
.on('error', deferred.reject)
.on('close', deferred.resolve.bind(deferred, dest));
return deferred.promise;
}
function extractTarGz(archive, dest) {
var deferred = Q.defer();
fs.createReadStream(archive)
.pipe(zlib.createGunzip())
.pipe(tar.Extract({ path: this.path }))
.on('error', deferred.reject)
.on('close', deferred.resolve.bind(deferred, dest));
return deferred.promise;
}
function getExtractor(archive) {
var type = mout.array.find(extractorTypes, function (type) {
return mout.string.endsWith(archive, type);
});
return type ? extractors[type] : null;
}
function isSingleDir(dir) {
return Q.nfcall(fs.readdir, dir)
.then(function (files) {
var dir;
if (files.length !== 1) {
return false;
}
dir = files[0];
return Q.nfcall(fs.stat, dir)
.then(function (stat) {
return !stat.isDirectory() ? files[0] : false;
});
});
}
function moveSingleDirContents(dir) {
var destDir = path.dirname(dir);
return Q.nfcall(fs.readdir, dir)
.then(function (files) {
var promises;
promises = files.map(function (file) {
var src = path.join(dir, file),
dest = path.join(destDir, file);
return Q.nfcall(fs.rename, src, dest);
});
return Q.all(promises);
})
.then(function () {
return Q.rmdir(dir);
});
}
// -----------------------------
function extract(archive, dest, options) {
var extractor,
promise;
options = options || {};
extractor = getExtractor(options.extension || archive);
// If extractor is null, then the archive type is unknown
if (!extractor) {
return Q.reject(new Error('File "' + archive + '" is not a known archive'));
}
// Extract archive
promise = extractor(archive, dest);
// Remove archive
if (!options.keepArchive) {
promise = promise
.then(function () {
return Q.nfcall(fs.unlink, archive);
});
}
// Move contents if a single directory was extracted
if (!options.keepStructure) {
promise = promise
.then(function () {
return isSingleDir(dest);
})
.then(function (singleDir) {
return singleDir ? moveSingleDirContents(singleDir) : null;
});
}
// Resolve promise to the dest dir
return promise.then(function () {
return dest;
});
}
function canExtract(archive) {
if (!getExtractor(archive)) {
return Q.resolve(false);
}
return Q.nfcall(fs.stat, archive)
.then(function (stat) {
return stat.isFile();
});
}
module.exports = extract;
module.exports.canExtract = canExtract;