Commit 11ecc338 authored by nanahira's avatar nanahira

add PutObject for class-based command params

parent f95469e6
......@@ -221,6 +221,7 @@ export const PutTemplate = (name: string, text: string | Dict<string>) =>
name,
text: adaptLocaleDict(text),
});
export const PutObject = () => CommandPut.decorate('typeClass');
export const TopLevelAction = (action: TopLevelActionDef): ClassDecorator =>
Metadata.append('KoishiTopLevelAction', action);
......
// metadatas
import {
CommandDefinitionFun,
MappingStruct,
OnContextFunction,
TopLevelActionDef,
} from './interfaces';
import { DoRegister } from '../registry';
import { CommandPut, DoRegister } from '../registry';
export const KoishiOnContextScope = 'KoishiOnContextScope';
export const KoishiDoRegister = 'KoishiDoRegister';
......@@ -19,8 +20,13 @@ export interface MetadataArrayMap {
KoishiCommandDefinition: CommandDefinitionFun;
KoishiDoRegisterKeys: string;
KoishiTopLevelAction: TopLevelActionDef;
KoishiPutClassFieldKeys: string;
}
export interface MetadataMap {
KoishiDoRegister: DoRegister.Config;
KoishiPutClassField: MappingStruct<
CommandPut.ConfigMap,
keyof CommandPut.ConfigMap
>;
}
......@@ -13,6 +13,8 @@ import {
applyOptionToCommand,
registerTemplate,
} from '../../utility';
import { Metadata } from '../../meta/metadata.decorators';
import { reflector } from '../../meta/meta-fetch';
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace CommandPut {
......@@ -29,6 +31,7 @@ export namespace CommandPut {
sessionField: keyof Session;
renderer: string | undefined;
template: TemplateConfig;
typeClass: void;
}
export type Config<K extends keyof ConfigMap = keyof ConfigMap> =
......@@ -79,6 +82,20 @@ export namespace CommandPut {
registerTemplate(data, ctx, cmd),
);
preRegistry.extend('typeClass', (data, cmd, ctx, nativeType) => {
const keys = reflector.getArray('KoishiPutClassFieldKeys', nativeType);
for (const key of keys) {
const meta = reflector.get('KoishiPutClassField', nativeType, key);
if (!meta) continue;
const propertyNativeType = Reflect.getMetadata(
'design:type',
nativeType.prototype,
key,
);
preRegistry.execute(meta, cmd, ctx, propertyNativeType);
}
});
export const registry = new MethodRegistry<
ConfigMap,
any,
......@@ -122,17 +139,47 @@ export namespace CommandPut {
(data, argv, args) => (params: object) =>
argv.session.text(`.${data.name}`, params),
);
registry.extend(
'typeClass',
(data, argv, args, nativeType: { new (): any }) => {
const keys = reflector.getArray('KoishiPutClassFieldKeys', nativeType);
const obj = new nativeType();
for (const key of keys) {
const meta = reflector.get('KoishiPutClassField', nativeType, key);
if (!meta) continue;
const propertyNativeType = Reflect.getMetadata(
'design:type',
nativeType.prototype,
key,
);
obj[key] = registry.execute(meta, argv, args, propertyNativeType);
}
return obj;
},
);
export function decorate<T extends keyof ConfigMap>(
type: T,
data?: ConfigMap[T],
): ParameterDecorator {
return (obj, key: string, index) => {
const objClass = obj.constructor;
const list: Config<T>[] =
Reflect.getMetadata(KoishiCommandPutDef, objClass, key) || [];
list[index] = GenerateMappingStruct(type, data);
Reflect.defineMetadata(KoishiCommandPutDef, list, objClass, key);
): ParameterDecorator & PropertyDecorator {
return (obj, key: string, index?: number) => {
const def = GenerateMappingStruct(type, data);
if (typeof index === 'number') {
// As a parameter decorator
const objClass = obj.constructor;
const list: Config<T>[] =
Reflect.getMetadata(KoishiCommandPutDef, objClass, key) || [];
list[index] = def;
Reflect.defineMetadata(KoishiCommandPutDef, list, objClass, key);
} else {
// As a property decorator
Metadata.set(
'KoishiPutClassField',
def,
'KoishiPutClassFieldKeys',
)(obj, key);
}
return obj;
};
}
}
import {
CommandUsage,
PutArg,
PutObject,
PutOption,
UseCommand,
UseEvent,
......@@ -10,6 +11,14 @@ import { App, Command, Next, Session } from 'koishi';
import { Registrar } from '../src/register';
import { EventNameAndPrepend } from '../src/def';
class SkirtArg {
@PutArg(0)
count: number;
@PutOption('color', '-c <color>')
color: string;
}
class MyClass {
@UseMiddleware()
async onPing(session: Session, next: Next) {
......@@ -36,6 +45,11 @@ class MyClass {
async onCount(@PutArg(0) count: number) {
return `I have ${count} dresses.`;
}
@UseCommand('skirt')
async onSkirt(@PutObject() arg: SkirtArg) {
return `I have ${arg.count} ${arg.color} skirts.`;
}
}
const registrar = new Registrar(new MyClass());
......@@ -79,4 +93,15 @@ describe('Register', () => {
expect(command._arguments[0].type).toBe('number');
expect(command.execute({ args: ['4'] })).resolves.toBe('I have 4 dresses.');
});
it('should work on class type', () => {
const result = registrar.register(app, 'onSkirt');
expect(result.type).toBe('command');
const command: Command = result.result;
expect(command._arguments[0].type).toBe('number');
expect(command._options.color.type).toBe('string');
expect(
command.execute({ args: ['4'], options: { color: 'red' } }),
).resolves.toBe('I have 4 red skirts.');
});
});
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