Skip to content

Commit

Permalink
Add simple state machine
Browse files Browse the repository at this point in the history
  • Loading branch information
vladimir-tikhonov committed Oct 15, 2018
1 parent c2bcfb1 commit 9b03774
Show file tree
Hide file tree
Showing 15 changed files with 139 additions and 3 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
dist
coverage
node_modules
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
dist
coverage
node_modules
2 changes: 1 addition & 1 deletion package-lock.json

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

10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"prettier": "^1.14.3",
"ts-jest": "^23.10.4",
"ts-loader": "^5.2.2",
"tslib": "^1.9.3",
"tslint": "^5.11.0",
"tslint-eslint-rules": "^5.4.0",
"typescript": "^3.1.3",
Expand All @@ -54,6 +55,15 @@
"webpack-merge": "^4.1.4"
},
"jest": {
"globals": {
"ts-jest": {
"diagnostics": {
"ignoreCodes": [
151001
]
}
}
},
"transform": {
"^.+\\.ts$": "ts-jest"
},
Expand Down
20 changes: 19 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
import { StateMachine } from 'src/state_machine';

/**
* Parses a command line string into an argument array
*
* @param {string} arvgString - string that you would normally pass to the command line
* @returns {string[]} array of arguments
*/
export default function parseArgvString(arvgString: string): string[] {
return arvgString.split(' ');
const stateMachine = new StateMachine();
const output: string[] = [];
let currentOutputIndex = 0;

for (const character of arvgString) {
const outputCharacter = stateMachine.handleCharacter(character);

if (outputCharacter) {
output[currentOutputIndex] = output[currentOutputIndex]
? output[currentOutputIndex] + character
: character;
} else if (output[currentOutputIndex]) {
currentOutputIndex++;
}
}

return output;
}

// Allows commonjs and es6 imports at the same time.
Expand Down
1 change: 1 addition & 0 deletions src/state_machine/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as StateMachine } from './state_machine';
26 changes: 26 additions & 0 deletions src/state_machine/state_machine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { getStateByHandle, StateHandle } from './states';

export default class StateMachine {
private currentState = getStateByHandle(StateHandle.Empty);

public handleCharacter(character: string) {
const [outputCharacter, nextStateHandle] = this.performTransition(character);

if (nextStateHandle) {
this.currentState = getStateByHandle(nextStateHandle);
}

return outputCharacter;
}

private performTransition(character: string) {
const currentState = this.currentState;

switch (character) {
case ' ':
return currentState.onWhitespace(character);
default:
return currentState.onCharacter(character);
}
}
}
12 changes: 12 additions & 0 deletions src/state_machine/states/empty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import baseState, { State, StateHandle } from 'src/state_machine/states/state';

const emptyState: State = {
...baseState,
stateHandle: StateHandle.Empty,

onCharacter(character: string) {
return [character, StateHandle.InsideToken];
},
};

export default emptyState;
17 changes: 17 additions & 0 deletions src/state_machine/states/factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { StateHandle } from './state';

import emptyState from './empty';
import insideTokenState from './inside_token';

const ALL_STATES = [emptyState, insideTokenState];

export default function getStateByHandle(stateHandle: StateHandle) {
for (const state of ALL_STATES) {
if (state.stateHandle === stateHandle) {
return state;
}
}

// istanbul ignore next line - shouldn't really happen
throw new Error(`State with handle = ${stateHandle} cannot be found`);
}
2 changes: 2 additions & 0 deletions src/state_machine/states/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { State, StateHandle } from './state';
export { default as getStateByHandle } from './factory';
15 changes: 15 additions & 0 deletions src/state_machine/states/inside_token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import baseState, { State, StateHandle } from 'src/state_machine/states/state';

const insiteTokenState: State = {
...baseState,
stateHandle: StateHandle.InsideToken,

onCharacter(character: string) {
return [character, null];
},
onWhitespace() {
return [null, StateHandle.Empty];
},
};

export default insiteTokenState;
27 changes: 27 additions & 0 deletions src/state_machine/states/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
type HandlerFunction = (character: string) => [string | null, StateHandle | null];

export interface State {
stateHandle: StateHandle;
onCharacter: HandlerFunction;
onWhitespace: HandlerFunction;
onDoubleQuote: HandlerFunction;
onSingleQuote: HandlerFunction;
onEscapeCharacter: HandlerFunction;
}

export enum StateHandle {
Empty,
InsideToken,
}

const defaultHandler: HandlerFunction = (_character: string) => [null, null];

const baseState = {
onCharacter: defaultHandler,
onWhitespace: defaultHandler,
onDoubleQuote: defaultHandler,
onSingleQuote: defaultHandler,
onEscapeCharacter: defaultHandler,
};

export default baseState;
5 changes: 5 additions & 0 deletions tests/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ it('can parse a simple space-separated string', () => {
const commandString = 'npm run build';
expect(parseArgvString(commandString)).toEqual(['npm', 'run', 'build']);
});

it('can handle trailing / leading spaces', () => {
const commandString = ' npm run build ';
expect(parseArgvString(commandString)).toEqual(['npm', 'run', 'build']);
});
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"moduleResolution": "node",
"target": "es5",
"baseUrl": ".",
"importHelpers": true,

"strict": true,
"alwaysStrict": true,
Expand Down
2 changes: 1 addition & 1 deletion tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
],
"triple-equals": true,
"quotemark": [true, "single"],
"ter-indent": [true, 4],
"ter-indent": [true, 4, { "SwitchCase": 1 }],
"variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore", "allow-pascal-case"],
"whitespace": [true, "check-branch", "check-operator", "check-typecast", "check-decl", "check-separator"]
}
Expand Down

0 comments on commit 9b03774

Please sign in to comment.