From f73496498c03f6fc052966db43334643bdeec079 Mon Sep 17 00:00:00 2001 From: Nate Fischer Date: Sun, 28 Feb 2016 15:33:26 -0800 Subject: [PATCH 1/3] Add export command for assigning env variables --- bin/export.js | 2 + commands.json | 2 + dist/commands/export.js | 72 +++++++++++++++++++++++++++ dist/help.js | 2 +- dist/help/export.js | 3 ++ src/commands/export.js | 78 +++++++++++++++++++++++++++++ src/help.js | 3 +- src/help/export.js | 20 ++++++++ test/export.js | 105 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 bin/export.js create mode 100755 dist/commands/export.js create mode 100755 dist/help/export.js create mode 100755 src/commands/export.js create mode 100755 src/help/export.js create mode 100755 test/export.js diff --git a/bin/export.js b/bin/export.js new file mode 100644 index 0000000..8a7649f --- /dev/null +++ b/bin/export.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('./parser')(process.argv, 'export'); diff --git a/commands.json b/commands.json index 8e95a48..e83f65b 100755 --- a/commands.json +++ b/commands.json @@ -5,6 +5,7 @@ "clear", "cp", "echo", + "export", "kill", "ls", "mkdir", @@ -22,6 +23,7 @@ "alias", "cd", "echo", + "export", "less", "sort", "unalias" diff --git a/dist/commands/export.js b/dist/commands/export.js new file mode 100755 index 0000000..e5bf987 --- /dev/null +++ b/dist/commands/export.js @@ -0,0 +1,72 @@ +'use strict'; + +var interfacer = require('./../util/interfacer'); +var preparser = require('./../preparser'); + +var _export = { + exec: function exec(args, options) { + args = args || []; + options = options || {}; + + if (args.length < 1) { + options.p = true; + } + + if (typeof args === 'string' || args instanceof String) { + args = [args]; + } + + // Parse similarly to how `alias` does + var id = args.join(' '); + var value = ''; + if (String(id).indexOf('=') > -1) { + var parts = String(id).trim().split('='); + id = parts[0]; + value = parts[1] || value; + if (value.match(/^".*"$/)) { + value = JSON.parse(value); + } else { + var regMatch = value.match(/^'(.*)'$/); + if (regMatch && regMatch[1]) { + value = regMatch[1]; + } + } + } else { + var parts = String(id).trim().split(' '); + id = parts.shift(); + value = parts.join(' ') || null; + } + + var validIdRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/; + if (options.p) { + for (var name in process.env) { + if (process.env.hasOwnProperty(name)) { + this.log('declare -x ' + String(name) + '=' + JSON.stringify(process.env[name]).replace(/\$/g, '\\$')); + } + } + } else if (id.match(validIdRegex)) { + process.env[id] = value !== null ? value : process.env[id]; + } else { + this.log('-cash: export: `' + id + '\': not a valid identifier'); + return 1; + } + + return 0; + } +}; + +module.exports = function (vorpal) { + if (vorpal === undefined) { + return _export; + } + vorpal.api._export = _export; + vorpal.command('export [name...]').parse(preparser).option('-p', 'print all defined aliases in a reusable format').action(function (args, callback) { + args.options = args.options || {}; + return interfacer.call(this, { + command: _export, + args: args.name, + options: args.options, + callback: callback + }); + }); +}; \ No newline at end of file diff --git a/dist/help.js b/dist/help.js index 177bd91..ab163d4 100755 --- a/dist/help.js +++ b/dist/help.js @@ -2,7 +2,7 @@ var pad = require('./util/pad'); -var commands = ['alias [-p] [name=[value]]', 'cat [-AbeEnstTv] [files ...]', 'cd [dir]', 'clear', 'cp [-fnr] source ... dest', 'echo [eE] [arg ...]', 'grep [-bHhinqsvw] [-m max] [--silent] [--include pattern] pattern [files ...]', 'kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l', 'less [files ...]', 'ls [-aAdFhilQrRStUwx1] [paths ...]', 'mkdir [-pv] [directories ...]', 'mv [-fnv] source ... dest', 'pwd [files ...]', 'rm [-frR] [files ...]', 'sort [-chMnrR] [-o file] [files ...]', 'touch [-acm] [-d date] [-r ref] [--time word] file ...', 'unalias [-a] name [names ...]']; +var commands = ['alias [-p] [name=[value]]', 'cat [-AbeEnstTv] [files ...]', 'cd [dir]', 'clear', 'cp [-fnr] source ... dest', 'echo [-eE] [arg ...]', 'export [-p][id=[value]]', 'grep [-bHhinqsvw] [-m max] [--silent] [--include pattern] pattern [files ...]', 'kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l', 'less [files ...]', 'ls [-aAdFhilQrRStUwx1] [paths ...]', 'mkdir [-pv] [directories ...]', 'mv [-fnv] source ... dest', 'pwd [files ...]', 'rm [-frR] [files ...]', 'sort [-chMnrR] [-o file] [files ...]', 'touch [-acm] [-d date] [-r ref] [--time word] file ...', 'unalias [-a] name [names ...]']; function chop(str, len) { var res = String(str).slice(0, len - 2); diff --git a/dist/help/export.js b/dist/help/export.js new file mode 100755 index 0000000..45f7cd5 --- /dev/null +++ b/dist/help/export.js @@ -0,0 +1,3 @@ +"use strict"; + +module.exports = "\nUsage: export [OPTION] [name[=value]]\nExport variables into the environment\n\nWithout arguments, `export' prints the list of environnmental variables in the\nform `declare -x NAME=\"VALUE\"' on standard output. This is the same as the\nbehavior if `-p' is given.\n\nOtherwise, the variable is exported to the environment. If the variable has\nalready been defined in the environment (ex. `PATH'), then this will either\nredefine its value or do nothing (if no value is passed in). If the variable is\nnot already in the environment, it will be added with the `VALUE' given\n(defaults to the empty string).\n\n -p print all exported variables in a reusable format\n --help display this help and exit\n\nReport export bugs to \nCash home page: \n"; \ No newline at end of file diff --git a/src/commands/export.js b/src/commands/export.js new file mode 100755 index 0000000..da590f4 --- /dev/null +++ b/src/commands/export.js @@ -0,0 +1,78 @@ +'use strict'; + +const interfacer = require('./../util/interfacer'); +const preparser = require('./../preparser'); + +const _export = { + + exec(args, options) { + args = args || []; + options = options || {}; + + if (args.length < 1) { + options.p = true; + } + + if (typeof args === 'string' || args instanceof String) { + args = [args]; + } + + // Parse similarly to how `alias` does + let id = args.join(' '); + let value = ''; + if (String(id).indexOf('=') > -1) { + const parts = String(id).trim().split('='); + id = parts[0]; + value = parts[1] || value; + if (value.match(/^".*"$/)) { + value = JSON.parse(value); + } else { + const regMatch = value.match(/^'(.*)'$/); + if (regMatch && regMatch[1]) { + value = regMatch[1]; + } + } + } else { + const parts = String(id).trim().split(' '); + id = parts.shift(); + value = parts.join(' ') || null; + } + + const validIdRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/; + if (options.p) { + for (const name in process.env) { + if (process.env.hasOwnProperty(name)) { + this.log(`declare -x ${String(name)}=${JSON.stringify(process.env[name]) + .replace(/\$/g, '\\$')}`); + } + } + } else if (id.match(validIdRegex)) { + process.env[id] = value !== null ? value : process.env[id]; + } else { + this.log(`-cash: export: \`${id}': not a valid identifier`); + return 1; + } + + return 0; + } +}; + +module.exports = function (vorpal) { + if (vorpal === undefined) { + return _export; + } + vorpal.api._export = _export; + vorpal + .command('export [name...]') + .parse(preparser) + .option('-p', 'print all defined aliases in a reusable format') + .action(function (args, callback) { + args.options = args.options || {}; + return interfacer.call(this, { + command: _export, + args: args.name, + options: args.options, + callback + }); + }); +}; diff --git a/src/help.js b/src/help.js index 5d29db4..837cbd1 100755 --- a/src/help.js +++ b/src/help.js @@ -8,7 +8,8 @@ const commands = [ 'cd [dir]', 'clear', 'cp [-fnr] source ... dest', - 'echo [eE] [arg ...]', + 'echo [-eE] [arg ...]', + 'export [-p][id=[value]]', 'grep [-bHhinqsvw] [-m max] [--silent] [--include pattern] pattern [files ...]', 'kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l', 'less [files ...]', diff --git a/src/help/export.js b/src/help/export.js new file mode 100755 index 0000000..101d583 --- /dev/null +++ b/src/help/export.js @@ -0,0 +1,20 @@ +module.exports = ` +Usage: export [OPTION] [name[=value]] +Export variables into the environment + +Without arguments, \`export' prints the list of environnmental variables in the +form \`declare -x NAME="VALUE"' on standard output. This is the same as the +behavior if \`-p' is given. + +Otherwise, the variable is exported to the environment. If the variable has +already been defined in the environment (ex. \`PATH'), then this will either +redefine its value or do nothing (if no value is passed in). If the variable is +not already in the environment, it will be added with the \`VALUE' given +(defaults to the empty string). + + -p print all exported variables in a reusable format + --help display this help and exit + +Report export bugs to +Cash home page: +`; diff --git a/test/export.js b/test/export.js new file mode 100755 index 0000000..f5787d7 --- /dev/null +++ b/test/export.js @@ -0,0 +1,105 @@ +'use strict'; + +require('assert'); +const should = require('should'); +const cash = require('../dist/index.js'); + +let oldProcessEnv; + +describe('export', function () { + before(function () { + oldProcessEnv = process.env; + }); + + beforeEach(function () { + process.env = {}; + }); + + after(function () { + process.env = oldProcessEnv; + }); + + it('should exist and be a function', function () { + should.exist(cash._export); + }); + + it('should create an export with an equal symbol', function () { + (function () { + cash._export(['foo=bar']); + }).should.not.throw(); + cash('echo $foo').should.equal('bar\n'); + }); + + it('should accept a string argument', function () { + (function () { + cash._export('foo=bar'); + }).should.not.throw(); + cash('echo $foo').should.equal('bar\n'); + }); + + it('should print msg when reading an invalid export', function () { + cash._export(['1lalalala']).should.equal('-cash: export: `1lalalala\': not a valid identifier\n'); + cash._export(['la@lalala']).should.equal('-cash: export: `la@lalala\': not a valid identifier\n'); + }); + + it('should reassign an export', function () { + (function () { + cash._export(['foo=cows']); + cash._export(['foo=dogs']); + }).should.not.throw(); + cash('echo $foo').should.equal('dogs\n'); + }); + + it('should do nothing if already exported', function () { + (function () { + cash._export(['PATH=/usr/bin']); + cash._export(['PATH']); + }).should.not.throw(); + cash('echo $PATH').should.equal(`${process.env.PATH}\n`); + cash('echo $PATH').should.equal('/usr/bin\n'); + }); + + it('should work without surrounding quotes', function () { + cash._export(['foo=bar']); + cash('echo $foo').should.equal('bar\n'); + }); + + it('should deal with surrounding single quotes', function () { + cash._export(['foo=\'bar tender nice to meet you\'']); + cash('echo $foo').should.equal('bar tender nice to meet you\n'); + }); + + it('should deal with surrounding double quotes', function () { + cash._export(['foo="bar tender nice to meet you"']); + cash('echo $foo').should.equal('bar tender nice to meet you\n'); + }); + + it('should handle multiple exports', function () { + (function () { + cash._export(['a="A"']); + cash._export(['b=\'B\'']); + cash._export(['c="C"']); + }).should.not.throw(); + cash('echo $a $b $c').should.equal('A B C\n'); + }); + + it('should list all registered aliases', function () { + (function () { + cash._export(['a="A"']); + cash._export(['b=\'B\'']); + cash._export(['c="C"']); + }).should.not.throw(); + cash._export().should.equal('declare -x a="A"\ndeclare -x b="B"\ndeclare -x c="C"\n'); + }); + + describe('-p', function () { + it('should list all registered exports', function () { + (function () { + cash._export(['a="A"']); + cash._export(['b=\'B\'']); + cash._export(['c="C"']); + }).should.not.throw(); + cash._export(undefined, {p: true}).should.equal('declare -x a="A"\ndeclare -x b="B"\ndeclare -x c="C"\n'); + }); + }); +}); From b02983abe1e7af9f586b3d67355ad60d24ca3782 Mon Sep 17 00:00:00 2001 From: Nate Fischer Date: Sun, 28 Feb 2016 15:40:13 -0800 Subject: [PATCH 2/3] Add preparser test for export --- test/preparser.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/preparser.js b/test/preparser.js index 40a83b9..a9e28c9 100755 --- a/test/preparser.js +++ b/test/preparser.js @@ -112,6 +112,14 @@ describe('preparser', function () { cash('cp $FOO dest').should.equal(`cp: cannot stat ${process.env.FOO}: No such file or directory\n`); }); + it('should work for export', function () { + (function () { + cash('export BAR=$FOO').should.equal(''); + }).should.not.throw(); + cash('echo $BAR').should.equal(`${process.env.FOO}\n`); + delete process.env.BAR; + }); + it('should work for ls', function () { cash('ls $FOO').should.equal(''); cash('ls $NOSUCHENVVAR').should.equal(cash('ls')); From 991b60e00aa81d3385d295433d7b65ef3c7dae46 Mon Sep 17 00:00:00 2001 From: Nate Fischer Date: Sun, 28 Feb 2016 15:51:05 -0800 Subject: [PATCH 3/3] Rename _export to export where possible --- dist/commands/export.js | 2 +- src/commands/export.js | 2 +- test/export.js | 46 ++++++++++++++++++++--------------------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/dist/commands/export.js b/dist/commands/export.js index e5bf987..a00efac 100755 --- a/dist/commands/export.js +++ b/dist/commands/export.js @@ -59,7 +59,7 @@ module.exports = function (vorpal) { if (vorpal === undefined) { return _export; } - vorpal.api._export = _export; + vorpal.api.export = _export; vorpal.command('export [name...]').parse(preparser).option('-p', 'print all defined aliases in a reusable format').action(function (args, callback) { args.options = args.options || {}; return interfacer.call(this, { diff --git a/src/commands/export.js b/src/commands/export.js index da590f4..c40ab8d 100755 --- a/src/commands/export.js +++ b/src/commands/export.js @@ -61,7 +61,7 @@ module.exports = function (vorpal) { if (vorpal === undefined) { return _export; } - vorpal.api._export = _export; + vorpal.api.export = _export; vorpal .command('export [name...]') .parse(preparser) diff --git a/test/export.js b/test/export.js index f5787d7..0ed6f1e 100755 --- a/test/export.js +++ b/test/export.js @@ -20,86 +20,86 @@ describe('export', function () { }); it('should exist and be a function', function () { - should.exist(cash._export); + should.exist(cash.export); }); it('should create an export with an equal symbol', function () { (function () { - cash._export(['foo=bar']); + cash.export(['foo=bar']); }).should.not.throw(); cash('echo $foo').should.equal('bar\n'); }); it('should accept a string argument', function () { (function () { - cash._export('foo=bar'); + cash.export('foo=bar'); }).should.not.throw(); cash('echo $foo').should.equal('bar\n'); }); it('should print msg when reading an invalid export', function () { - cash._export(['1lalalala']).should.equal('-cash: export: `1lalalala\': not a valid identifier\n'); - cash._export(['la@lalala']).should.equal('-cash: export: `la@lalala\': not a valid identifier\n'); + cash.export(['1lalalala']).should.equal('-cash: export: `1lalalala\': not a valid identifier\n'); + cash.export(['la@lalala']).should.equal('-cash: export: `la@lalala\': not a valid identifier\n'); }); it('should reassign an export', function () { (function () { - cash._export(['foo=cows']); - cash._export(['foo=dogs']); + cash.export(['foo=cows']); + cash.export(['foo=dogs']); }).should.not.throw(); cash('echo $foo').should.equal('dogs\n'); }); it('should do nothing if already exported', function () { (function () { - cash._export(['PATH=/usr/bin']); - cash._export(['PATH']); + cash.export(['PATH=/usr/bin']); + cash.export(['PATH']); }).should.not.throw(); cash('echo $PATH').should.equal(`${process.env.PATH}\n`); cash('echo $PATH').should.equal('/usr/bin\n'); }); it('should work without surrounding quotes', function () { - cash._export(['foo=bar']); + cash.export(['foo=bar']); cash('echo $foo').should.equal('bar\n'); }); it('should deal with surrounding single quotes', function () { - cash._export(['foo=\'bar tender nice to meet you\'']); + cash.export(['foo=\'bar tender nice to meet you\'']); cash('echo $foo').should.equal('bar tender nice to meet you\n'); }); it('should deal with surrounding double quotes', function () { - cash._export(['foo="bar tender nice to meet you"']); + cash.export(['foo="bar tender nice to meet you"']); cash('echo $foo').should.equal('bar tender nice to meet you\n'); }); it('should handle multiple exports', function () { (function () { - cash._export(['a="A"']); - cash._export(['b=\'B\'']); - cash._export(['c="C"']); + cash.export(['a="A"']); + cash.export(['b=\'B\'']); + cash.export(['c="C"']); }).should.not.throw(); cash('echo $a $b $c').should.equal('A B C\n'); }); it('should list all registered aliases', function () { (function () { - cash._export(['a="A"']); - cash._export(['b=\'B\'']); - cash._export(['c="C"']); + cash.export(['a="A"']); + cash.export(['b=\'B\'']); + cash.export(['c="C"']); }).should.not.throw(); - cash._export().should.equal('declare -x a="A"\ndeclare -x b="B"\ndeclare -x c="C"\n'); + cash.export().should.equal('declare -x a="A"\ndeclare -x b="B"\ndeclare -x c="C"\n'); }); describe('-p', function () { it('should list all registered exports', function () { (function () { - cash._export(['a="A"']); - cash._export(['b=\'B\'']); - cash._export(['c="C"']); + cash.export(['a="A"']); + cash.export(['b=\'B\'']); + cash.export(['c="C"']); }).should.not.throw(); - cash._export(undefined, {p: true}).should.equal('declare -x a="A"\ndeclare -x b="B"\ndeclare -x c="C"\n'); + cash.export(undefined, {p: true}).should.equal('declare -x a="A"\ndeclare -x b="B"\ndeclare -x c="C"\n'); }); }); });