diff options
Diffstat (limited to 'lib/models/fs.js')
-rw-r--r-- | lib/models/fs.js | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/lib/models/fs.js b/lib/models/fs.js new file mode 100644 index 0000000..2400ff2 --- /dev/null +++ b/lib/models/fs.js @@ -0,0 +1,277 @@ +var path = require('path'); +var Immutable = require('immutable'); + +var File = require('./file'); +var Promise = require('../utils/promise'); +var error = require('../utils/error'); +var PathUtil = require('../utils/path'); + +var FS = Immutable.Record({ + root: String(), + + fsExists: Function(), + fsReadFile: Function(), + fsStatFile: Function(), + fsReadDir: Function(), + fsLoadObject: null +}); + +/** + Return path to the root + + @return {String} +*/ +FS.prototype.getRoot = function() { + return this.get('root'); +}; + +/** + Verify that a file is in the fs scope + + @param {String} filename + @return {Boolean} +*/ +FS.prototype.isInScope = function(filename) { + var rootPath = this.getRoot(); + filename = path.resolve(rootPath, filename); + return PathUtil.isInRoot(rootPath, filename); +}; + +/** + Resolve a file in this FS + + @param {String} + @return {String} +*/ +FS.prototype.resolve = function() { + var rootPath = this.getRoot(); + var args = Array.prototype.slice.call(arguments); + var filename = path.resolve.apply(path, [rootPath].concat(args)); + + if (!this.isInScope(filename)) { + throw error.FileOutOfScopeError({ + filename: filename, + root: this.root + }); + } + + return filename; +}; + +/** + Check if a file exists, run a Promise(true) if that's the case, Promise(false) otherwise + + @param {String} filename + @return {Promise<Boolean>} +*/ +FS.prototype.exists = function(filename) { + var that = this; + + return Promise() + .then(function() { + filename = that.resolve(filename); + var exists = that.get('fsExists'); + + return exists(filename); + }); +}; + +/** + Read a file and returns a promise with the content as a buffer + + @param {String} filename + @return {Promise<Buffer>} +*/ +FS.prototype.read = function(filename) { + var that = this; + + return Promise() + .then(function() { + filename = that.resolve(filename); + var read = that.get('fsReadFile'); + + return read(filename); + }); +}; + +/** + Read a file as a string (utf-8) + + @param {String} filename + @return {Promise<String>} +*/ +FS.prototype.readAsString = function(filename, encoding) { + encoding = encoding || 'utf8'; + + return this.read(filename) + .then(function(buf) { + return buf.toString(encoding); + }); +}; + +/** + Read stat infos about a file + + @param {String} filename + @return {Promise<File>} +*/ +FS.prototype.statFile = function(filename) { + var that = this; + + return Promise() + .then(function() { + var filepath = that.resolve(filename); + var stat = that.get('fsStatFile'); + + return stat(filepath); + }) + .then(function(stat) { + return File.createFromStat(filename, stat); + }); +}; + +/** + List files/directories in a directory. + Directories ends with '/' + + @param {String} dirname + @return {Promise<List<String>>} +*/ +FS.prototype.readDir = function(dirname) { + var that = this; + + return Promise() + .then(function() { + var dirpath = that.resolve(dirname); + var readDir = that.get('fsReadDir'); + + return readDir(dirpath); + }) + .then(function(files) { + return Immutable.List(files); + }); +}; + +/** + List only files in a diretcory + Directories ends with '/' + + @param {String} dirname + @return {Promise<List<String>>} +*/ +FS.prototype.listFiles = function(dirname) { + return this.readDir(dirname) + .then(function(files) { + return files.filterNot(pathIsFolder); + }); +}; + +/** + List all files in a directory + + @param {String} dirname + @return {Promise<List<String>>} +*/ +FS.prototype.listAllFiles = function(folder) { + var that = this; + folder = folder || '.'; + + return this.readDir(folder) + .then(function(files) { + return Promise.reduce(files, function(out, file) { + var isDirectory = pathIsFolder(file); + + if (!isDirectory) { + return out.push(file); + } + + return that.listAllFiles(path.join(folder, file)) + .then(function(inner) { + return out.concat(inner); + }); + }, Immutable.List()); + }) + .then(function(files) { + return files.map(function(file) { + return path.join(folder, file); + }); + }); +}; + +/** + Find a file in a folder (case incensitive) + Return the found filename + + @param {String} dirname + @param {String} filename + @return {Promise<String>} +*/ +FS.prototype.findFile = function(dirname, filename) { + return this.listFiles(dirname) + .then(function(files) { + return files.find(function(file) { + return (file.toLowerCase() == filename.toLowerCase()); + }); + }); +}; + +/** + Load a JSON file + By default, fs only supports JSON + + @param {String} filename + @return {Promise<Object>} +*/ +FS.prototype.loadAsObject = function(filename) { + var that = this; + var fsLoadObject = this.get('fsLoadObject'); + + return this.exists(filename) + .then(function(exists) { + if (!exists) { + var err = new Error('Module doesn\'t exist'); + err.code = 'MODULE_NOT_FOUND'; + + throw err; + } + + if (fsLoadObject) { + return fsLoadObject(that.resolve(filename)); + } else { + return that.readAsString(filename) + .then(function(str) { + return JSON.parse(str); + }); + } + }); +}; + +/** + Create a FS instance + + @param {Object} def + @return {FS} +*/ +FS.create = function create(def) { + return new FS(def); +}; + +/** + Create a new FS instance with a reduced scope + + @param {FS} fs + @param {String} scope + @return {FS} +*/ +FS.reduceScope = function reduceScope(fs, scope) { + return fs.set('root', path.join(fs.getRoot(), scope)); +}; + + +// .readdir return files/folder as a list of string, folder ending with '/' +function pathIsFolder(filename) { + var lastChar = filename[filename.length - 1]; + return lastChar == '/' || lastChar == '\\'; +} + +module.exports = FS;
\ No newline at end of file |