filer/src/shell.js

247 lines
6.3 KiB
JavaScript
Raw Normal View History

/* jshint evil:true */
define(function(require) {
2014-02-15 15:54:54 +00:00
var Path = require('src/path');
2014-02-18 19:51:06 +00:00
var FilerError = require('src/error');
var async = require('async');
2014-02-15 15:54:54 +00:00
function Shell(fs, options) {
options = options || {};
2014-02-17 15:47:47 +00:00
var cwd = '/';
Object.defineProperty(this, 'fs', {
get: function() { return fs; },
enumerable: true
});
Object.defineProperty(this, 'cwd', {
get: function() { return cwd; },
enumerable: true
});
// We include `cd` on the this vs. proto so that
// we can access cwd without exposing it externally.
2014-02-17 15:47:47 +00:00
this.cd = function(path, callback) {
path = Path.resolve(this.cwd, path);
// Make sure the path actually exists, and is a dir
fs.stat(path, function(err, stats) {
if(err) {
2014-02-18 19:51:06 +00:00
callback(new FilerError.ENotDirectory());
2014-02-17 15:47:47 +00:00
return;
}
if(stats.type === 'DIRECTORY') {
cwd = path;
callback();
} else {
2014-02-18 19:51:06 +00:00
callback(new FilerError.ENotDirectory());
2014-02-17 15:47:47 +00:00
}
});
};
}
2014-02-15 20:28:00 +00:00
/**
* Execute the .js command located at `path`. Such commands
* should assume the existence of 3 arguments, which will be
* defined at runtime:
*
* * options - an object containing any arguments, data, etc.
* * callback - a callback function(error, result) to call when done.
*
* The .js command's contents should be the body of a function
* that looks like this:
*
* function(fs, options, callback) {
* // .js code here
* }
*/
Shell.prototype.exec = function(path, options, callback) {
var fs = this.fs;
if(typeof options === 'function') {
callback = options;
options = {};
}
options = options || {};
callback = callback || function(){};
path = Path.resolve(this.cwd, path);
2014-02-15 20:28:00 +00:00
fs.readFile(path, "utf8", function(error, data) {
if(error) {
callback(error);
return;
}
try {
var cmd = new Function('fs', 'options', 'callback', data);
cmd(fs, options, callback);
} catch(e) {
callback(e);
}
});
};
2014-02-15 15:54:54 +00:00
/**
* Create a file if it does not exist, or update access and
* modified times if it does. Valid options include:
*
2014-02-15 20:28:00 +00:00
* * updateOnly - whether to create the file if missing (defaults to false)
2014-02-15 15:54:54 +00:00
* * date - use the provided Date value instead of current date/time
*/
Shell.prototype.touch = function(path, options, callback) {
var fs = this.fs;
2014-02-15 15:54:54 +00:00
if(typeof options === 'function') {
callback = options;
options = {};
}
2014-02-15 20:28:00 +00:00
options = options || {};
callback = callback || function(){};
path = Path.resolve(this.cwd, path);
function createFile(path) {
2014-02-17 15:47:47 +00:00
fs.writeFile(path, '', callback);
}
function updateTimes(path) {
var now = Date.now();
2014-02-15 15:54:54 +00:00
var atime = options.date || now;
var mtime = options.date || now;
2014-02-17 15:47:47 +00:00
fs.utimes(path, atime, mtime, callback);
}
fs.stat(path, function(error, stats) {
if(error) {
2014-02-15 20:28:00 +00:00
if(options.updateOnly === true) {
2014-02-15 15:54:54 +00:00
callback();
} else {
createFile(path);
}
} else {
updateTimes(path);
}
});
};
2014-02-18 19:51:06 +00:00
/**
* Concatenate multiple files into a single String, with each
* file separated by a newline. The `files` argument should
* be a String (path to single file) or an Array of Strings
* (multiple file paths).
*/
Shell.prototype.cat = function(files, callback) {
if(!files) {
callback(new Error("Missing files argument"));
return;
}
var fs = this.fs;
var all = '';
files = typeof files === 'string' ? [ files ] : files;
callback = callback || function(){};
function append(item, callback) {
var filename = Path.resolve(this.cwd, item);
fs.readFile(filename, 'utf8', function(error, data) {
if(error) {
callback(error);
return;
}
all += data + '\n';
callback();
});
}
async.eachSeries(files, append, function(error) {
if(error) {
callback(error);
} else {
callback(null, all.replace(/\n$/, ''));
}
});
};
2014-02-18 21:48:53 +00:00
/**
* Get the listing of a directory, returning an array of
* file entries in the following form:
*
* {
* path: <String> the basename of the directory entry
* links: <Number> the number of links to the entry
* size: <Number> the size in bytes of the entry
* modified: <Number> the last modified date/time
* type: <String> the type of the entry
* contents: <Array> an optional array of child entries
* }
*
* By default ls() gives a shallow listing. If you want
* to follow directories as they are encountered, use
* the `recursive=true` option.
*/
Shell.prototype.ls = function(dir, options, callback) {
if(!dir) {
callback(new Error("Missing dir argument"));
return;
}
var fs = this.fs;
if(typeof options === 'function') {
callback = options;
options = {};
}
options = options || {};
callback = callback || function(){};
function list(path, callback) {
var pathname = Path.resolve(this.cwd, path);
var result = [];
fs.readdir(pathname, function(error, entries) {
if(error) {
callback(error);
return;
}
function getDirEntry(name, callback) {
name = Path.join(pathname, name);
fs.stat(name, function(error, stats) {
if(error) {
callback(error);
return;
}
var entry = {
path: Path.basename(name),
links: stats.nlinks,
size: stats.size,
modified: stats.mtime,
type: stats.type
};
if(options.recursive && stats.type === 'DIRECTORY') {
list(Path.join(pathname, entry.path), function(error, items) {
if(error) {
callback(error);
return;
}
entry.contents = items;
result.push(entry);
callback();
});
} else {
result.push(entry);
callback();
}
});
}
async.each(entries, getDirEntry, function(error) {
callback(error, result);
});
});
}
list(dir, callback);
};
return Shell;
});