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 on directory names ? if(listEl.search(/\*[^\/]*\//) > -1 || listEl.search(/\*\*[^\/]*\//) > -1) { var match = listEl.match(/^([^*]+\/|)(.*)/); var root = match[1]; var rest = match[2]; var restRegex = rest.replace(/\*\*/g, ".*").replace(/\*/g, "[^\\/]*"); restRegex = new RegExp(restRegex); _ls('-R', root).filter(function (e) { return restRegex.test(e); }).forEach(function(file) { expanded.push(file); }); } // Wildcard present on file names ? else 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;