import { StandardOutputWriter } from "./StandardOutputWriter";
import { PromiseExecutor } from "../utils/types";
/**
* @description A specialized output writer that uses regular expressions to process output.
* @summary This class extends StandardOutputWriter to provide regex-based output processing.
* It allows for pattern matching in the output stream and can trigger specific actions
* based on matched patterns.
*
* @template T - The type of the resolved value, defaulting to string.
*
* @param cmd - The command string to be executed.
* @param lock - A PromiseExecutor to control the asynchronous flow.
* @param regexp - A string or RegExp to match against the output.
* @param flags - Optional flags for the RegExp constructor, defaults to "g".
*
* @class
* @example
* ```typescript
* import { RegexpOutputWriter } from '@decaf-ts/utils';
* import { PromiseExecutor } from '@decaf-ts/utils';
*
* // Create a promise executor
* const executor: PromiseExecutor<string, Error> = {
* resolve: (value) => console.log(`Resolved: ${value}`),
* reject: (error) => console.error(`Rejected: ${error.message}`)
* };
*
* // Create a regexp output writer that matches version numbers
* const writer = new RegexpOutputWriter('node --version', executor, /v(\d+\.\d+\.\d+)/);
*
* // Use the writer to handle command output
* writer.data('v14.17.0'); // This will automatically resolve with "v14.17.0"
* ```
*
* @mermaid
* sequenceDiagram
* participant Client
* participant RegexpOutputWriter
* participant StandardOutputWriter
* participant Logger
*
* Client->>RegexpOutputWriter: new RegexpOutputWriter(cmd, lock, regexp, flags)
* RegexpOutputWriter->>StandardOutputWriter: super(cmd, lock)
* StandardOutputWriter->>Logger: Logging.for(cmd)
* RegexpOutputWriter->>RegexpOutputWriter: compile regexp
*
* Client->>RegexpOutputWriter: data(chunk)
* RegexpOutputWriter->>StandardOutputWriter: super.data(chunk)
* StandardOutputWriter->>Logger: logger.info(log)
* RegexpOutputWriter->>RegexpOutputWriter: testAndResolve(chunk)
* RegexpOutputWriter->>RegexpOutputWriter: test(chunk)
* alt match found
* RegexpOutputWriter->>RegexpOutputWriter: resolve(match[0])
* RegexpOutputWriter->>StandardOutputWriter: resolve(match[0])
* end
*
* Client->>RegexpOutputWriter: error(chunk)
* RegexpOutputWriter->>StandardOutputWriter: super.error(chunk)
* StandardOutputWriter->>Logger: logger.info(log)
* RegexpOutputWriter->>RegexpOutputWriter: testAndReject(chunk)
* RegexpOutputWriter->>RegexpOutputWriter: test(chunk)
* alt match found
* RegexpOutputWriter->>RegexpOutputWriter: reject(match[0])
* RegexpOutputWriter->>StandardOutputWriter: reject(match[0])
* end
*/
export class RegexpOutputWriter extends StandardOutputWriter<string> {
/**
* @description The regular expression used for matching output.
* @summary This readonly property stores the compiled RegExp used for pattern matching.
*/
protected readonly regexp: RegExp;
constructor(
cmd: string,
lock: PromiseExecutor<string, Error>,
regexp: string | RegExp,
flags = "g"
) {
super(cmd, lock);
try {
this.regexp =
typeof regexp === "string" ? new RegExp(regexp, flags) : regexp;
} catch (e: unknown) {
throw new Error(`Invalid regular expression: ${e}`);
}
}
/**
* @description Tests the input data against the stored regular expression.
* @summary Executes the regular expression on the input data and returns the match result.
*
* @param data - The string to test against the regular expression.
* @return The result of the regular expression execution, or undefined if an error occurs.
*/
private test(data: string) {
this.regexp.lastIndex = 0;
let match;
try {
match = this.regexp.exec(data);
} catch (e: unknown) {
return console.debug(`Failed to parse chunk: ${data}\nError: ${e} `);
}
return match;
}
/**
* @description Tests the data and resolves the promise if a match is found.
* @summary Executes the test method and resolves the promise with the first match group if successful.
*
* @param data - The string to test against the regular expression.
*/
protected testAndResolve(data: string) {
const match = this.test(data);
if (match) this.resolve(match[0]);
}
/**
* @description Tests the data and rejects the promise if a match is found.
* @summary Executes the test method and rejects the promise with the first match group if successful.
*
* @param data - The string to test against the regular expression.
*/
protected testAndReject(data: string) {
const match = this.test(data);
if (match) this.reject(match[0]);
}
/**
* @description Processes incoming data chunks.
* @summary Calls the parent class data method and then tests the data for a match to potentially resolve the promise.
*
* @param chunk - The data chunk to process.
*/
override data(chunk: any) {
super.data(chunk);
this.testAndResolve(String(chunk));
}
/**
* @description Processes incoming error chunks.
* @summary Calls the parent class error method and then tests the data for a match to potentially reject the promise.
*
* @param chunk - The error chunk to process.
*/
override error(chunk: any) {
super.error(chunk);
this.testAndReject(String(chunk));
}
}
Source