Skip to content

Commit

Permalink
Cleanup KeywordPropertyDescription.
Browse files Browse the repository at this point in the history
We now take the name from the associated property and close up weird
semantics with presentation types and flags by making the keyword
acceptor the top presentation schema.

We assume that the parser doesn't actually accept any associated
argument when a flag is used. But to be able to test that we need
to be able to run the commands in the unit tests.

This will require faking the command dispatcher or interface adaptor
and then as a pre-requisite probably putting all the dependencies
in some eays to use object that the client (user of the library)
will implement/extend some base thing that logs to console.
  • Loading branch information
Gnuxie committed Sep 3, 2024
1 parent 68b0278 commit b6ea5c5
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 5 deletions.
63 changes: 60 additions & 3 deletions src/Command/KeywordParameterDescription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ import { Keyword } from "./Keyword";
import { ParameterDescription } from "./ParameterDescription";
import { ArgumentParseError, UnexpectedArgumentError } from "./ParseErrors";
import { ParsedKeywords, StandardParsedKeywords } from "./ParsedKeywords";
import { Presentation } from "./Presentation";
import { Presentation, PresentationTypeWithoutWrap } from "./Presentation";
import { RestDescription } from "./RestParameterDescription";
import { PartialCommand } from "./Command";
import {
PresentationSchema,
PresentationSchemaType,
SinglePresentationSchema,
TopPresentationSchema,
checkPresentationSchema,
printPresentationSchema,
} from "./PresentationSchema";
Expand All @@ -35,6 +39,14 @@ export interface KeywordPropertyDescription<ObjectType = unknown>
readonly isFlag: boolean;
}

export interface DescribeKeywordProperty<ObjectType = unknown> {
readonly acceptor?:
| PresentationSchema<ObjectType>
| PresentationTypeWithoutWrap<ObjectType>;
readonly isFlag?: boolean;
readonly description?: string;
}

/**
* Describes which keyword arguments can be accepted by a command.
*/
Expand Down Expand Up @@ -164,15 +176,60 @@ export class KeywordParser<TKeywordsMeta extends KeywordsMeta = KeywordsMeta> {

export type DescribeKeywordParametersOptions<
TKeywordsMeta extends KeywordsMeta = KeywordsMeta,
> = Omit<KeywordParametersDescription<TKeywordsMeta>, "getParser">;
> = {
readonly keywordDescriptions: {
[I in keyof TKeywordsMeta]: DescribeKeywordProperty<TKeywordsMeta[I]>;
};
readonly allowOtherKeys?: boolean;
};

function describeKeywordProperty<ObjectType>(
name: string,
property: DescribeKeywordProperty<ObjectType>
): KeywordPropertyDescription<ObjectType> {
if (property.acceptor === undefined) {
if (!property.isFlag) {
throw new TypeError(
"An acceptor is required if the property is not a flag."
);
}
}
const acceptor = ((acceptor) => {
if (acceptor === undefined) {
return TopPresentationSchema;
} else if ("schemaType" in acceptor) {
return acceptor;
} else {
return {
schemaType: PresentationSchemaType.Single,
presentationType: acceptor,
} as SinglePresentationSchema<ObjectType>;
}
})(property.acceptor);
return {
name,
isFlag: property.isFlag ?? false,
acceptor,
description: property.description,
};
}

export function describeKeywordParameters<
TKeywordsMeta extends KeywordsMeta = KeywordsMeta,
>(
options: DescribeKeywordParametersOptions<TKeywordsMeta>
): KeywordParametersDescription<TKeywordsMeta> {
const keywordDescriptions: Record<string, KeywordPropertyDescription> = {};
for (const [name, property] of Object.entries(options.keywordDescriptions)) {
keywordDescriptions[name] = describeKeywordProperty(
name,
property as DescribeKeywordProperty
);
}
return {
...options,
keywordDescriptions:
keywordDescriptions as KeywordPropertyDescriptionsFromKeywordsMeta<TKeywordsMeta>,
allowOtherKeys: options.allowOtherKeys ?? false,
getParser() {
return new KeywordParser(this);
},
Expand Down
2 changes: 1 addition & 1 deletion src/Command/ParameterParsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export function describeCommandParameters<
options.keywords === undefined
? describeKeywordParameters({
keywordDescriptions:
{} as KeywordParametersDescription<TKeywordsMeta>["keywordDescriptions"],
{} as DescribeKeywordParametersOptions<TKeywordsMeta>["keywordDescriptions"],
allowOtherKeys: false,
})
: describeKeywordParameters(options.keywords),
Expand Down
46 changes: 45 additions & 1 deletion src/Command/describeCommand.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import {
MatrixRoomIDPresentationType,
MatrixRoomReferencePresentationSchema,
MatrixUserIDPresentationType,
StringPresentationType,

Check failure on line 23 in src/Command/describeCommand.test.ts

View workflow job for this annotation

GitHub Actions / Unit Tests

'StringPresentationType' is declared but its value is never read.

Check failure on line 23 in src/Command/describeCommand.test.ts

View workflow job for this annotation

GitHub Actions / Lint

'StringPresentationType' is declared but its value is never read.
} from "../TextReader";
import { StandardParsedKeywords } from "./ParsedKeywords";
import { ParsedKeywords, StandardParsedKeywords } from "./ParsedKeywords";
import { tuple } from "./ParameterParsing";
import { PromptOptions } from "./PromptForAccept";
import { describeKeywordParameters } from "./KeywordParameterDescription";

Check failure on line 28 in src/Command/describeCommand.test.ts

View workflow job for this annotation

GitHub Actions / Unit Tests

'describeKeywordParameters' is declared but its value is never read.

Check failure on line 28 in src/Command/describeCommand.test.ts

View workflow job for this annotation

GitHub Actions / Lint

'describeKeywordParameters' is declared but its value is never read.

it("Can define and execute commands.", async function () {
type Context = {
Expand Down Expand Up @@ -90,3 +92,45 @@ it("Can define and execute commands.", async function () {
);
expect(isOk(banResult)).toBe(true);
});

// DescribeKeywordProperty optionally
// accepts an acceptor only when the type is flag, and replaces that
// with the TopPresentationSchema once its is turned into a KeywordPropertyDescription.
it("Can define keyword arguments.", async function () {
// so it's at this point that i ralised that we have no ability to test the parseAndInvoke
// functionality in unit tests here without implementing a MatrixInterfaceAdaptor,
// or some other adaptor for unit testing.
// Making that fake probably requires splitting out the arguments to the interface adaptor
// or the command dispatcher too, so that there's something that has all those callbacks
// defined on them and they can be optionally implemented by the consumer.
// That will make it really easy for people to get started using the library without wondering
// what the hell these 200 dependencies are that they have to instantiate somehow.
const KeywordsCommandTest = describeCommand({

Check failure on line 108 in src/Command/describeCommand.test.ts

View workflow job for this annotation

GitHub Actions / Unit Tests

'KeywordsCommandTest' is declared but its value is never read.

Check failure on line 108 in src/Command/describeCommand.test.ts

View workflow job for this annotation

GitHub Actions / Lint

'KeywordsCommandTest' is declared but its value is never read.
summary: "A command to test keyword arguments",
async executor(
_context: never,
_info: unknown,
keywords: ParsedKeywords
): Promise<Result<unknown>> {},

Check failure on line 114 in src/Command/describeCommand.test.ts

View workflow job for this annotation

GitHub Actions / Unit Tests

A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.

Check failure on line 114 in src/Command/describeCommand.test.ts

View workflow job for this annotation

GitHub Actions / Lint

A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
parameters: [],
keywords: {
keywordDescriptions: {
"dry-run": {
isFlag: true,
description:
"Runs the kick command without actually removing any users.",
},
glob: {
isFlag: true,
description:
"Allows globs to be used to kick several users from rooms.",
},
room: {
acceptor: MatrixRoomReferencePresentationSchema,
description:
"Allows the command to be scoped to just one protected room.",
},
},
},
});
});

0 comments on commit b6ea5c5

Please sign in to comment.