--- /dev/null
+var os = require('os');
+var fs = require('fs');
+var _ls = require('./ls');
+
+// Module globals
+var config = {
+ silent: false,
+ fatal: false
+};
+exports.config = config;
+
+var state = {
+ error: null,
+ currentCmd: 'shell.js',
+ tempDir: null
+};
+exports.state = state;
+
+var platform = os.type().match(/^Win/) ? 'win' : 'unix';
+exports.platform = platform;
+
+function log() {
+ if (!config.silent)
+ console.log.apply(this, arguments);
+}
+exports.log = log;
+
+// Shows error message. Throws unless _continue or config.fatal are true
+function error(msg, _continue) {
+ if (state.error === null)
+ state.error = '';
+ state.error += state.currentCmd + ': ' + msg + '\n';
+
+ if (msg.length > 0)
+ log(state.error);
+
+ if (config.fatal)
+ process.exit(1);
+
+ if (!_continue)
+ throw '';
+}
+exports.error = error;
+
+// In the future, when Proxies are default, we can add methods like `.to()` to primitive strings.
+// For now, this is a dummy function to bookmark places we need such strings
+function ShellString(str) {
+ return str;
+}
+exports.ShellString = ShellString;
+
+// Returns {'alice': true, 'bob': false} when passed a dictionary, e.g.:
+// parseOptions('-a', {'a':'alice', 'b':'bob'});
+function parseOptions(str, map) {
+ if (!map)
+ error('parseOptions() internal error: no map given');
+
+ // All options are false by default
+ var options = {};
+ for (var letter in map)
+ options[map[letter]] = false;
+
+ if (!str)
+ return options; // defaults
+
+ if (typeof str !== 'string')
+ error('parseOptions() internal error: wrong str');
+
+ // e.g. match[1] = 'Rf' for str = '-Rf'
+ var match = str.match(/^\-(.+)/);
+ if (!match)
+ return options;
+
+ // e.g. chars = ['R', 'f']
+ var chars = match[1].split('');
+
+ chars.forEach(function(c) {
+ if (c in map)
+ options[map[c]] = true;
+ else
+ error('option not recognized: '+c);
+ });
+
+ return options;
+}
+exports.parseOptions = parseOptions;
+
+// Expands wildcards with matching (ie. existing) file names.
+// For example:
+// expand(['file*.js']) = ['file1.js', 'file2.js', ...]
+// (if the files 'file1.js', 'file2.js', etc, exist in the current dir)
+function expand(list) {
+ var expanded = [];
+ list.forEach(function(listEl) {
+ // Wildcard present?
+ if (listEl.search(/\*/) > -1) {
+ _ls('', listEl).forEach(function(file) {
+ expanded.push(file);
+ });
+ } else {
+ expanded.push(listEl);
+ }
+ });
+ return expanded;
+}
+exports.expand = expand;
+
+// Normalizes _unlinkSync() across platforms to match Unix behavior, i.e.
+// file can be unlinked even if it's read-only, see https://github.com/joyent/node/issues/3006
+function unlinkSync(file) {
+ try {
+ fs.unlinkSync(file);
+ } catch(e) {
+ // Try to override file permission
+ if (e.code === 'EPERM') {
+ fs.chmodSync(file, '0666');
+ fs.unlinkSync(file);
+ } else {
+ throw e;
+ }
+ }
+}
+exports.unlinkSync = unlinkSync;
+
+// e.g. 'shelljs_a5f185d0443ca...'
+function randomFileName() {
+ function randomHash(count) {
+ if (count === 1)
+ return parseInt(16*Math.random(), 10).toString(16);
+ else {
+ var hash = '';
+ for (var i=0; i<count; i++)
+ hash += randomHash(1);
+ return hash;
+ }
+ }
+
+ return 'shelljs_'+randomHash(20);
+}
+exports.randomFileName = randomFileName;
+
+// extend(target_obj, source_obj1 [, source_obj2 ...])
+// Shallow extend, e.g.:
+// extend({A:1}, {b:2}, {c:3}) returns {A:1, b:2, c:3}
+function extend(target) {
+ var sources = [].slice.call(arguments, 1);
+ sources.forEach(function(source) {
+ for (var key in source)
+ target[key] = source[key];
+ });
+
+ return target;
+}
+exports.extend = extend;
+
+// Common wrapper for all Unix-like commands
+function wrap(cmd, fn, options) {
+ return function() {
+ var retValue = null;
+
+ state.currentCmd = cmd;
+ state.error = null;
+
+ try {
+ var args = [].slice.call(arguments, 0);
+
+ if (options && options.notUnix) {
+ retValue = fn.apply(this, args);
+ } else {
+ if (args.length === 0 || typeof args[0] !== 'string' || args[0][0] !== '-')
+ args.unshift(''); // only add dummy option if '-option' not already present
+ retValue = fn.apply(this, args);
+ }
+ } catch (e) {
+ if (!state.error) {
+ // If state.error hasn't been set it's an error thrown by Node, not us - probably a bug...
+ console.log('shell.js: internal error');
+ console.log(e.stack || e);
+ process.exit(1);
+ }
+ if (config.fatal)
+ throw e;
+ }
+
+ state.currentCmd = 'shell.js';
+ return retValue;
+ };
+} // wrap
+exports.wrap = wrap;