mirror of
https://github.com/the-draupnir-project/Draupnir.git
synced 2026-05-25 18:04:01 +00:00
help for commands.
This commit is NOT contributed under the Apache-2.0 License. Copyright (C) 2022 Gnuxie <Gnuxie@protonmail.com> All rights reserved.
This commit is contained in:
@@ -54,6 +54,14 @@ export class CommandTable<ExecutorType extends BaseFunction> {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to render the help command.
|
||||
* @returns All of the commands in this table.
|
||||
*/
|
||||
public getCommands(): InterfaceCommand<BaseFunction>[] {
|
||||
return [...this.flattenedCommands.values()];
|
||||
}
|
||||
|
||||
// We use the argument stream so that they can use stream.rest() to get the unconsumed arguments.
|
||||
public findAMatchingCommand(stream: ArgumentStream) {
|
||||
const tableHelper = (table: CommandLookupEntry<ExecutorType>, argumentStream: ArgumentStream): undefined|InterfaceCommand<ExecutorType> => {
|
||||
@@ -112,9 +120,13 @@ export function findCommandTable<ExecutorType extends BaseFunction>(name: string
|
||||
|
||||
export class InterfaceCommand<ExecutorType extends BaseFunction> {
|
||||
constructor(
|
||||
private readonly argumentListParser: IArgumentListParser,
|
||||
public readonly argumentListParser: IArgumentListParser,
|
||||
private readonly command: ExecutorType,
|
||||
public readonly designator: string[],
|
||||
/** A short one line summary of what the command does to display alongside it's help */
|
||||
public readonly summary: string,
|
||||
/** A longer description that goes into detail. */
|
||||
public readonly description?: string,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -144,11 +156,15 @@ export function defineInterfaceCommand<ExecutorType extends BaseFunction>(descri
|
||||
table: string,
|
||||
command: ExecutorType,
|
||||
designator: string[],
|
||||
summary: string,
|
||||
description?: string,
|
||||
}) {
|
||||
const command = new InterfaceCommand<ExecutorType>(
|
||||
description.paramaters,
|
||||
description.command,
|
||||
description.designator,
|
||||
description.summary,
|
||||
description.description,
|
||||
);
|
||||
const table = findCommandTable(description.table);
|
||||
table.internCommand(command);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (C) 2022 Gnuxie <Gnuxie@protonmail.com>
|
||||
*/
|
||||
|
||||
import { MatrixSendClient } from "../../MatrixEmitter";
|
||||
import { BaseFunction, InterfaceCommand } from "./InterfaceCommand";
|
||||
import { KeywordParser } from "./ParamaterParsing";
|
||||
import { CommandError, CommandResult } from "./Validation";
|
||||
|
||||
function requiredArgument(argumentName: string): string {
|
||||
return `<${argumentName}>`;
|
||||
}
|
||||
|
||||
function keywordArgument(keyword: string): string {
|
||||
// ahh fuck what about defaults for keys?
|
||||
return `[--${keyword}]`;
|
||||
}
|
||||
|
||||
// they should be allowed to name the rest argument...
|
||||
function restArgument(): string {
|
||||
return `[...rest]`;
|
||||
}
|
||||
|
||||
function renderCommandHelp(command: InterfaceCommand<BaseFunction>): string {
|
||||
let text = '';
|
||||
for (const designator of command.designator) {
|
||||
text += `${designator} `
|
||||
}
|
||||
for (const description of command.argumentListParser.descriptions) {
|
||||
text += `${requiredArgument(description.name)} `;
|
||||
}
|
||||
const restParser = command.argumentListParser.restParser;
|
||||
if (restParser !== undefined) {
|
||||
// not too happy with how keywords are represented here., like there's just the keys with no context smh.
|
||||
if (restParser instanceof KeywordParser) {
|
||||
for (const keyword of Object.keys(restParser.description)) {
|
||||
if (keyword === "allowOtherKeys") {
|
||||
continue;
|
||||
}
|
||||
// ahh fuck what about defaults for keys?
|
||||
text += `${keywordArgument(keyword)} `;
|
||||
}
|
||||
if (restParser.description.allowOtherKeys) {
|
||||
text += `${restArgument()} `;
|
||||
}
|
||||
} else {
|
||||
text += `${restArgument()} `;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
// What is really needed is a rendering protocol, that works with bullshit text+html that's really just string building like we're doing here or some other media format
|
||||
export async function renderHelp(client: MatrixSendClient, commandRoomId: string, event: any, result: CommandResult<InterfaceCommand<BaseFunction>[], CommandError>): Promise<void> {
|
||||
const commands = result.ok;
|
||||
let text = ''
|
||||
for (const command of commands) {
|
||||
text += `${renderCommandHelp(command)}\n`;
|
||||
}
|
||||
await client.replyNotice(commandRoomId, event, text);
|
||||
}
|
||||
@@ -106,18 +106,15 @@ interface KeywordsDescription {
|
||||
readonly allowOtherKeys: boolean
|
||||
}
|
||||
|
||||
interface KeywordPropertyDescription {
|
||||
interface KeywordPropertyDescription extends ParamaterDescription {
|
||||
readonly isFlag: boolean;
|
||||
readonly propertyPredicate?: PredicateIsParamater;
|
||||
readonly name: string,
|
||||
|
||||
}
|
||||
|
||||
// Things that are also needed that are not done yet:
|
||||
// 1) We need to figure out what happens to aliases for keywords..
|
||||
// 2) We need to sort out the predicates thing.
|
||||
export class KeywordParser extends RestParser {
|
||||
constructor(private readonly description: KeywordsDescription) {
|
||||
constructor(public readonly description: KeywordsDescription) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -127,7 +124,6 @@ export class KeywordParser extends RestParser {
|
||||
*/
|
||||
public parseRest(itemStream: ArgumentStream): CommandResult<DestructableRest> {
|
||||
const destructable: DestructableRest = { rest: [] };
|
||||
// Wrong, we can't use position, we need a stream.
|
||||
while (itemStream.peekItem() !== undefined) {
|
||||
const item = itemStream.readItem();
|
||||
if (item instanceof Keyword) {
|
||||
@@ -141,7 +137,7 @@ export class KeywordParser extends RestParser {
|
||||
return CommandResult.Ok(property);
|
||||
} else {
|
||||
if (!description.isFlag) {
|
||||
return CommandError.Result(`An associated argument was not provided for the keyword ${description.name}.`)
|
||||
return ArgumentParseError.Result(`An associated argument was not provided for the keyword ${description.name}.`, { paramater: description, stream: itemStream })
|
||||
}
|
||||
return CommandResult.Ok(true);
|
||||
}
|
||||
@@ -206,7 +202,7 @@ class ArgumentListParser implements IArgumentListParser {
|
||||
for (const paramater of descriptions) {
|
||||
if (itemStream.peekItem() === undefined) {
|
||||
// FIXME asap: we need a proper paramater description?
|
||||
return CommandError.Result(`An argument for the paramater ${paramater.name} was expected but was not provided.`);
|
||||
return ArgumentParseError.Result(`An argument for the paramater ${paramater.name} was expected but was not provided.`, { paramater, stream: itemStream });
|
||||
}
|
||||
const item = itemStream.readItem()!;
|
||||
const result = paramater.acceptor.validator(item);
|
||||
@@ -228,6 +224,19 @@ class ArgumentListParser implements IArgumentListParser {
|
||||
}
|
||||
}
|
||||
|
||||
export class ArgumentParseError extends CommandError {
|
||||
constructor(
|
||||
public readonly paramater: ParamaterDescription,
|
||||
public readonly stream: ArgumentStream,
|
||||
message: string) {
|
||||
super(message)
|
||||
}
|
||||
|
||||
public static Result<Ok>(message: string, options: { paramater: ParamaterDescription, stream: ArgumentStream }): CommandResult<Ok, ArgumentParseError> {
|
||||
return CommandResult.Err(new ArgumentParseError(options.paramater, options.stream, message));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* I don't think we should use `union` and it should be replaced by a presentationTypeTranslator
|
||||
* these are specific to applications e.g. imagine you want to resolve an alias or something.
|
||||
|
||||
@@ -90,7 +90,13 @@ export class CommandError {
|
||||
|
||||
}
|
||||
|
||||
public static Result<Ok>(message: string): CommandResult<Ok> {
|
||||
/**
|
||||
* Utility to wrap the error into a Result.
|
||||
* @param message The message for the CommandError.
|
||||
* @param _options This exists so that the method is extensible by subclasses. Otherwise they wouldn't be able to pass other constructor arguments through this method.
|
||||
* @returns
|
||||
*/
|
||||
public static Result<Ok>(message: string, _options = {}): CommandResult<Ok> {
|
||||
return CommandResult.Err(new CommandError(message));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user