Skip to content

Commit

Permalink
Merge pull request #2 from AlexanderPruss/FinishingCLI
Browse files Browse the repository at this point in the history
Finishing cli
  • Loading branch information
Alexander Pruss committed Jun 9, 2019
2 parents 556cfe3 + f3e6082 commit e40b1eb
Show file tree
Hide file tree
Showing 28 changed files with 634 additions and 99 deletions.
53 changes: 52 additions & 1 deletion Readme.MD
Expand Up @@ -3,8 +3,58 @@
This is a simple POC app that I'm using to poke at doing numerics in node. I'm also playing with node-based CLIs while I'm
at it.

# Installation

You'll need NPM installed to build the project. Load dependencies and compile the app with

`npm install && npm run build`

You can then start the application from source code directly with

`npm run start`

Alternatively, you can save the app to your terminal as a CLI app. To do this, run

`npm link`

This will install the app as `matrix-stuff` to your path.

# Usage

Starting the app opens a CLI session in your terminal. You can ask for commands by typing "help", which will give the
following response:

The available commands are:
import {filename} - imports a matrix from the given file;
exit - exits the app.
Matrix files should be formatted as follows:
* Each entry of the matrix is separated with an empty space
* Each row of the matrix is separated with a newline
Currently, only rational numbers are supported. A sample valid row could look like this:
-4 12/15 0 4/6
When a matrix is imported, the follow commands become available:
[Press LEFT] - See what the matrix looked like in the previous step of the execution.
[Press RIGHT] - See what the matrix looked like in the next step of the execution.
start - Jump back to the start of the execution.
result - Jump back to the result of the execution.
A new matrix can be imported with the 'import {filename} command while a matrix execution is active.`


# Examples

Two example matrices can be found in the `examples` folder. You could try them out from inside the app with

`import examples/nicematrix.txt`

`import examples/complicatedMatrix.txt`

Feel free to write your own matrices to be imported and reduced.

# Continuous Integration

ATM this POC isn't packaged, so the CI just runs tests.
Expand All @@ -16,4 +66,5 @@ https://circleci.com/gh/AlexanderPruss/Matrix-Reduction
* Add a more intelligent way to factor a number into prime factors
* Having better immutable types would be helpful
* Some improvements could be made to RationalNumbers to help prevent overflow
* Need to look into whether having non-blocking functions (more async) is relevant when the program is a CLI
* Need to look into whether having non-blocking functions (more async) is relevant when the program is a CLI
* While the math is very well covered, some of the CLI stuff isn't tested much
5 changes: 5 additions & 0 deletions examples/complicatedmatrix.txt
@@ -0,0 +1,5 @@
1 2 0 4 5
4 5 0 -2 -3
7 8 0 1/2 3/4
-1/5 1 0 3 3
2 4 0 8 10
3 changes: 3 additions & 0 deletions examples/nicematrix.txt
@@ -0,0 +1,3 @@
1 2 3
4 5 6
7 8 9
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions package.json
@@ -1,18 +1,18 @@
{
"name": "matrix-reduction",
"version": "0.0.1",
"version": "1.0.0",
"description": "POC matrix reduction program, trying out some numerics in node",
"repository": {
"type": "git",
"url": "git@github.com:AlexanderPruss/MatrixReduction.git"
},
"bin": {
"matrix-stuff": "dist/cli.js"
"matrix-stuff": "dist/Cli.js"
},
"scripts": {
"build": "tsc",
"start": "node dist",
"devstart": "nodemon dist",
"start": "node dist/Cli.js",
"devstart": "nodemon dist/Cli.js",
"test": "tsc && mocha 'dist/**/*.spec.js'",
"test-clean": "rm -rf dist && npm run build && npm run test",
"test-report": "touch report.xml && JUNIT_REPORT_PATH=report.xml mocha 'dist/**/*.spec.js' --colors --reporter mocha-jenkins-reporter --timeout 20000 --exit",
Expand Down
41 changes: 11 additions & 30 deletions src/Cli.ts
@@ -1,47 +1,28 @@
#!/usr/bin/env node

import * as readline from "readline";
import logger from "./logging/Logger";

console.log("Hi");
let prompt = 'OHAI>';
import {ClientEventHandler} from "./cli/ClientEventHandler";

readline.emitKeypressEvents(process.stdin);
const prompter = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: 'OHAI> ',
prompt: 'Matrix: ',
historySize: 1
});

const handler = new ClientEventHandler(prompter);

process.stdin.on('keypress', (str, key) => {
// console.log(`Pressed str: ${str} and key: ${key.name}`);
handler.handleKeypress(key.name);
});


prompter.prompt();

prompter.on('line', (line) => {
switch (line.trim()) {
case 'hello' :
console.log("Hi to you as well");
logger.info("Wut");
break;
case 'clear' :
console.log("Clearing");
console.clear();
break;
case 'ohai' :
prompt = `OHAI${prompt}`;
prompter.setPrompt(prompt);
break;
case 'stop' :
console.log("RUDE");
process.exit(1);
break;
}
prompter.prompt();
handler.handlePrompt(line);
prompter.prompt();
}).on('close', () => {
console.log("Farewell!");
console.log("See you");
process.exit(0);
});
});

prompter.prompt();
119 changes: 119 additions & 0 deletions src/cli/ClientEventHandler.ts
@@ -0,0 +1,119 @@
import defaultRationalNumbers, {RationalNumbers} from "../fields/rationals/RationalNumbers";
import {ReductionExecution} from "./ReductionExecution";
import {ReadLine} from "readline";
import defaultMatrixLoader, {MatrixLoader} from "./MatrixLoader";
import defaultReductionService, {ReductionService} from "../matrix/ReductionService";
import defaultMatrixPainter, {MatrixPainter} from "./MatrixPainter";

export class ClientEventHandler {

currentExecution: ReductionExecution<any>;
readLine: ReadLine;

matrixLoader: MatrixLoader = defaultMatrixLoader;
matrixPainter: MatrixPainter = defaultMatrixPainter;

rationalNumbers: RationalNumbers = defaultRationalNumbers;
reductionService: ReductionService = defaultReductionService;

constructor(readLine: ReadLine) {
this.readLine = readLine;
}

/**
* This currently doesn't do anything, it's a hook to allow different fields in the future.
*/
getCurrentField() {
return this.rationalNumbers;
}

handlePrompt(prompt: string) {
prompt = prompt.trim();

if (prompt.includes("-h") || prompt.includes(" h ") || prompt.includes("help")) {
console.log(`The available commands are:
import {filename} - imports a matrix from the given file;
exit - exits the app.
Matrix files should be formatted as follows:
* Each entry of the matrix is separated with an empty space
* Each row of the matrix is separated with a newline
Currently, only rational numbers are supported. A sample valid row could look like this:
-4 12/15 0 4/6
When a matrix is imported, the follow commands become available:
[Press LEFT] - See what the matrix looked like in the previous step of the execution.
[Press RIGHT] - See what the matrix looked like in the next step of the execution.
start - Jump back to the start of the execution.
result - Jump back to the result of the execution.
A new matrix can be imported with the 'import {filename} command while a matrix execution is active.`)
}

if (prompt.startsWith("import")) {
try {
const filename = prompt.split(" ")[1];
const matrix = this.matrixLoader.importMatrix(this.getCurrentField().getParser(), filename);
this.currentExecution = new ReductionExecution(matrix, this.getCurrentField(), this.matrixPainter, this.reductionService);
} catch (e) {
console.log(e.message);
return;
}

this.readLine.setPrompt("Matrix [<- go back; -> go forward]: ");
return;
}

if (prompt.startsWith("exit")) {
console.log("See you");
process.exit(0);
return;
}

if (this.currentExecution == null) {
return;
}

if (prompt.includes("start") || prompt == "S" || prompt == "s") {
this.currentExecution.goToInitialMatrix();
this.readLine.prompt();
return;
}

if (prompt.includes("result") || prompt == "R" || prompt == "r") {
this.currentExecution.goToFinalMatrix();
this.readLine.prompt();
return;
}

console.log("Sorry, I didn't understand");
this.readLine.prompt();
}

handleKeypress(key: string) {
if (this.currentExecution == null) {
return;
}

let result: string = "";
switch (key) {
case "left":
result = this.currentExecution.goToPreviousMatrix();
if (result != "") {
this.readLine.prompt();
}
break;
case "right":
result = this.currentExecution.goToNextMatrix();
if (result != "") {
this.readLine.prompt();
}
break;
default:
break;
}

}
}
29 changes: 29 additions & 0 deletions src/cli/MatrixLoader.ts
@@ -0,0 +1,29 @@
import {Matrix} from "../matrix/Matrix";
import {Parser} from "./Parser";
import * as fileStream from "fs";

export class MatrixLoader {

//Importing a file synchronously in node is probably a cardinal sin
importMatrix<E>(parser: Parser<E>, filename: string): Matrix<E> {

const buffer = fileStream.readFileSync(filename);
const bufferString = buffer.toString('utf8');

const rows = [];
for (let line of bufferString.split('\n')) {
const row = [];
rows.push(row);

line = line.trim();
for (const element of line.split(" ")) {
row.push(parser.parse(element));
}
}

return new Matrix(rows);
}

}

export default new MatrixLoader();
39 changes: 39 additions & 0 deletions src/cli/MatrixPainter.spec.ts
@@ -0,0 +1,39 @@
import {MatrixPainter} from "./MatrixPainter";
import {RationalParser} from "../fields/rationals/RationalParser";
import {convertToRationalMatrix, defaultMatrix} from "../matrix/test-helpers/MatrixProvider.spec";
import {expect} from "chai";

describe("MatrixPainter", () => {

const matrixPainter = new MatrixPainter();
const parser = new RationalParser();

describe("#printMatrix", () => {

it('should print a matrix', function () {
const matrix = defaultMatrix();

const expected = `[ 1 2 3 ]
[ 4 5 6 ]
[ 7 8 9 ]` + "\n";

expect(matrixPainter.printMatrix(matrix, parser)).to.eql(expected);
});

it('should shorten long entries', function () {
const matrix = convertToRationalMatrix([
[100, 2, 3],
[6, 90000, 123424343434343],
[7, 8, 9]
]);

const expected = `[ 100 2 3 ]
[ 6 90000 1234243... ]
[ 7 8 9 ]` + "\n";

expect(matrixPainter.printMatrix(matrix, parser)).to.eql(expected);
});

})

});

0 comments on commit e40b1eb

Please sign in to comment.