Skip to content

Commit

Permalink
Add pipe support
Browse files Browse the repository at this point in the history
Shell supports pipes | now. For now cat is the only command that
actually uses stdin though. Also fixed some other consistency issues and
a few missing tests.

See #15.
  • Loading branch information
jorinvo committed Nov 4, 2017
1 parent 8413b2a commit adc75c8
Show file tree
Hide file tree
Showing 18 changed files with 215 additions and 52 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,9 @@ for bundling and minification in your own workflow.
- [See below](#the-state-object)


### Built-in commands and flags
### Built-in features, commands and flags

- pipes `|`
- `ls -l -a`
- `cd`
- `pwd`
Expand Down
3 changes: 1 addition & 2 deletions bash-emulator.min.js

Large diffs are not rendered by default.

3 changes: 0 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,6 @@
if (e.which !== ENTER) {
return
}
if (!input.value.length) {
return
}
run(input.value).then(function () {
input.value = ''
document.body.scrollTop = 10e6
Expand Down
36 changes: 28 additions & 8 deletions src/commands/cat.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
var addLineNumbers = require('../utils/lineNumber')
var lineNumber = require('../utils/lineNumber')

var numColumnWidth = 6
var numberFlag = '-n'

function cat (env, args) {
// Ignore command name
args.shift()

var exitCode = 0
var numberFlagIndex = args.findIndex(function (arg) {
return arg === numberFlag
Expand All @@ -12,6 +15,26 @@ function cat (env, args) {
if (showNumbers) {
args.splice(numberFlagIndex, 1)
}

if (!args.length) {
var num = 1
return {
input: function (str) {
str.split('\n').forEach(function (l) {
if (!l) {
return
}
var line = showNumbers ? lineNumber.addLineNumber(numColumnWidth, num, l) : l
num++
env.output(line + '\n')
})
},
close: function () {
env.exit(exitCode)
}
}
}

Promise
.all(args.map(function (path) {
return env.system.read(path).then(null, function (err) {
Expand All @@ -20,13 +43,10 @@ function cat (env, args) {
})
}))
.then(function (contents) {
if (!showNumbers) {
return contents
}
return addLineNumbers(numColumnWidth, contents)
})
.then(function (contents) {
env.output(contents.join('\n'))
var lines = showNumbers ? lineNumber.addLineNumbers(numColumnWidth, contents) : contents
lines.forEach(function (line) {
env.output(line + '\n')
})
env.exit(exitCode)
})
}
Expand Down
3 changes: 3 additions & 0 deletions src/commands/cd.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
function cd (env, args) {
// Ignore command name
args.shift()

var path = args[0] || '/home/' + env.system.state.user

env.system.changeDir(path).then(
Expand Down
3 changes: 3 additions & 0 deletions src/commands/cp.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
var SINGLE_COPY = 'SINGLE_COPY'

function cp (env, args) {
// Ignore command name
args.shift()

var rFlagIndex = args.findIndex(function (arg) {
return arg === '-r' || arg === '-R'
})
Expand Down
4 changes: 2 additions & 2 deletions src/commands/history.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
var addLineNumbers = require('../utils/lineNumber')
var lineNumber = require('../utils/lineNumber')

// default width for number column
var numColumnWidth = 5

function history (env) {
env.system.getHistory().then(function (history) {
env.output(addLineNumbers(numColumnWidth, history).join('\n'))
env.output(lineNumber.addLineNumbers(numColumnWidth, history).join('\n'))
env.exit()
})
}
Expand Down
3 changes: 3 additions & 0 deletions src/commands/ls.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ var disclaimer =
'On a real system you would also see file permissions, user, group, block size and more.'

function ls (env, args) {
// Ignore command name
args.shift()

var aFlagIndex = args.findIndex(function (arg) {
return arg === '-a'
})
Expand Down
3 changes: 3 additions & 0 deletions src/commands/mkdir.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
function mkdir (env, args) {
// Ignore command name
args.shift()

if (!args.length) {
env.error('mkdir: missing operand')
env.exit(1)
Expand Down
3 changes: 3 additions & 0 deletions src/commands/mv.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
var SINGLE_COPY = 'SINGLE_COPY'

function mv (env, args) {
// Ignore command name
args.shift()

var nFlagIndex = args.findIndex(function (arg) {
return arg === '-n'
})
Expand Down
2 changes: 1 addition & 1 deletion src/commands/pwd.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function pwd (env, args) {
function pwd (env) {
env.system.getDir().then(function (dir) {
env.output(dir)
env.exit()
Expand Down
3 changes: 3 additions & 0 deletions src/commands/rm.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
function rm (env, args) {
// Ignore command name
args.shift()

var rFlagIndex = args.findIndex(function (arg) {
return arg.toLowerCase() === '-r'
})
Expand Down
3 changes: 3 additions & 0 deletions src/commands/rmdir.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
function rmdir (env, args) {
// Ignore command name
args.shift()

if (!args.length) {
env.error('rmdir: missing operand')
env.exit(1)
Expand Down
3 changes: 3 additions & 0 deletions src/commands/touch.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
function touch (env, args) {
// Ignore command name
args.shift()

if (!args.length) {
env.error('touch: missing file operand')
env.exit(1)
Expand Down
87 changes: 65 additions & 22 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,32 +30,75 @@ function bashEmulator (initialState) {
state: state,

run: function (input) {
var args = input.split(' ')
var cmd = args.shift()
state.history.push(input)
if (!commands[cmd]) {
return Promise.reject(cmd + ': command not found')

var argsList = input.split('|').map(function (pipe) {
var args = pipe.trim().split(' ').filter(function (s) {
return s
})
return args
})

if (argsList.length === 1 && !argsList[0].length) {
return Promise.resolve('\n')
}

if (!argsList[argsList.length - 1][0]) {
return Promise.reject('syntax error: unexpected end of file')
}

if (argsList.find(function (a) { return !a.length })) {
return Promise.reject("syntax error near unexpected token `|'")
}

var nonExistent = argsList.filter(function (args) {
var cmd = args[0]
return !commands[cmd]
})
if (nonExistent.length) {
return Promise.reject(nonExistent.map(function (args) {
return args[0] + ': command not found'
}).join('\n'))
}

var result = ''

return new Promise(function (resolve, reject) {
commands[cmd]({
output: function (str) {
result += str
},
// NOTE: For now we just redirect stderr to stdout
error: function (str) {
result += str
},
// NOTE: For now we don't use specific error codes
exit: function (code) {
if (code) {
reject(result)
} else {
resolve(result)
}
},
system: emulator
}, args)
var pipes = argsList.map(function (args, idx) {
var isLast = idx === argsList.length - 1
return commands[args[0]]({
output: function (str) {
if (isLast) {
result += str
return
}
var nextInput = pipes[idx + 1] && pipes[idx + 1].input
if (nextInput) {
nextInput(str)
}
},
// NOTE: For now we just redirect stderr to stdout
error: function (str) {
result += str
},
// NOTE: For now we don't use specific error codes
exit: function (code) {
if (isLast) {
if (code) {
reject(result)
} else {
resolve(result)
}
return
}
var nextClose = pipes[idx + 1] && pipes[idx + 1].close
if (nextClose) {
nextClose()
}
},
system: emulator
}, args)
})
})
},

Expand Down
17 changes: 10 additions & 7 deletions src/utils/lineNumber.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
function addLineNumbers (width, list) {
return list.map(function (item, i) {
function addLineNumber (width, num, line) {
var numChars = num.toString().length
var space = ' '.repeat(width - numChars)
return space + num + ' ' + line
}
module.exports.addLineNumber = addLineNumber

module.exports.addLineNumbers = function (width, lines) {
return lines.map(function (line, i) {
var num = i + 1
var numChars = num.toString().length
var space = ' '.repeat(width - numChars)
return space + num + ' ' + item
return addLineNumber(width, num, line)
})
}

module.exports = addLineNumbers
24 changes: 18 additions & 6 deletions test/commands/cat.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ var test = require('tape')
var bashEmulator = require('../../src')

test('cat', function (t) {
t.plan(5)
t.plan(7)

var emulator = bashEmulator({
workingDirectory: '/',
Expand All @@ -29,39 +29,51 @@ test('cat', function (t) {
var res =
'first\n' +
'third\n' +
'second'
'second\n'
t.equal(output, res, 'cats multiple files')
})

emulator.run('cat nonexistent /1.txt').then(null, function (err) {
var res =
'cat: nonexistent: No such file or directory' +
'\n' +
'first'
'first\n'
t.equal(err, res, 'with errors')
})

emulator.run('cat -n 1.txt /3.txt /2.txt').then(function (output) {
var res =
' 1 first\n' +
' 2 third\n' +
' 3 second'
' 3 second\n'
t.equal(output, res, 'with -n')
})

emulator.run('cat 1.txt -n /3.txt /2.txt').then(function (output) {
var res =
' 1 first\n' +
' 2 third\n' +
' 3 second'
' 3 second\n'
t.equal(output, res, 'with -n in between')
})

emulator.run('cat 1.txt /3.txt /2.txt -n').then(function (output) {
var res =
' 1 first\n' +
' 2 third\n' +
' 3 second'
' 3 second\n'
t.equal(output, res, 'with -n as last')
})

emulator.run('cat 1.txt | cat').then(function (output) {
var res = 'first\n'
t.equal(output, res, 'cat from stdin')
})

emulator.run('cat 1.txt 2.txt | cat -n').then(function (output) {
var res =
' 1 first\n' +
' 2 second\n'
t.equal(output, res, 'cat from stdin with -n')
})
})
Loading

0 comments on commit adc75c8

Please sign in to comment.