Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add 'node' command #5

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 demo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const structure = {
file2: { content: 'The is the content for file2 in the <public> directory.' },
file3: { content: 'The is the content for file3 in the <public> directory.' },
},
'alert.js': { content: 'alert("Hello world")' },
'README.md': { content: '✌⊂(✰‿✰)つ✌ Thanks for checking out the tool! There is a lot that you can do with react-bash and I\'m excited to see all of the fun commands and projects build on top of it!' },
};

Expand Down
8 changes: 8 additions & 0 deletions src/bash.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ export default class Bash {
}
}

executeShell(input, state) {
return Object.assign({}, state, {
history: state.history.concat({
value: Util.evaluate(input),
}),
});
}

parseInput(input) {
const tokens = input.trim().split(/ +/);
const command = tokens.shift();
Expand Down
44 changes: 25 additions & 19 deletions src/commands.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as Util from './util';
import { Errors } from './const';
import { Errors, Shells } from './const';

const helpCommands = ['clear', 'ls', 'cat', 'mkdir', 'cd', 'pwd'];
const helpCommands = ['clear', 'ls', 'cat', 'mkdir', 'cd', 'pwd', 'node'];

export const help = {
exec: ({ history, structure, cwd }) => {
Expand Down Expand Up @@ -49,22 +49,30 @@ export const cat = {
exec: (state, args) => {
const { history, structure, cwd } = state;
const path = args[0];
const relativePath = path.split('/');
const fileName = relativePath.pop();
const fullPath = Util.extractPath(relativePath.join('/'), cwd);
const { err, dir } = Util.getDirectoryByPath(structure, fullPath);
if (err) {
return Util.reportError(state, err, path);
} else if (!dir[fileName]) {
return Util.reportError(state, Errors.NO_SUCH_FILE, path);
} else if (!dir[fileName].hasOwnProperty('content')) {
return Util.reportError(state, Errors.IS_A_DIRECTORY, path);
} else {
return Util.getFile(path, state, file => {
return { cwd, structure,
history: history.concat({
value: dir[fileName].content,
value: file.content,
}),
};
});
},
};

export const node = {
exec: (state, args) => {
const { history, structure, cwd } = state;
const path = args[0];
if (path) {
return Util.getFile(path, state, file => {
return { cwd, structure,
history: history.concat({
value: Util.evaluate(file.content),
}),
};
});
} else {
return { cwd, structure, history, shell: Shells.NODE };
}
},
};
Expand Down Expand Up @@ -108,13 +116,11 @@ export const cd = {
};

export const pwd = {
exec: (state) => {
const { history, structure, cwd } = state;
const directory = `/${cwd}`;

exec: ({ history, structure, cwd }) => {
const value = `/${cwd}`;
return {
cwd, structure,
history: history.concat({ value: directory }),
history: history.concat({ value }),
};
},
};
16 changes: 12 additions & 4 deletions src/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ export default class Terminal extends Component {
} else if (evt.which === C_CHAR_CODE) {
if (this.ctrlPressed) {
this.refs.input.value = '';
if (this.state.shell) {
this.setState({ shell: null });
}
}
} else if (evt.which === UP_CHAR_CODE) {
if (this.Bash.hasPrevCommand()) {
Expand All @@ -116,8 +119,11 @@ export default class Terminal extends Component {

// Execute command
const input = evt.target[0].value;
const newState = this.Bash.execute(input, this.state);
this.setState(newState);
if (this.state.shell) {
this.setState(this.Bash.executeShell(input, this.state));
} else {
this.setState(this.Bash.execute(input, this.state));
}
this.refs.input.value = '';
}

Expand All @@ -132,7 +138,7 @@ export default class Terminal extends Component {

render() {
const { prefix, theme } = this.props;
const { history, cwd } = this.state;
const { history, cwd, shell } = this.state;
const style = Styles[theme] || Styles.light;
return (
<div className="ReactBash" style={style.ReactBash}>
Expand All @@ -144,7 +150,9 @@ export default class Terminal extends Component {
<div style={style.body} onClick={() => this.refs.input.focus()}>
{history.map(this.renderHistoryItem(style))}
<form onSubmit={evt => this.handleSubmit(evt)} style={style.form}>
<span style={style.prefix}>{`${prefix} ~${cwd} $`}</span>
{shell ? <span style={style.shell}>{'>'}</span> : (
<span style={style.prefix}>{`${prefix} ~${cwd} $`}</span>
)}
<input
autoComplete="off"
onKeyDown={this.handleKeyDown}
Expand Down
4 changes: 4 additions & 0 deletions src/const.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ export const Errors = {
NOT_A_DIRECTORY: '-bash: cd: $1: Not a directory',
IS_A_DIRECTORY: 'cat: $1: Is a directory',
};

export const Shells = {
NODE: 'NODE',
};
2 changes: 1 addition & 1 deletion src/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ BaseStyles.input = {
padding: '0',
};

BaseStyles.prefix = {
BaseStyles.prefix = BaseStyles.shell = {
marginRight: '5px',
};

Expand Down
25 changes: 25 additions & 0 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,28 @@ export function getDirectoryByPath(structure, relativePath) {
}
return { dir };
}

export function getFile(path, state, callback) {
const { structure, cwd } = state;
const relativePath = path.split('/');
const fileName = relativePath.pop();
const fullPath = extractPath(relativePath.join('/'), cwd);
const { err, dir } = getDirectoryByPath(structure, fullPath);
if (err) {
return reportError(state, err, path);
} else if (!dir[fileName]) {
return reportError(state, Errors.NO_SUCH_FILE, path);
} else if (!dir[fileName].hasOwnProperty('content')) {
return reportError(state, Errors.IS_A_DIRECTORY, path);
} else {
return callback(dir[fileName]);
}
}

export function evaluate(code) {
try {
return JSON.stringify(eval(code));
} catch (error) {
return `${error.name}: ${error.message}`;
}
}
39 changes: 37 additions & 2 deletions tests/commands.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import chai from 'chai';
import sinon from 'sinon';
import { stateFactory } from './factories';
import Bash from '../src/bash';
import * as BaseCommands from '../src/commands';
import { Errors } from '../src/const';
import { Errors, Shells } from '../src/const';

describe('bash commands', () => {
let bash;
Expand Down Expand Up @@ -147,6 +148,40 @@ describe('bash commands', () => {

});

describe('node', () => {

it('should exist', () => {
chai.assert.isFunction(bash.commands.node.exec);
});

it('should set `shell` state variable to Shells.NODE', () => {
const state = stateFactory();
const { shell } = bash.commands.node.exec(state, {});
chai.assert.strictEqual(shell, Shells.NODE);
});

it('should eval a file\'s contents', () => {
const state = stateFactory();
const expected = state.structure.file1.content;
const stub = sinon.stub(global, 'eval').returns(expected);
const { history } = bash.commands.node.exec(state, { 0: 'file1' });
chai.assert.strictEqual(history.length, 1);
chai.assert.strictEqual(history[0].value, `"${expected}"`);
stub.restore();
});

it('should eval a file\'s contents from path', () => {
const state = stateFactory();
const expected = state.structure.dir1.dir1File.content;
const stub = sinon.stub(global, 'eval').returns(expected);
const { history } = bash.commands.node.exec(state, { 0: 'dir1/dir1File' });
chai.assert.strictEqual(history.length, 1);
chai.assert.strictEqual(history[0].value, `"${expected}"`);
stub.restore();
});

});

describe('mkdir', () => {

it('should exist', () => {
Expand Down Expand Up @@ -229,7 +264,7 @@ describe('bash commands', () => {

});

describe('cd', () => {
describe('pwd', () => {
it('should exist', () => {
chai.assert.isFunction(bash.commands.pwd.exec);
});
Expand Down