Update all of InterfaceManager to use MPS.

This commit is contained in:
gnuxie committed 2024-04-06 20:03:33 +01:00
1 parent 98f3471ea0
commit 68bfd61ccf
10 files changed
+113 -113

No files matched your search

@@ -3,7 +3,7 @@
* All rights reserved.
*/
import { MatrixSendClient } from "../../MatrixEmitter";
import { MatrixSendClient } from "matrix-protection-suite-for-matrix-bot-sdk";
import { AbstractNode, DocumentNode, FringeWalker, NodeTag } from "./DeadDocument";
import { HTML_RENDERER } from "./DeadDocumentHtml";
import { MARKDOWN_RENDERER } from "./DeadDocumentMarkdown";
@@ -29,8 +29,8 @@ limitations under the License.
* I'd like to remove the dependency on matrix-bot-sdk.
*/
import { ActionResult, isError } from "matrix-protection-suite";
import { ParameterParser, IArgumentStream, IArgumentListParser, ParsedKeywords, ArgumentStream } from "./ParameterParsing";
import { CommandResult } from "./Validation";
/**
* 💀 . o O ( at least I don't have to remember the types )
@@ -38,7 +38,7 @@ import { CommandResult } from "./Validation";
* Probably am "doing something wrong", and no, trying to make this protocol isn't it.
*/
export type BaseFunction = (keywords: ParsedKeywords, ...args: any) => Promise<CommandResult<any>>;
export type BaseFunction = (keywords: ParsedKeywords, ...args: any) => Promise<ActionResult<any>>;
type CommandLookupEntry<ExecutorType extends BaseFunction> = {
next?: Map<string, CommandLookupEntry<ExecutorType>>,
@@ -200,7 +200,7 @@ export class InterfaceCommand<ExecutorType extends BaseFunction = BaseFunction>
public async parseThenInvoke(context: ThisParameterType<ExecutorType>, stream: IArgumentStream): Promise<ReturnType<ExecutorType>> {
const parameterDescription = await this.parseArguments(stream);
if (parameterDescription.isErr()) {
if (isError(parameterDescription)) {
// The inner type is irrelevant when it is Err, i don't know how to encode this in TS's type system but whatever.
return parameterDescription as ReturnType<Awaited<ExecutorType>>;
}
@@ -2,16 +2,15 @@
* Copyright (C) 2022 Gnuxie <Gnuxie@protonmail.com>
*/
import { MatrixSendClient } from "../../MatrixEmitter";
import { BaseFunction, CommandTable, InterfaceCommand } from "./InterfaceCommand";
import { MatrixContext, MatrixInterfaceAdaptor } from "./MatrixInterfaceAdaptor";
import { ArgumentParseError, ParameterDescription, RestDescription } from "./ParameterParsing";
import { CommandError, CommandResult } from "./Validation";
import { JSXFactory } from "./JSXFactory";
import { DocumentNode } from "./DeadDocument";
import { renderMatrixAndSend } from "./DeadDocumentMatrix";
import { CommandException } from "./CommandException";
import { LogService } from "matrix-bot-sdk";
import { MatrixSendClient } from "matrix-protection-suite-for-matrix-bot-sdk";
import { ActionException, ActionResult, StringRoomID, isError } from "matrix-protection-suite";
function requiredArgument(argumentName: string): string {
return `<${argumentName}>`;
@@ -77,45 +76,45 @@ function renderTableHelp(table: CommandTable): DocumentNode {
</root>
}
export async function renderHelp(client: MatrixSendClient, commandRoomId: string, event: any, result: CommandResult<CommandTable, CommandError>): Promise<void> {
if (result.isErr()) {
export async function renderHelp(client: MatrixSendClient, commandRoomID: StringRoomID, event: any, result: ActionResult<CommandTable>): Promise<void> {
if (isError(result)) {
throw new TypeError("This command isn't supposed to fail");
}
await renderMatrixAndSend(
renderTableHelp(result.ok),
commandRoomId,
commandRoomID,
event,
client
);
}
export async function tickCrossRenderer(this: MatrixInterfaceAdaptor<MatrixContext, BaseFunction>, client: MatrixSendClient, commandRoomId: string, event: any, result: CommandResult<unknown, CommandError>): Promise<void> {
export async function tickCrossRenderer(this: MatrixInterfaceAdaptor<MatrixContext, BaseFunction>, client: MatrixSendClient, commandRoomID: StringRoomID, event: any, result: ActionResult<unknown>): Promise<void> {
const react = async (emote: string) => {
try {
await client.unstableApis.addReactionToEvent(commandRoomId, event['event_id'], emote);
await client.unstableApis.addReactionToEvent(commandRoomID, event['event_id'], emote);
} catch (e) {
LogService.error("tickCrossRenderer", "Couldn't react to the event", event['event_id'], e);
}
}
if (result.isOk()) {
if (result.isOkay) {
await react('✅')
} else {
if (result.err instanceof ArgumentParseError) {
if (result.error instanceof ArgumentParseError) {
await renderMatrixAndSend(
renderArgumentParseError(this.interfaceCommand, result.err),
commandRoomId,
renderArgumentParseError(this.interfaceCommand, result.error),
commandRoomID,
event,
client);
} else if (result.err instanceof CommandException) {
const commandError = result.err;
} else if (result.error instanceof ActionException) {
const commandError = result.error;
LogService.error("CommandException", commandError.uuid, commandError.message, commandError.exception);
await renderMatrixAndSend(
renderCommandException(this.interfaceCommand, result.err),
commandRoomId,
renderCommandException(this.interfaceCommand, result.error),
commandRoomID,
event,
client);
} else {
await client.replyNotice(commandRoomId, event, result.err.message);
await client.replyNotice(commandRoomID, event, result.error.message);
}
// reacting is way less important than communicating what happened, do it last.
await react('❌');
@@ -146,7 +145,7 @@ function renderArgumentParseError(command: InterfaceCommand<BaseFunction>, error
</root>
}
function renderCommandException(command: InterfaceCommand<BaseFunction>, error: CommandException): DocumentNode {
function renderCommandException(command: InterfaceCommand<BaseFunction>, error: ActionException): DocumentNode {
return <root>
There was an unexpected error when processing this command:<br />
{error.message}<br />
@@ -29,28 +29,28 @@ limitations under the License.
* I'd like to remove the dependency on matrix-bot-sdk.
*/
import { CommandError, CommandResult } from "./Validation";
import { LogService, MatrixClient } from "matrix-bot-sdk";
import { ReadItem } from "./CommandReader";
import { MatrixEmitter, MatrixSendClient } from "../../MatrixEmitter";
import { BaseFunction, InterfaceCommand } from "./InterfaceCommand";
import { tickCrossRenderer } from "./MatrixHelpRenderer";
import { CommandInvocationRecord, InterfaceAcceptor, PromptableArgumentStream, PromptOptions } from "./PromptForAccept";
import { ParameterDescription } from "./ParameterParsing";
import { matrixPromptForAccept } from "./MatrixPromptForAccept";
import { MatrixSendClient, SafeMatrixEmitter } from "matrix-protection-suite-for-matrix-bot-sdk";
import { ActionError, ActionResult, ResultError, RoomEvent, RoomMessage, StringRoomID, isError } from "matrix-protection-suite";
export interface MatrixContext {
client: MatrixSendClient,
emitter: MatrixEmitter,
roomId: string,
event: any,
emitter: SafeMatrixEmitter,
roomID: StringRoomID,
event: RoomMessage,
}
type RendererSignature<C extends MatrixContext, ExecutorType extends BaseFunction> = (
this: MatrixInterfaceAdaptor<C, ExecutorType>,
client: MatrixClient,
commandRoomId: string,
event: any,
event: RoomEvent,
result: Awaited<ReturnType<ExecutorType>>) => Promise<void>;
export class MatrixInterfaceAdaptor<C extends MatrixContext, ExecutorType extends BaseFunction = BaseFunction> implements InterfaceAcceptor {
@@ -58,7 +58,7 @@ export class MatrixInterfaceAdaptor<C extends MatrixContext, ExecutorType extend
constructor(
public readonly interfaceCommand: InterfaceCommand<ExecutorType>,
private readonly renderer: RendererSignature<C, ExecutorType>,
private readonly validationErrorHandler?: (client: MatrixClient, roomId: string, event: any, validationError: CommandError) => Promise<void>
private readonly validationErrorHandler?: (client: MatrixClient, roomID: StringRoomID, event: RoomEvent, validationError: ActionError) => Promise<void>
) {
}
@@ -75,13 +75,13 @@ export class MatrixInterfaceAdaptor<C extends MatrixContext, ExecutorType extend
const invocationRecord = new MatrixInvocationRecord<ThisParameterType<ExecutorType>>(this.interfaceCommand, executorContext, matrixContext);
const stream = new PromptableArgumentStream(args, this, invocationRecord);
const executorResult: Awaited<ReturnType<typeof this.interfaceCommand.parseThenInvoke>> = await this.interfaceCommand.parseThenInvoke(executorContext, stream);
if (executorResult.isErr()) {
this.reportValidationError(matrixContext.client, matrixContext.roomId, matrixContext.event, executorResult.err);
if (isError(executorResult)) {
this.reportValidationError(matrixContext.client, matrixContext.roomID, matrixContext.event, executorResult.error);
return;
}
// just give the renderer the MatrixContext.
// we need to give the renderer the command itself!
await this.renderer.apply(this, [matrixContext.client, matrixContext.roomId, matrixContext.event, executorResult]);
await this.renderer.apply(this, [matrixContext.client, matrixContext.roomID, matrixContext.event, executorResult]);
}
// is this still necessary, surely this should be handled entirely by the renderer?
@@ -89,16 +89,16 @@ export class MatrixInterfaceAdaptor<C extends MatrixContext, ExecutorType extend
// and an error discovered because their is a fault or an error running the command. Though i don't think this is correct
// since any CommandError recieved is an expected error. It means there is no fault. An exception on the other hand does
// so this suggests we should just remove this.
private async reportValidationError(client: MatrixSendClient, roomId: string, event: any, validationError: CommandError): Promise<void> {
private async reportValidationError(client: MatrixSendClient, roomID: StringRoomID, event: RoomMessage, validationError: ActionError): Promise<void> {
LogService.info("MatrixInterfaceCommand", `User input validation error when parsing command ${JSON.stringify(this.interfaceCommand.designator)}: ${validationError.message}`);
if (this.validationErrorHandler) {
await this.validationErrorHandler.apply(this, arguments);
return;
}
await tickCrossRenderer.call(this, client, roomId, event, CommandResult.Err(validationError));
await tickCrossRenderer.call(this, client, roomID, event, ResultError(validationError));
}
public async promptForAccept<PresentationType = unknown>(parameter: ParameterDescription, invocationRecord: CommandInvocationRecord): Promise<CommandResult<PresentationType>> {
public async promptForAccept<PresentationType = unknown>(parameter: ParameterDescription, invocationRecord: CommandInvocationRecord): Promise<ActionResult<PresentationType>> {
if (!(invocationRecord instanceof MatrixInvocationRecord)) {
throw new TypeError("The MatrixInterfaceAdaptor only supports invocation records that were produced by itself.");
}
@@ -6,10 +6,10 @@
import { ReadItem } from "./CommandReader";
import { findPresentationType, makePresentationType, simpleTypeValidator } from "./ParameterParsing";
import { UserID } from "matrix-bot-sdk";
import { MatrixRoomAlias, MatrixRoomID, MatrixRoomReference } from "./MatrixRoomReference";
import { definePresentationRenderer } from "./DeadDocumentPresentation";
import { JSXFactory } from "./JSXFactory";
import { DocumentNode } from "./DeadDocument";
import { MatrixRoomAlias, MatrixRoomID } from "matrix-protection-suite";
makePresentationType({
@@ -19,7 +19,7 @@ makePresentationType({
makePresentationType({
name: 'MatrixRoomReference',
validator: simpleTypeValidator('MatrixRoomReference', (item: ReadItem) => item instanceof MatrixRoomReference),
validator: simpleTypeValidator('MatrixRoomReference', (item: ReadItem) => item instanceof MatrixRoomID || item instanceof MatrixRoomAlias),
})
makePresentationType({
@@ -3,6 +3,7 @@
* All rights reserved.
*/
import { ActionResult, StringUserID } from "matrix-protection-suite";
import { renderMatrixAndSend } from "./DeadDocumentMatrix";
import { BaseFunction, InterfaceCommand } from "./InterfaceCommand";
import { JSXFactory } from "./JSXFactory";
@@ -10,7 +11,6 @@ import { MatrixContext } from "./MatrixInterfaceAdaptor";
import { PromptResponseListener } from "./MatrixPromptUX";
import { ParameterDescription } from "./ParameterParsing";
import { PromptOptions } from "./PromptForAccept";
import { CommandResult } from "./Validation";
async function promptDefault<PresentationType>(this: MatrixContext, parameter: ParameterDescription, command: InterfaceCommand<BaseFunction>, defaultPrompt: PresentationType) {
await renderMatrixAndSend(
@@ -18,7 +18,7 @@ async function promptDefault<PresentationType>(this: MatrixContext, parameter: P
No argument was provided for the parameter {parameter.name}, would you like to accept the default?<br/>
{defaultPrompt}
</root>,
this.roomId, this.event, this.client
this.roomID, this.event, this.client
)
}
@@ -38,22 +38,23 @@ async function promptSuggestions<PresentationType>(
})}
</ol>
</root>,
this.roomId, this.event, this.client
this.roomID, this.event, this.client
)).at(0) as string;
}
export async function matrixPromptForAccept<PresentationType = any> (
this: MatrixContext, parameter: ParameterDescription, command: InterfaceCommand<BaseFunction>, promptOptions: PromptOptions
): Promise<CommandResult<PresentationType>> {
const promptHelper = new PromptResponseListener(this.emitter, await this.client.getUserId(), this.client);
): Promise<ActionResult<PresentationType>> {
// FIXME: is there a better way to get the clinet ID? why isn't Draupnir in the command context?
const promptHelper = new PromptResponseListener(this.emitter, await this.client.getUserId() as StringUserID, this.client);
if (promptOptions.default) {
await promptDefault.call(this, parameter, command, promptOptions.default);
throw new TypeError("default prompts are not implemented yet.");
}
return await promptHelper.waitForPresentationList<PresentationType>(
promptOptions.suggestions,
this.roomId,
this.roomID,
promptSuggestions.call(this, parameter, command, promptOptions.suggestions)
);
}
@@ -3,9 +3,9 @@
* All rights reserved.
*/
import { MatrixEmitter, MatrixSendClient } from "../../MatrixEmitter";
import { CommandError, CommandResult } from "./Validation";
import { LogService } from "matrix-bot-sdk";
import { ActionError, ActionResult, Ok, StringUserID } from "matrix-protection-suite";
import { MatrixSendClient, SafeMatrixEmitter } from "matrix-protection-suite-for-matrix-bot-sdk";
type PresentationByReactionKey = Map<string/*reaction key*/, any/*presentation*/>;
@@ -29,8 +29,8 @@ class ReactionHandler {
private readonly promptRecordByEvent: Map<string/*event id*/, Set<ReactionPromptRecord>> = new Map();
constructor(
matrixEmitter: MatrixEmitter,
private readonly userId: string,
matrixEmitter: SafeMatrixEmitter,
private readonly userID: StringUserID,
private readonly client: MatrixSendClient,
) {
matrixEmitter.on('room.event', this.handleEvent.bind(this))
@@ -83,7 +83,7 @@ class ReactionHandler {
if (!(typeof relatedEventId === 'string' && typeof reactionKey === 'string')) {
return;
}
if (event['sender'] === this.userId) {
if (event['sender'] === this.userID) {
return;
}
const entry = this.promptRecordByEvent.get(relatedEventId);
@@ -104,7 +104,7 @@ class ReactionHandler {
public async waitForReactionToPrompt<T>(
roomId: string, eventId: string, presentationByReaction: PresentationByReactionKey, timeout = 600_000 // ten minutes
): Promise<CommandResult<T, CommandError>> {
): Promise<ActionResult<T>> {
let record;
let timeoutId;
const presentationOrTimeout = await Promise.race([
@@ -120,9 +120,9 @@ class ReactionHandler {
if (record !== undefined) {
this.removePromptRecordForEvent(eventId, record);
}
return CommandError.Result(`Timed out while waiting for a response to the prompt`);
return ActionError.Result(`Timed out while waiting for a response to the prompt`);
} else {
return CommandResult.Ok(presentationOrTimeout as T);
return Ok(presentationOrTimeout as T);
}
}
}
@@ -154,11 +154,11 @@ export class PromptResponseListener {
private readonly reactionHandler: ReactionHandler;
constructor(
matrixEmitter: MatrixEmitter,
userId: string,
matrixEmitter: SafeMatrixEmitter,
userID: StringUserID,
client: MatrixSendClient,
) {
this.reactionHandler = new ReactionHandler(matrixEmitter, userId, client);
this.reactionHandler = new ReactionHandler(matrixEmitter, userID, client);
}
private indexToReactionKey(index: number): string {
@@ -168,7 +168,7 @@ export class PromptResponseListener {
// This won't work, we have to have a special key in the original event
// that means we should be waiting for it, that can't be abused/forged.
// As we can't have the event id AOT.
public async waitForPresentationList<T>(presentations: T[], roomId: string, eventPromise: Promise<string>): Promise<CommandResult<T>> {
public async waitForPresentationList<T>(presentations: T[], roomId: string, eventPromise: Promise<string>): Promise<ActionResult<T>> {
const presentationByReactionKey = presentations.reduce(
(map: PresentationByReactionKey, presentation: T, index: number) => {
return map.set(this.indexToReactionKey(index), presentation);
@@ -4,8 +4,8 @@
*/
import { EventEmitter } from "stream";
import { MatrixEmitter, MatrixSendClient } from "../../MatrixEmitter";
import { LogService } from "matrix-bot-sdk";
import { MatrixSendClient, SafeMatrixEmitter } from "matrix-protection-suite-for-matrix-bot-sdk";
const REACTION_ANNOTATION_KEY = 'ge.applied-langua.ge.draupnir.reaction_handler';
@@ -92,14 +92,14 @@ export class MatrixReactionHandler extends EventEmitter {
* Start listening for reactions to events.
* Called normally by an associated mjolnir instance when it is started.
*/
public start(emitter: MatrixEmitter): void {
public start(emitter: SafeMatrixEmitter): void {
emitter.on('room.event', this.listener);
}
/**
* Stop listening for reactions to events.
*/
public stop(emitter: MatrixEmitter): void {
public stop(emitter: SafeMatrixEmitter): void {
emitter.off('room.event', this.listener);
}
@@ -24,15 +24,15 @@ limitations under the License.
* are NOT distributed, contributed, or committed under the Apache License.
*/
import { ActionError, ActionResult, Ok, ResultError, isError } from "matrix-protection-suite";
import { ISuperCoolStream, Keyword, ReadItem, SuperCoolStream } from "./CommandReader";
import { PromptOptions } from "./PromptForAccept";
import { CommandError, CommandResult } from "./Validation";
export interface IArgumentStream extends ISuperCoolStream<ReadItem[]> {
rest(): ReadItem[],
isPromptable(): boolean,
// should prompt really return a new stream?
prompt(parameterDescription: ParameterDescription): Promise<CommandResult<ReadItem>>,
prompt(parameterDescription: ParameterDescription): Promise<ActionResult<ReadItem>>,
}
export class ArgumentStream extends SuperCoolStream<ReadItem[]> implements IArgumentStream {
@@ -44,7 +44,7 @@ export class ArgumentStream extends SuperCoolStream<ReadItem[]> implements IArgu
return false;
}
prompt(parameterDescription: ParameterDescription): Promise<CommandResult<ReadItem>> {
prompt(parameterDescription: ParameterDescription): Promise<ActionResult<ReadItem>> {
throw new TypeError("This argument stream is NOT promptable, did you even check isPromptable().");
}
}
@@ -52,7 +52,7 @@ export class ArgumentStream extends SuperCoolStream<ReadItem[]> implements IArgu
// TODO: Presentation types should be extracted to their own file.
// FIXME: PresentationTypes should not be limited to ReadItems.
export type PredicateIsParameter = (readItem: ReadItem) => CommandResult<true>;
export type PredicateIsParameter = (readItem: ReadItem) => ActionResult<true>;
export interface PresentationType {
validator: PredicateIsParameter,
@@ -86,10 +86,10 @@ export function simpleTypeValidator(name: string, predicate: (readItem: ReadItem
return (readItem: ReadItem) => {
const result = predicate(readItem);
if (result) {
return CommandResult.Ok(result);
return Ok(result);
} else {
// How do we accurately denote the type when it includes spaces in its name, same for the read item?
return CommandError.Result(`Was expecting a match for the presentation type: ${name} but got ${readItem}.`);
return ActionError.Result(`Was expecting a match for the presentation type: ${name} but got ${readItem}.`);
}
}
}
@@ -98,7 +98,7 @@ export function presentationTypeOf(presentation: unknown): PresentationType|unde
// We have no concept of presentation-subtype
// But we have a top type which is any...
const candidates = [...PRESENTATION_TYPES.values()]
.filter(possibleType => possibleType.validator(presentation as ReadItem).isOk()
.filter(possibleType => possibleType.validator(presentation as ReadItem).isOkay
&& possibleType.name !== 'any'
);
if (candidates.length === 0) {
@@ -154,34 +154,34 @@ export class RestDescription<ExecutorContext = unknown> implements ParameterDesc
* Parse the rest of a command.
* @param stream An argument stream that starts at the rest of a command.
* @param keywordParser Used to store any keywords found in the rest of the command.
* @returns A CommandResult of ReadItems associated with the rest of the command.
* @returns A ActionResult of ReadItems associated with the rest of the command.
* If a ReadItem or Keyword is invalid for the command, then an error will be returned.
*/
public async parseRest(stream: IArgumentStream, promptForRest: boolean, keywordParser: KeywordParser): Promise<CommandResult<ReadItem[]>> {
public async parseRest(stream: IArgumentStream, promptForRest: boolean, keywordParser: KeywordParser): Promise<ActionResult<ReadItem[]>> {
const items: ReadItem[] = [];
if (this.prompt && promptForRest && stream.isPromptable() && stream.peekItem() === undefined) {
const result = await stream.prompt(this);
if (result.isErr()) {
return result as any;
if (isError(result)) {
return result;
}
}
while (stream.peekItem() !== undefined) {
const keywordResult = keywordParser.parseKeywords(stream);
if (keywordResult.isErr()) {
return CommandResult.Err(keywordResult.err);
if (isError(keywordResult)) {
return keywordResult;
}
if (stream.peekItem() !== undefined) {
const validationResult = this.acceptor.validator(stream.peekItem());
if (validationResult.isErr()) {
if (isError(validationResult)) {
return ArgumentParseError.Result(
validationResult.err.message,
validationResult.error.message,
{ parameter: this, stream }
);
}
items.push(stream.readItem());
}
}
return CommandResult.Ok(items);
return Ok(items);
}
}
@@ -263,24 +263,24 @@ class KeywordParser {
}
private readKeywordAssociatedProperty(keyword: KeywordPropertyDescription, itemStream: IArgumentStream): CommandResult<any, ArgumentParseError> {
private readKeywordAssociatedProperty(keyword: KeywordPropertyDescription, itemStream: IArgumentStream): ActionResult<any, ArgumentParseError> {
if (itemStream.peekItem() !== undefined && !(itemStream.peekItem() instanceof Keyword)) {
const validationResult = keyword.acceptor.validator(itemStream.peekItem());
if (validationResult.isOk()) {
return CommandResult.Ok(itemStream.readItem());
if (validationResult.isOkay) {
return Ok(itemStream.readItem());
} else {
return ArgumentParseError.Result(validationResult.err.message, { parameter: keyword, stream: itemStream });
return ArgumentParseError.Result(validationResult.error.message, { parameter: keyword, stream: itemStream });
}
} else {
if (!keyword.isFlag) {
return ArgumentParseError.Result(`An associated argument was not provided for the keyword ${keyword.name}.`, { parameter: keyword, stream: itemStream });
} else {
return CommandResult.Ok(true);
return Ok(true);
}
}
}
public parseKeywords(itemStream: IArgumentStream): CommandResult<this> {
public parseKeywords(itemStream: IArgumentStream): ActionResult<this> {
while (itemStream.peekItem() !== undefined && itemStream.peekItem() instanceof Keyword) {
const item = itemStream.readItem() as Keyword;
const description = this.description.description[item.designator];
@@ -298,7 +298,7 @@ class KeywordParser {
}
} else {
const associatedPropertyResult = this.readKeywordAssociatedProperty(description, itemStream);
if (associatedPropertyResult.isErr()) {
if (isError(associatedPropertyResult)) {
return associatedPropertyResult;
} else {
this.arguments.set(description.name, associatedPropertyResult.ok);
@@ -306,21 +306,21 @@ class KeywordParser {
}
}
return CommandResult.Ok(this);
return Ok(this);
}
public async parseRest(stream: IArgumentStream, shouldPromptForRest = false, restDescription?: RestDescription): Promise<CommandResult<ReadItem[]|undefined>> {
public async parseRest(stream: IArgumentStream, shouldPromptForRest = false, restDescription?: RestDescription): Promise<ActionResult<ReadItem[]|undefined>> {
if (restDescription !== undefined) {
return await restDescription.parseRest(stream, shouldPromptForRest, this)
} else {
const result = this.parseKeywords(stream);
if (result.isErr()) {
return CommandResult.Err(result.err);
if (isError(result)) {
return result;
}
if (stream.peekItem() !== undefined) {
return CommandError.Result(`There is an unexpected non-keyword argument ${JSON.stringify(stream.peekItem())}`);
return ActionError.Result(`There is an unexpected non-keyword argument ${JSON.stringify(stream.peekItem())}`);
} else {
return CommandResult.Ok(undefined);
return Ok(undefined);
}
}
}
@@ -347,11 +347,11 @@ export interface ParameterDescription<ExecutorContext = unknown> {
prompt?: Prompt<ExecutorContext>,
}
export type ParameterParser = (stream: IArgumentStream) => Promise<CommandResult<ParsedArguments>>;
export type ParameterParser = (stream: IArgumentStream) => Promise<ActionResult<ParsedArguments>>;
// So this should really just be something used by defineInterfaceCommand which turns parameters into a validator that can be used.
// It can't be, because then otherwise how does the semantics for union work?
// We should have a new type of CommandResult that accepts a ParamterDescription, and can render what's wrong (e.g. missing parameter).
// We should have a new type of ActionResult that accepts a ParamterDescription, and can render what's wrong (e.g. missing parameter).
// Showing where in the item stream it is missing and the command syntax and everything lovely like that.
// How does that work with Union?
export function parameters(descriptions: ParameterDescription[], rest: undefined|RestDescription = undefined, keywords: KeywordsDescription = new KeywordsDescription({}, false)): IArgumentListParser {
@@ -377,21 +377,21 @@ class ArgumentListParser implements IArgumentListParser {
) {
}
public async parse(stream: IArgumentStream): Promise<CommandResult<ParsedArguments>> {
public async parse(stream: IArgumentStream): Promise<ActionResult<ParsedArguments>> {
let hasPrompted = false;
const keywordsParser = this.keywords.getParser();
for (const parameter of this.descriptions) {
// it eats any keywords at any point in the stream
// as they can appear at any point technically.
const keywordResult = keywordsParser.parseKeywords(stream);
if (keywordResult.isErr()) {
return CommandResult.Err(keywordResult.err);
if (isError(keywordResult)) {
return keywordResult;
}
if (stream.peekItem() === undefined) {
if (parameter.prompt && stream.isPromptable()) {
const promptResult = await stream.prompt(parameter);
if (promptResult.isErr()) {
return promptResult as any;
if (isError(promptResult)) {
return promptResult;
}
hasPrompted = true;
} else {
@@ -402,20 +402,20 @@ class ArgumentListParser implements IArgumentListParser {
}
}
const result = parameter.acceptor.validator(stream.peekItem());
if (result.isErr()) {
return ArgumentParseError.Result(result.err.message, { parameter, stream });
if (isError(result)) {
return ArgumentParseError.Result(result.error.message, { parameter, stream });
}
stream.readItem();
}
const restResult = await keywordsParser.parseRest(stream, hasPrompted, this.rest);
if (restResult.isErr()) {
return CommandResult.Err(restResult.err);
if (isError(restResult)) {
return restResult;
}
const immediateArguments = restResult.ok === undefined
|| restResult.ok.length === 0
? stream.source
: stream.source.slice(0, stream.source.indexOf(restResult.ok[0]))
return CommandResult.Ok({
return Ok({
immediateArguments: immediateArguments,
keywords: keywordsParser.getKeywords(),
rest: restResult.ok
@@ -423,15 +423,15 @@ class ArgumentListParser implements IArgumentListParser {
}
}
export class AbstractArgumentParseError extends CommandError {
export class AbstractArgumentParseError extends ActionError {
constructor(
public readonly stream: IArgumentStream,
message: string) {
super(message)
}
public static Result<Ok>(message: string, options: { stream: IArgumentStream }): CommandResult<Ok, AbstractArgumentParseError> {
return CommandResult.Err(new AbstractArgumentParseError(options.stream, message));
public static Result(message: string, options: { stream: IArgumentStream }): ActionResult<never, AbstractArgumentParseError> {
return ResultError(new AbstractArgumentParseError(options.stream, message));
}
}
@@ -443,14 +443,14 @@ export class ArgumentParseError extends AbstractArgumentParseError {
super(stream, message)
}
public static Result<Ok>(message: string, options: { parameter: ParameterDescription, stream: IArgumentStream }): CommandResult<Ok, ArgumentParseError> {
return CommandResult.Err(new ArgumentParseError(options.parameter, options.stream, message));
public static Result<Ok>(message: string, options: { parameter: ParameterDescription, stream: IArgumentStream }): ActionResult<Ok, ArgumentParseError> {
return ResultError(new ArgumentParseError(options.parameter, options.stream, message));
}
}
export class UnexpectedArgumentError extends AbstractArgumentParseError {
public static Result<Ok>(message: string, options: { stream: IArgumentStream }): CommandResult<Ok, UnexpectedArgumentError> {
return CommandResult.Err(new UnexpectedArgumentError(options.stream, message));
public static Result<Ok>(message: string, options: { stream: IArgumentStream }): ActionResult<Ok, UnexpectedArgumentError> {
return ResultError(new UnexpectedArgumentError(options.stream, message));
}
}
@@ -464,10 +464,10 @@ export function union(...presentationTypes: PresentationType[]): PresentationTyp
return {
name,
validator: (readItem: ReadItem) => {
if (presentationTypes.some(p => p.validator(readItem).isOk())) {
return CommandResult.Ok(true);
if (presentationTypes.some(p => p.validator(readItem).isOkay)) {
return Ok(true);
} else {
return CommandError.Result(`Read item didn't match any of the presentaiton types ${name}`);
return ActionError.Result(`Read item didn't match any of the presentaiton types ${name}`);
}
}
}
@@ -3,10 +3,10 @@
* All rights reserved.
*/
import { ActionResult } from "matrix-protection-suite";
import { ReadItem } from "./CommandReader";
import { BaseFunction, InterfaceCommand } from "./InterfaceCommand";
import { ArgumentStream, ParameterDescription } from "./ParameterParsing";
import { CommandResult } from "./Validation";
export interface PromptOptions<PresentationType = any> {
readonly suggestions: PresentationType[]
@@ -19,7 +19,7 @@ export interface PromptOptions<PresentationType = any> {
*/
export interface InterfaceAcceptor<PresentationType = any> {
readonly isPromptable: boolean
promptForAccept(parameter: ParameterDescription, invocationRecord: CommandInvocationRecord): Promise<CommandResult<PresentationType>>
promptForAccept(parameter: ParameterDescription, invocationRecord: CommandInvocationRecord): Promise<ActionResult<PresentationType>>
}
export interface CommandInvocationRecord {
@@ -43,12 +43,12 @@ export class PromptableArgumentStream extends ArgumentStream {
return this.interfaceAcceptor.isPromptable
}
public async prompt<T = ReadItem>(parameterDescription: ParameterDescription): Promise<CommandResult<T>> {
public async prompt<T = ReadItem>(parameterDescription: ParameterDescription): Promise<ActionResult<T>> {
const result = await this.interfaceAcceptor.promptForAccept(
parameterDescription,
this.invocationRecord
);
if (result.isOk()) {
if (result.isOkay) {
this.source.push(result.ok);
}
return result;