Commit 7ab3dcc9 authored by nanahira's avatar nanahira

better If and For flow control

parent 1b06b896
...@@ -127,11 +127,13 @@ export const PluginName = (name: string) => ...@@ -127,11 +127,13 @@ export const PluginName = (name: string) =>
export const If = <T>( export const If = <T>(
func: Condition<boolean, T, [Record<string, any>]>, func: Condition<boolean, T, [Record<string, any>]>,
): TypedMethodDecorator<T> => Metadata.append('KoishiIf', func); ): TypedMethodDecorator<T> =>
Metadata.append('KoishiControl', { type: 'if', condition: func });
export const For = <T>( export const For = <T>(
func: Condition<Iterable<Record<string, any>>, T, [Record<string, any>]>, func: Condition<Iterable<Record<string, any>>, T, [Record<string, any>]>,
): TypedMethodDecorator<T> => Metadata.append('KoishiFor', func); ): TypedMethodDecorator<T> =>
Metadata.append('KoishiControl', { type: 'for', condition: func });
export const UseModel = (...models: ModelClassType[]): ClassDecorator => export const UseModel = (...models: ModelClassType[]): ClassDecorator =>
TopLevelAction((ctx) => { TopLevelAction((ctx) => {
......
// metadatas // metadatas
import { Context, Schema } from 'koishi'; import { Context, Schema } from 'koishi';
import { Condition, ProvideDefinition, SystemInjectFun } from './interfaces'; import {
Condition,
ControlType,
ControlTypeMap,
ProvideDefinition,
SystemInjectFun,
} from './interfaces';
import { ClassType } from 'schemastery-gen'; import { ClassType } from 'schemastery-gen';
export const KoishiServiceInjectSym = 'KoishiServiceInjectSym'; export const KoishiServiceInjectSym = 'KoishiServiceInjectSym';
...@@ -19,12 +25,7 @@ export interface MetadataArrayMap { ...@@ -19,12 +25,7 @@ export interface MetadataArrayMap {
KoishiSystemInjectSymKeys: string; KoishiSystemInjectSymKeys: string;
KoishiAddUsingList: keyof Context.Services; KoishiAddUsingList: keyof Context.Services;
KoishiPartialUsing: keyof Context.Services; KoishiPartialUsing: keyof Context.Services;
KoishiIf: Condition<boolean, any, [Record<string, any>]>; KoishiControl: ControlType;
KoishiFor: Condition<
Iterable<Record<string, any>>,
any,
[Record<string, any>]
>;
} }
export interface MetadataMap { export interface MetadataMap {
......
...@@ -53,3 +53,15 @@ export type TypedMethodDecorator<T> = <P>( ...@@ -53,3 +53,15 @@ export type TypedMethodDecorator<T> = <P>(
propertyKey: string | symbol, propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<P>, descriptor: TypedPropertyDescriptor<P>,
) => void; ) => void;
export interface ControlTypeMap {
if: boolean;
for: Iterable<Record<string, any>>;
}
export interface ControlType<
T extends keyof ControlTypeMap = keyof ControlTypeMap,
> {
type: T;
condition: Condition<ControlTypeMap[T], any, [Record<string, any>]>;
}
import { Context, Plugin, Schema, WebSocketLayer } from 'koishi'; import { Context, Plugin, Schema, WebSocketLayer } from 'koishi';
import { import {
Condition, Condition,
ControlType,
KoishiAddUsingList, KoishiAddUsingList,
KoishiPartialUsing, KoishiPartialUsing,
KoishiServiceInjectSym, KoishiServiceInjectSym,
...@@ -150,8 +151,6 @@ export function DefinePlugin<T>( ...@@ -150,8 +151,6 @@ export function DefinePlugin<T>(
methodKey: keyof C & string, methodKey: keyof C & string,
view: Record<string, any> = {}, view: Record<string, any> = {},
) { ) {
const conditions = reflector.getArray('KoishiIf', this, methodKey);
if (conditions.some((condition) => !condition(this, view))) return;
const ctx = this.__registrar.getScopeContext( const ctx = this.__registrar.getScopeContext(
this.__ctx, this.__ctx,
methodKey, methodKey,
...@@ -178,27 +177,40 @@ export function DefinePlugin<T>( ...@@ -178,27 +177,40 @@ export function DefinePlugin<T>(
_registerDeclarationsWithStack( _registerDeclarationsWithStack(
methodKey: keyof C & string, methodKey: keyof C & string,
stack: Condition< stack: ControlType[],
Iterable<Record<string, any>>,
any,
[Record<string, any>]
>[],
existing: Record<string, any> = {}, existing: Record<string, any> = {},
) { ) {
if (!stack.length) { if (!stack.length) {
return this._registerDeclarationsResolving(methodKey, existing); return this._registerDeclarationsResolving(methodKey, existing);
} }
const [iter, ...rest] = stack; const rest = [...stack];
for (const view of iter(this, existing)) { const control = rest.pop();
this._registerDeclarationsWithStack(methodKey, rest, {
...existing, switch (control.type) {
...view, case 'if':
}); if (!(control as ControlType<'if'>).condition(this, existing))
return;
return this._registerDeclarationsWithStack(
methodKey,
rest,
existing,
);
case 'for':
for (const view of (control as ControlType<'for'>).condition(
this,
existing,
)) {
this._registerDeclarationsWithStack(methodKey, rest, {
...existing,
...view,
});
}
return;
} }
} }
_registerDeclarationsFor(methodKey: keyof C & string) { _registerDeclarationsFor(methodKey: keyof C & string) {
const stack = reflector.getArray('KoishiFor', this, methodKey); const stack = reflector.getArray('KoishiControl', this, methodKey);
return this._registerDeclarationsWithStack(methodKey, stack); return this._registerDeclarationsWithStack(methodKey, stack);
} }
......
...@@ -23,8 +23,9 @@ class MyPlugin extends BasePlugin<{ foo: boolean; bar: boolean }> { ...@@ -23,8 +23,9 @@ class MyPlugin extends BasePlugin<{ foo: boolean; bar: boolean }> {
class MyPlugin2 extends BasePlugin<{ class MyPlugin2 extends BasePlugin<{
prefix: string; prefix: string;
commands: { name: string; return: string }[]; commands: { name: string; return: string }[];
matrix: { commands: { name: string; return: string }[] }[];
}> { }> {
@For(({ config }) => config.commands) @For<MyPlugin2>(({ config }) => config.commands)
@If<MyPlugin2>((_, def) => def.name !== 'badthing') @If<MyPlugin2>((_, def) => def.name !== 'badthing')
@UseCommand('{{name}}') @UseCommand('{{name}}')
onCommand( onCommand(
...@@ -33,6 +34,16 @@ class MyPlugin2 extends BasePlugin<{ ...@@ -33,6 +34,16 @@ class MyPlugin2 extends BasePlugin<{
) { ) {
return prefix + returnValue; return prefix + returnValue;
} }
@For<MyPlugin2>(({ config }) => config.matrix)
@For<MyPlugin2>((_, matrix) => matrix.commands)
@If<MyPlugin2>((_, def) => def.name !== 'badthing')
@UseCommand('{{name}}')
onMatrix(
@PutValue('{{return}}') returnValue: string,
@PutValue('{{prefix}}') prefix: string,
) {
return prefix + returnValue;
}
} }
describe('It should register conditionally', () => { describe('It should register conditionally', () => {
...@@ -54,6 +65,22 @@ describe('It should register conditionally', () => { ...@@ -54,6 +65,22 @@ describe('It should register conditionally', () => {
{ name: 'bar', return: 'baz' }, { name: 'bar', return: 'baz' },
{ name: 'badthing', return: 'bad' }, { name: 'badthing', return: 'bad' },
], ],
matrix: [
{
commands: [
{ name: 'foo1', return: 'bar1' },
{ name: 'bar1', return: 'baz1' },
{ name: 'badthing', return: 'bad' },
],
},
{
commands: [
{ name: 'foo2', return: 'bar2' },
{ name: 'bar2', return: 'baz2' },
{ name: 'badthing', return: 'bad' },
],
},
],
prefix: '> ', prefix: '> ',
}); });
await app.start(); await app.start();
...@@ -61,6 +88,14 @@ describe('It should register conditionally', () => { ...@@ -61,6 +88,14 @@ describe('It should register conditionally', () => {
const commandBar = app.command('bar'); const commandBar = app.command('bar');
expect(await commandFoo.execute({})).toBe('> bar'); expect(await commandFoo.execute({})).toBe('> bar');
expect(await commandBar.execute({})).toBe('> baz'); expect(await commandBar.execute({})).toBe('> baz');
const commandFoo1 = app.command('foo1');
const commandBar1 = app.command('bar1');
expect(await commandFoo1.execute({})).toBe('> bar1');
expect(await commandBar1.execute({})).toBe('> baz1');
const commandFoo2 = app.command('foo2');
const commandBar2 = app.command('bar2');
expect(await commandFoo2.execute({})).toBe('> bar2');
expect(await commandBar2.execute({})).toBe('> baz2');
expect(await app.command('badthing').execute({})).toBeFalsy(); expect(await app.command('badthing').execute({})).toBeFalsy();
}); });
}); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment