Skip to content

Commit

Permalink
Updates
Browse files Browse the repository at this point in the history
  • Loading branch information
BrandonQDixon committed Jul 13, 2021
1 parent f45cf33 commit 17edd89
Show file tree
Hide file tree
Showing 8 changed files with 197 additions and 6 deletions.
36 changes: 36 additions & 0 deletions example-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,42 @@ const commands: iCliCommand[] = [{
execute: async (params: {txt: string;}, cliOutputter: iCliOutputter): Promise<void> => {
cliOutputter.pushMessage("Txt from user ==>", params.txt);
}
}, {
name: 'print-sentence',
displayText: "Print an entire sentence based on the user input",
tokens: ["print-sentence", "p-s"],
requiredParams: [{
name: "txt",
displayText: "Sentence to print",
isValid: (input: string, numAttempts: number) => {
if (input.indexOf(".") > -1) {
return {isValid: true};
} else {
return {
isValid: false,
message: 'A sentence must contain a period.',
tryAgain: numAttempts < 3
};
}
}
}],
execute: async (params: {txt: string;}, cliOutputter: iCliOutputter): Promise<void> => {
cliOutputter.pushMessage("Sentence from user ==>", params.txt);
}
}, {
name: 'add-numbers',
displayText: "Add some numbers together",
tokens: ["add-numbers", "a-n"],
requiredParams: [{
name: "number",
displayText: "Enter the number to add, or empty to conclude",
type: "number",
doRepeat: () => true
}],
execute: async (params: {number: number[]}, cliOutputter: iCliOutputter): Promise<void> => {
const sum: number = params.number.reduce((acc, n) => acc + n, 0);
cliOutputter.pushMessage(`The sum is ${sum}`);
}
}];

new CliApplication().startApp(
Expand Down
4 changes: 3 additions & 1 deletion src/cli-application/cli-application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ export class CliApplication implements iCliApplication {
cliOutputter = new ConsoleOutputter();
}
if (!cliUserInputRequestor) {
cliUserInputRequestor = new ConsoleUserInputRequestor();
cliUserInputRequestor = new ConsoleUserInputRequestor(
cliOutputter
);
}
if (!cmdExecutor) {
cmdExecutor = new CliCommandExecutor(
Expand Down
42 changes: 42 additions & 0 deletions src/cli-application/internal-app-commands/cmd-request-cmd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { iCliCommand } from "../../models/cli-command";
import { iCliCommandExecutor } from "../../models/cli-command-executor";
import { iCliCommandParam } from "../../models/cli-command-param";
import { iCliOutputter } from "../../models/cli-outputter";
import EventEmitter from "events";
import { EVENTS } from "../../events/events";

/**
* This is an internal command to be used by the application to execute other commands
*/
export class CommandRequestorCommand implements iCliCommand {
name: string = "_request_cmd";

displayText: string = "";

tokens: string[] = [];

requiredParams?: iCliCommandParam[] | undefined = [{
name: "cmdNameToken",
displayText: "Enter the next command to execute"
}];

constructor(
private commands: iCliCommand[],
private cmdExecutor: iCliCommandExecutor,
private eventEmitter: EventEmitter
) {}

async execute(userParamsInput: { [key: string]: any; }, cliOutputter: iCliOutputter): Promise<void> {
const cmdNameToken = userParamsInput.cmdNameToken.toLowerCase();
const cmd = this.commands.find(cmd => {
return cmd.name === cmdNameToken || !!cmd.tokens.find(tkn => tkn === cmdNameToken)
});
if (!cmd) {
cliOutputter.pushError(`There is no command with the name or token ${cmdNameToken}`);
return;
}

this.eventEmitter.emit(EVENTS.cmdDefinitionReceived, cmd);
}

}
53 changes: 53 additions & 0 deletions src/cli-application/internal-app-commands/multi-cmd-exec-cmd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { iCliCommand } from "../../models/cli-command";
import { iCliCommandExecutor } from "../../models/cli-command-executor";
import { iCliCommandParam } from "../../models/cli-command-param";
import { iCliOutputter } from "../../models/cli-outputter";
import { iCliUserInputRequestor } from "../../models/cli-user-input-requestor";

/**
* This is an internal command to be used by the application to execute other commands
*/
export class MultiCommandExecutorCommand implements iCliCommand {
name: string = "_multi_cmd";

displayText: string = "";

tokens: string[] = [];

constructor(
private commands: iCliCommand[],
private cmdExecutor: iCliCommandExecutor,
private cliUserInputRequestor: iCliUserInputRequestor
) {}

//we need to manually do the work of getting the commands + params
async execute(userParamsInput: {}, cliOutputter: iCliOutputter): Promise<void> {
const cmdNameToken = userParamsInput.cmdNameToken.toLowerCase();
const cmd = this.commands.find(cmd => {
return cmd.name === cmdNameToken || !!cmd.tokens.find(tkn => tkn === cmdNameToken)
});
if (!cmd) {
cliOutputter.pushError(`There is no command with the name or token ${cmdNameToken}`);
return;
}

await this.cmdExecutor.execute(cmd!);
}

private async getCmdParams(cliCmd: iCliCommand): Promise<{[key: string]: any}> {
const paramsInput: any = {};
const params = cliCmd.requiredParams || [];
for (let i = 0; i < params.length; i++) {
const param = params[i];
const input = await this.cliUserInputRequestor.awaitInput(param);
if (input[param.name]) {
throw new Error(
`CliCommandExecutor: multiple input params have the same name.`
);
}
paramsInput[param.name] = input;
}
return paramsInput;
}

}
3 changes: 2 additions & 1 deletion src/events/events.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const EVENTS = {
quitApp: "quitApp"
quitApp: "quitApp",
cmdDefinitionReceived: "cmdDefinitionReceived"
}
57 changes: 54 additions & 3 deletions src/input-requestor/base-input-requestor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,73 @@ import {
iCliCommandParam,
iCliCommandParamChoice,
} from "../models/cli-command-param";
import { iCliOutputter } from "../models/cli-outputter";
import { iCliUserInputRequestor } from "../models/cli-user-input-requestor";

export abstract class BaseUserInputRequestor implements iCliUserInputRequestor {

constructor(
private cliOutputter: iCliOutputter
) {}

async awaitInput(param: iCliCommandParam): Promise<any> {
if (param.doRepeat) {
const results: any = [];
let isQuerying = true;
do {
const thisResult = await this.awaitSingleInput(param);
if (thisResult) {
results.push(thisResult);
} else {
isQuerying = false;
}
} while (isQuerying && param.doRepeat(results));
return results;
} else {
return this.awaitSingleInput(param);
}
}

private async awaitSingleInput(param: iCliCommandParam, numAttempts = 0): Promise<any> {
const input = await this.getInput(
param.displayText,
param.defaultValue,
param.choices
);
if (input) {
return this.parseValidateInput(param, input, numAttempts);
} else {
return void 0;
}
}

private parseValidateInput(param: iCliCommandParam, input: string, numAttempts: number): any {
let parsed;
switch (param.type || "string") {
case "number":
return this.parseNumber(input);
parsed = this.parseNumber(input);
break;
case "boolean":
return this.parseBoolean(input);
parsed = this.parseBoolean(input);
break;
default:
return input.trim();
parsed = input.trim();
break;
}
if (param.isValid) {
const validation = param.isValid(parsed, numAttempts);
if (validation.isValid) {
return parsed;
} else {
const message = validation.message || `Input is invalid, please try again.`;
this.cliOutputter.pushWarning(message);
if (typeof validation.tryAgain === 'undefined' || !validation.tryAgain) {
throw new Error(`Input failed`)
};
return this.awaitSingleInput(param, numAttempts + 1);
}
} else {
return parsed;
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/models/cli-command-param.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,10 @@ export interface iCliCommandParam {
type?: "string" | "number" | "boolean";
choices?: iCliCommandParamChoice[];
defaultValue?: string;
doRepeat?: (valuesSoFar: any[]) => boolean;
isValid?: (value: any, numAttempts: number) => {
isValid: boolean;
message?: string;
tryAgain?: boolean;
};
}
2 changes: 1 addition & 1 deletion src/models/cli-user-input-requestor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { iCliCommandParam } from "./cli-command-param";

export interface iCliUserInputRequestor {
awaitInput(param: iCliCommandParam): Promise<any>;
awaitInput(param: iCliCommandParam): Promise<any>;
}

0 comments on commit 17edd89

Please sign in to comment.