Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
1c2bb7b
Start using REPL instead of plain readline
piranna Jun 21, 2014
42f8bd8
Accept both Javascript statements and command line instructions
piranna Jun 22, 2014
7440151
Clean-up
piranna Nov 24, 2015
2b1ac64
Use NshRepl class
piranna Nov 24, 2015
38ea4cd
Use npm-path
piranna Nov 24, 2015
2d1eff5
first version rewritten using `shell-parse` module
piranna Jan 27, 2016
26a5f2d
enable completer
piranna Jan 27, 2016
4c79106
simplified creation of command stdin
piranna Jan 27, 2016
de08a21
fixed variables management
piranna Jan 28, 2016
8fcf117
use readline input and output instead of process stdin and stdout
piranna Jan 28, 2016
d885de3
use `end` event instead of `close` (more compatible with functions)
piranna Jan 28, 2016
cbc580e
Dynamic $PATH & pretty completer
piranna Jan 28, 2016
5270666
prevent crashing when command not found
piranna Jan 28, 2016
ee2be5c
Fixed variableAssigment
piranna Jan 28, 2016
45f6da6
variableSubstitution
piranna Jan 28, 2016
fb54ec8
Made it compatible with Node.js v0.12
piranna Jan 31, 2016
6b47e72
Move processing of redirections to own file
piranna Jan 31, 2016
6b7fc97
Clean-up of pipe and redirectFd
piranna Jan 31, 2016
7ea3964
Use non pipe stdin on the returned Duplex object
piranna Jan 31, 2016
806985b
allow piping from stdout of one process to stdin of another
piranna Jan 31, 2016
5ce5baa
Moved `spawnStream()` to own file
piranna Jan 31, 2016
51205c2
private wrapStdio() function for spawnStream()
piranna Feb 1, 2016
0a4a98e
process Control+C on the shell
piranna Feb 1, 2016
06a1ca4
Close correctly stdin of piped commands
piranna Feb 3, 2016
d1e1763
Moved execCommand() to own file & improved stdio streams management
piranna Feb 3, 2016
a6f14e1
Support for `&>` and `&>>`
piranna Feb 4, 2016
09ca1f1
Use `reduce()` to calculate redirections
piranna Feb 4, 2016
cfd4771
Use EventEmitter as default instead of old Stream to prevent `.pipe()…
piranna Feb 5, 2016
b26f6de
Allow exec builtins & added `cd` working one
piranna Feb 5, 2016
c7417b3
use `stderr`, including errors from builtins
piranna Feb 5, 2016
7233c9e
Add underscore on private files names
piranna Feb 6, 2016
45ed593
exec commands globally instead of iterate over them independently
piranna Feb 6, 2016
750185a
commandSubstitution
piranna Feb 6, 2016
f9731a3
Only restore `$CWD` for commandSubstitution
piranna Feb 6, 2016
4f2f23b
Notify error if directory don't exists
piranna Feb 6, 2016
67dc17f
output status
piranna Feb 6, 2016
0e517e8
auto-complete of builtins
piranna Feb 6, 2016
de817fb
capture `EACCES` error when executing a command
piranna Feb 7, 2016
e39ed40
Completer for global and environment variables
piranna Feb 7, 2016
def19e8
Unified `variable` and `variableSubstitution` (they are the same in s…
piranna Feb 7, 2016
820afcf
Update environment variables if they are defined there
piranna Feb 7, 2016
773908d
Not use global scope to store environment variables
piranna Feb 7, 2016
b7222b2
decode prompt
piranna Feb 8, 2016
8203065
Allow globs on command arguments
piranna Feb 8, 2016
1e0abfc
optimizations
piranna Feb 8, 2016
0fea078
fixed redirections
piranna Feb 8, 2016
c68b412
Store `$OLDPWD` as global variable as POSIX dictates
piranna Feb 8, 2016
350cebf
Don't pipe `stderr` to the one of piped `stdout` if it's not available
piranna Feb 8, 2016
c56a137
Full backup of environment before command substitution
piranna Feb 8, 2016
0c5b82e
Clean-up of auto-completion of environment variables
piranna Feb 8, 2016
566af96
Recover for errors and show them instead of throw them
piranna Feb 8, 2016
a86b7b2
Removed old files
piranna Feb 8, 2016
7056363
Create class `Nsh`
piranna Feb 12, 2016
81ddac6
Allow commands to detect they run on an interactive TTY terminal
piranna Feb 14, 2016
a0222db
Isolated `wrapStdio()` function for builtins
piranna Feb 14, 2016
e59000e
Moved stdio related operations on execCommans to `connectStdio()` fun…
piranna Feb 14, 2016
c4baa20
Tests for `spawnStream`
piranna Feb 28, 2016
edda2b2
Tests for 'ignore stdio' and 'pipe two commands between them'
piranna Feb 28, 2016
5a8d13c
Improved tests
piranna Feb 28, 2016
38df6c7
Simplified wrapping of spawn streams
piranna Mar 5, 2016
37d547c
Updated dependencies
piranna Jul 20, 2016
25fe9b2
Hack to exec Node.js interactively
piranna Jul 20, 2016
5e83df3
Reused `runTest()` function for test statement on `ifElse` AST node
piranna Jul 21, 2016
488e05d
processSubstitution
piranna Jul 21, 2016
1eefb6a
Use merged `npm-path` instead of own fork & fixed dependencies versions
piranna Jul 22, 2016
6707450
Split `variableSubstitution` from `variable`
piranna Jul 22, 2016
b09e2f2
Improved completer
piranna Jul 24, 2016
452defc
Merge branch 'master' of github.com:piranna/nsh
piranna Jul 24, 2016
d1278b3
Support for Travis-CI and Coveralls.io
piranna Jul 24, 2016
6b204d7
Support for c++11
piranna Jul 24, 2016
2acbfe2
Removed support for Node.js 0.6
piranna Jul 24, 2016
b95688a
Fix "Build for NodeOS" badge
piranna Jul 24, 2016
da2d2be
Fix typo
piranna Jul 24, 2016
0450384
Updated `easy-coveralls`
piranna Jul 24, 2016
fd99204
Merge branch 'master' of github.com:piranna/nsh
piranna Jul 24, 2016
b790ec7
Use a `Proxy` object to access the local and environment variables
piranna Jul 28, 2016
f810a5c
Use `cd` build-in command from `coreutils.js`
piranna Jul 29, 2016
35153e9
Added coreutils to dependencies
OMantere Oct 29, 2016
10df81c
Merge pull request #1 from OMantere/master
piranna Oct 30, 2016
553ee3d
Update dependencies
piranna Nov 18, 2016
b7d8273
Fixed tests
piranna Dec 10, 2016
3afdbb2
Fixed interactive Node.js REPL
piranna May 7, 2017
3e29cf9
Use `coreutils.js` module by default
piranna May 7, 2017
570b42e
Use `once` for finishing `spawn` events
piranna May 7, 2017
8322225
Updated dependencies
piranna May 7, 2017
e52e60a
`eval` function & `-c` argument
piranna May 8, 2017
d3ad5f6
Add `sh` symlink
piranna May 8, 2017
15ac012
0.5.1
piranna May 8, 2017
ee4ef2f
Hack for NodeOS on Docker
piranna May 14, 2017
aa0019b
Add alias for `sh` command
piranna May 14, 2017
6432820
Updated `mocha` dependency
piranna May 14, 2017
a764eca
0.5.1
piranna May 14, 2017
d30cbb2
Merge branch 'master' of github.com:piranna/nsh
piranna May 15, 2017
3416214
Removed `sh` symlink due to conflicts with dependencies during install
piranna May 15, 2017
49ce399
`-s` flag & execution of script files
piranna May 15, 2017
fea04d4
Use async version of `mkfifo` function
piranna May 15, 2017
2c17358
login shell
piranna May 16, 2017
4032445
0.5.2
piranna May 16, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
npm-debug.log
16 changes: 16 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
language: node_js
node_js:
- "6"
- "6.1"
- "5.11"
- "iojs"
env:
- CXX=g++-4.8
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
after_script:
- npm run coveralls
22 changes: 9 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
# No(de) Shell
[![Build Status](https://travis-ci.org/piranna/nsh.svg?branch=master)](https://travis-ci.org/piranna/nsh)
[![Coverage Status](https://coveralls.io/repos/github/piranna/nsh/badge.svg?branch=master)](https://coveralls.io/github/piranna/nsh?branch=master)

<a href="http://nodeos.github.io"><img src="http://i.imgur.com/pIJu2TS.png" width=200 height=79/></a>
# Node SHell

Both **no shell** or **node shell** accurately describe `nsh`.
[![Built for NodeOS](http://i.imgur.com/pIJu2TS.png)](http://nodeos.github.io)

The goal of `nsh` is to provide a basic shell that will run without having `bash` or another process tidy things up first.

The shell needs to be able to nest without mixing up who gets what keyboard input.
The shell should also be able to run interactive programs like *vim*.

Right now node doesn't support doing proper job control, although I have a pull-request into libuv about that.

- https://github.com/joyent/libuv/pull/934

Features are welcome, but may not be added until I'm sure the basics are stable.
`nsh` is a basic POSIX compliant shell that will run without having `bash` or
another process tidy things up first. It's also `require()`able and embedable on
other projects like [blesh](https://github.com/piranna/blesh), and has a
collection of basic commands as build-in functions running on the same shell
process powered by [Coreutils.js](https://github.com/piranna/Coreutils.js).
85 changes: 85 additions & 0 deletions lib/ast2js/_environment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
'use strict'

var environment =
{
'?': 0,
PS1: '\\w > ',
PS2: '> ',
PS4: '+ '
}


//
// Regular access
//

const handler =
{
ownKeys: function()
{
return Object.keys(environment)
},

get: function(_, prop)
{
var value = environment[prop]

if(value === undefined) value = process.env[prop]
if(value == null) value = ''

return value
},

set: function(_, prop, value)
{
if(value === undefined) return this.deleteProperty(_, prop)

// Exported environment variable
const env = process.env
if(env[prop] !== undefined)
env[prop] = value

// Local environment variable
else
environment[prop] = value

return true
},

deleteProperty: function(_, prop)
{
// Local environment variable
var value = environment[prop]
if(value !== undefined)
delete environment[prop]

// Exported environment variable
else
delete process.env[prop]

return true
}
}


exports = new Proxy({}, handler)


//
// Stacked environment variables
//

exports.push = function()
{
process.env = {__proto__: process.env}
environment = {__proto__: environment}
}

exports.pop = function()
{
process.env = process.env.__proto__
environment = environment.__proto__
}


module.exports = exports
43 changes: 43 additions & 0 deletions lib/ast2js/_execCommands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const eachSeries = require('async/eachSeries')


module.exports = execCommands

const ast2js = require('./index')
const environment = require('./_environment')


// Always calculate dynamic `$PATH` based on the original one
const npmPath = require('npm-path').bind(null, {env:{PATH:process.env.PATH}})


function execCommands(stdio, commands, callback)
{
eachSeries(commands, function(command, callback)
{
// `$PATH` is dynamic based on current directory and any command could
// change it, so we update it previously to exec any of them
npmPath()

command.stdio = stdio

ast2js(command, function(error, command)
{
if(error) return callback(error)

if(command == null) return callback()

command.once('error', callback)
.once('exit', function(code, signal)
{
environment['?'] = code
environment['??'] = signal

this.removeListener('error', callback)

callback(code || signal)
})
})
},
callback)
}
73 changes: 73 additions & 0 deletions lib/ast2js/_redirects.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
var reduce = require('async').reduce

var ast2js = require('./index')


function filterPipes(item)
{
return item.type === 'pipe'
}

function getSrcFd(stdio, fd)
{
var result = stdio[fd]

if(typeof result === 'string') return fd

return result
}

function setOutput(item)
{
item.command.output = this
}


function iterator(stdio, redirect, callback)
{
ast2js(redirect, function(error, value)
{
if(error) return callback(error)

var type = redirect.type
switch(type)
{
case 'duplicateFd':
stdio[redirect.destFd] = getSrcFd(stdio, redirect.srcFd)
break;

case 'moveFd':
stdio[redirect.dest] = getSrcFd(stdio, redirect.fd)
stdio[redirect.fd] = 'ignore'
break;

case 'pipe':
stdio[1] = value
break;

case 'redirectFd':
stdio[redirect.fd] = value

if(redirect.op === '&>'
|| redirect.op === '&>>')
stdio[2] = value
break;

default:
return callback('Unknown redirect type "'+type+'"')
}

callback(null, stdio)
})
}


function redirects(stdio, array, callback)
{
array.filter(filterPipes).forEach(setOutput, stdio.stdout)

reduce(array, stdio.slice(), iterator, callback)
}


module.exports = redirects
147 changes: 147 additions & 0 deletions lib/ast2js/_spawnStream.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
const EventEmitter = require('events')
const spawn = require('child_process').spawn
const stream = require('stream')

const Duplex = stream.Duplex
const Readable = stream.Readable
const Writable = stream.Writable


function noop(){}

/**
* Node.js `spawn` only accept streams with a file descriptor as stdio, so use
* pipes instead and connect the given streams to them.
*/
function wrapStdio(command, argv, options)
{
argv = argv || []
options = options || {}

var stdio = options.stdio
if(!stdio) options.stdio = stdio = []

var stdin = stdio[0]
var stdout = stdio[1]
var stderr = stdio[2]

// Wrap stdio
if(typeof stdin === 'string' || typeof stdin === 'number'
|| stdin && stdin.constructor.name === 'ReadStream')
stdin = null
else
stdio[0] = 'pipe'

if(typeof stdout === 'string' || typeof stdout === 'number'
|| stdout && stdout.constructor.name === 'WriteStream')
stdout = null
else
stdio[1] = 'pipe'

if(typeof stderr === 'string' || typeof stderr === 'number'
|| stderr && stderr.constructor.name === 'WriteStream')
stderr = null
else
stdio[2] = 'pipe'

// Create child process
var cp = spawn(command, argv, options)

// Adjust events, pipe streams and restore stdio
if(stdin != null)
{
stdin.pipe(cp.stdin)
cp.stdin = null
}
if(stdout != null)
{
cp.stdout.pipe(stdout)
cp.stdout = null
}
if(stderr != null)
{
cp.stderr.pipe(stderr)
cp.stderr = null
}

// Return child process
return cp
}


function spawnStream(command, argv, options)
{
if(argv && argv.constructor.name === 'Object')
{
options = argv
argv = undefined
}

options = options || {}
var stdio = options.stdio || []

var stdin = stdio[0]
var stdout = stdio[1]
var stderr = stdio[2]

var cp = wrapStdio(command, argv, options)

stdin = (stdin == null || stdin === 'pipe') ? cp.stdin : null
stdout = (stdout == null || stdout === 'pipe') ? cp.stdout : null
stderr = (stderr == null || stderr === 'pipe') ? cp.stderr : null

var result

// Both `stdin` and `stdout` are open, probably the normal case. Create a
// `Duplex` object with them so command can be used as a filter
if(stdin && stdout) result = Duplex()

// Only `stdout` is open, use it directly
else if(stdout) result = Readable()

// Only `stdin` is open, ensure is always 'only' `Writable`
else if(stdin) result = Writable()

// Both `stdin` and `stdout` are clossed, or already redirected on `spawn`
else result = new EventEmitter()

// Connect stdio streams
if(stdin)
{
result._write = stdin.write.bind(stdin)
result.once('finish', stdin.end.bind(stdin))
}

if(stdout)
{
result._read = noop
stdout.on ('data', result.push.bind(result))
stdout.once('end' , result.push.bind(result, null))
}

// Use child process `exit` event instead of missing `stdout` `end` event
else
cp.once('exit', result.emit.bind(result, 'end'))

if(stderr)
{
// Expose `stderr` so it can be used later.
result.stderr = stderr

// Redirect `stderr` from piped command to our own `stderr`, since there's
// no way to redirect it to `process.stderr` by default as it should be.
// This way we can at least fetch the error messages someway instead of
// lost them...
var out_stderr = stdout && stdout.stderr
if(out_stderr) out_stderr.pipe(stderr)
}

// Propagate process events
cp.once('error', result.emit.bind(result, 'error'))
cp.once('exit' , result.emit.bind(result, 'exit' ))

return result
}


module.exports = spawnStream
Loading