aboutsummaryrefslogtreecommitdiff
path: root/src/server/session_manager/input_manager.ts
diff options
context:
space:
mode:
authorSam Wilkins <samwilkins333@gmail.com>2019-12-14 02:00:36 -0500
committerSam Wilkins <samwilkins333@gmail.com>2019-12-14 02:00:36 -0500
commit2fbd5cdd6eec76d1b0533e325e60a2c53ba62078 (patch)
tree7d205701bdbb3b6f0d6e395ec37d841e809c430c /src/server/session_manager/input_manager.ts
parent4a0cd5a75d6f77067ee5d119012c489d73ffb649 (diff)
factored out repl into module
Diffstat (limited to 'src/server/session_manager/input_manager.ts')
-rw-r--r--src/server/session_manager/input_manager.ts101
1 files changed, 101 insertions, 0 deletions
diff --git a/src/server/session_manager/input_manager.ts b/src/server/session_manager/input_manager.ts
new file mode 100644
index 000000000..a95e6baae
--- /dev/null
+++ b/src/server/session_manager/input_manager.ts
@@ -0,0 +1,101 @@
+import { createInterface, Interface } from "readline";
+import { red } from "colors";
+
+export interface Configuration {
+ identifier: string;
+ onInvalid?: (culprit?: string) => string | string;
+ isCaseSensitive?: boolean;
+}
+
+export interface Registration {
+ argPattern: RegExp[];
+ action: (parsedArgs: IterableIterator<string>) => any | Promise<any>;
+}
+
+export default class InputManager {
+ private identifier: string;
+ private onInvalid: ((culprit?: string) => string) | string;
+ private isCaseSensitive: boolean;
+ private commandMap = new Map<string, Registration[]>();
+ public interface: Interface;
+ private busy = false;
+ private keys: string | undefined;
+
+ constructor({ identifier: prompt, onInvalid, isCaseSensitive }: Configuration) {
+ this.identifier = prompt;
+ this.onInvalid = onInvalid || this.usage;
+ this.isCaseSensitive = isCaseSensitive ?? true;
+ this.interface = createInterface(process.stdin, process.stdout).on('line', this.considerInput);
+ }
+
+ private usage = () => {
+ const resolved = this.keys;
+ if (resolved) {
+ return resolved;
+ }
+ const members: string[] = [];
+ const keys = this.commandMap.keys();
+ let next: IteratorResult<string>;
+ while (!(next = keys.next()).done) {
+ members.push(next.value);
+ }
+ return `${this.identifier} commands: { ${members.sort().join(", ")} }`;
+ }
+
+ public registerCommand = (basename: string, argPattern: RegExp[], action: any | Promise<any>) => {
+ const existing = this.commandMap.get(basename);
+ const registration = { argPattern, action };
+ if (existing) {
+ existing.push(registration);
+ } else {
+ this.commandMap.set(basename, [registration]);
+ }
+ }
+
+ private invalid = (culprit?: string) => {
+ console.log(red(typeof this.onInvalid === "string" ? this.onInvalid : this.onInvalid(culprit)));
+ this.busy = false;
+ }
+
+ private considerInput = async (line: string) => {
+ if (this.busy) {
+ console.log(red("Busy"));
+ return;
+ }
+ this.busy = true;
+ line = line.trim();
+ if (this.isCaseSensitive) {
+ line = line.toLowerCase();
+ }
+ const [command, ...args] = line.split(/\s+/g);
+ if (!command) {
+ return this.invalid();
+ }
+ const registered = this.commandMap.get(command);
+ if (registered) {
+ const { length } = args;
+ const candidates = registered.filter(({ argPattern: { length: count } }) => count === length);
+ for (const { argPattern, action } of candidates) {
+ const parsed: string[] = [];
+ let matched = false;
+ if (length) {
+ for (let i = 0; i < length; i++) {
+ let matches: RegExpExecArray | null;
+ if ((matches = argPattern[i].exec(args[i])) === null) {
+ break;
+ }
+ parsed.push(matches[0]);
+ }
+ matched = true;
+ }
+ if (!length || matched) {
+ await action(parsed[Symbol.iterator]());
+ this.busy = false;
+ return;
+ }
+ }
+ }
+ this.invalid(command);
+ }
+
+} \ No newline at end of file