Commit 6c959cf8 authored by nanahira's avatar nanahira

Merge branch 'put-type'

parents 5fddfa16 50d2d8d6
Pipeline #10673 failed with stages
in 1 minute and 28 seconds
{ {
"name": "koishi-decorators", "name": "koishi-decorators",
"version": "1.2.8", "version": "1.2.9",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "koishi-decorators", "name": "koishi-decorators",
"version": "1.2.8", "version": "1.2.9",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/koa": "^2.13.4", "@types/koa": "^2.13.4",
......
{ {
"name": "koishi-decorators", "name": "koishi-decorators",
"description": "Decorator defs for Koishi, especially for thirdeye and nestjs.", "description": "Decorator defs for Koishi, especially for thirdeye and nestjs.",
"version": "1.2.8", "version": "1.2.9",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"scripts": { "scripts": {
......
...@@ -60,11 +60,18 @@ export function UseCommand( ...@@ -60,11 +60,18 @@ export function UseCommand(
const putOptions: CommandPut.Config<keyof CommandPut.ConfigMap>[] = const putOptions: CommandPut.Config<keyof CommandPut.ConfigMap>[] =
Reflect.getMetadata(KoishiCommandPutDef, obj.constructor, key) || Reflect.getMetadata(KoishiCommandPutDef, obj.constructor, key) ||
undefined; undefined;
// eslint-disable-next-line @typescript-eslint/ban-types
const paramTypes: Function[] = Reflect.getMetadata(
'design:paramtypes',
obj,
key,
);
const metadataDec = DoRegister.command({ const metadataDec = DoRegister.command({
def, def,
desc, desc,
config, config,
putOptions, putOptions,
paramTypes,
}); });
return metadataDec(obj, key, des); return metadataDec(obj, key, des);
}; };
...@@ -177,7 +184,8 @@ export const PutArgv = (field?: keyof Argv) => ...@@ -177,7 +184,8 @@ export const PutArgv = (field?: keyof Argv) =>
field ? CommandPut.decorate('argvField', field) : CommandPut.decorate('argv'); field ? CommandPut.decorate('argvField', field) : CommandPut.decorate('argv');
export const PutSession = (field?: keyof Session) => export const PutSession = (field?: keyof Session) =>
field ? CommandPut.decorate('sessionField', field) : PutArgv('session'); field ? CommandPut.decorate('sessionField', field) : PutArgv('session');
export const PutArg = (i: number) => CommandPut.decorate('arg', i); export const PutArg = (index: number, decl?: Argv.Declaration) =>
CommandPut.decorate('arg', { index, decl });
export const PutArgs = () => CommandPut.decorate('args'); export const PutArgs = () => CommandPut.decorate('args');
export const PutOption = ( export const PutOption = (
name: string, name: string,
...@@ -213,6 +221,7 @@ export const PutTemplate = (name: string, text: string | Dict<string>) => ...@@ -213,6 +221,7 @@ export const PutTemplate = (name: string, text: string | Dict<string>) =>
name, name,
text: adaptLocaleDict(text), text: adaptLocaleDict(text),
}); });
export const PutObject = () => CommandPut.decorate('typeClass');
export const TopLevelAction = (action: TopLevelActionDef): ClassDecorator => export const TopLevelAction = (action: TopLevelActionDef): ClassDecorator =>
Metadata.append('KoishiTopLevelAction', action); Metadata.append('KoishiTopLevelAction', action);
......
// metadatas // metadatas
import { import {
CommandDefinitionFun, CommandDefinitionFun,
MappingStruct,
OnContextFunction, OnContextFunction,
TopLevelActionDef, TopLevelActionDef,
} from './interfaces'; } from './interfaces';
import { DoRegister } from '../registry'; import { CommandPut, DoRegister } from '../registry';
export const KoishiOnContextScope = 'KoishiOnContextScope'; export const KoishiOnContextScope = 'KoishiOnContextScope';
export const KoishiDoRegister = 'KoishiDoRegister'; export const KoishiDoRegister = 'KoishiDoRegister';
...@@ -19,8 +20,13 @@ export interface MetadataArrayMap { ...@@ -19,8 +20,13 @@ export interface MetadataArrayMap {
KoishiCommandDefinition: CommandDefinitionFun; KoishiCommandDefinition: CommandDefinitionFun;
KoishiDoRegisterKeys: string; KoishiDoRegisterKeys: string;
KoishiTopLevelAction: TopLevelActionDef; KoishiTopLevelAction: TopLevelActionDef;
KoishiPutClassFieldKeys: string;
} }
export interface MetadataMap { export interface MetadataMap {
KoishiDoRegister: DoRegister.Config; KoishiDoRegister: DoRegister.Config;
KoishiPutClassField: MappingStruct<
CommandPut.ConfigMap,
keyof CommandPut.ConfigMap
>;
} }
...@@ -103,6 +103,8 @@ export interface CommandRegisterConfig<D extends string = string> { ...@@ -103,6 +103,8 @@ export interface CommandRegisterConfig<D extends string = string> {
desc?: string; desc?: string;
config?: CommandConfigExtended; config?: CommandConfigExtended;
putOptions?: CommandPut.Config[]; putOptions?: CommandPut.Config[];
// eslint-disable-next-line @typescript-eslint/ban-types
paramTypes: Function[];
} }
export interface CommandConfigExtended extends Command.Config { export interface CommandConfigExtended extends Command.Config {
...@@ -157,3 +159,8 @@ export interface CommandLocaleDef extends Store { ...@@ -157,3 +159,8 @@ export interface CommandLocaleDef extends Store {
options?: Dict<string>; options?: Dict<string>;
messages?: Store; messages?: Store;
} }
export interface CommandArgDef {
index: number;
decl?: Argv.Declaration;
}
...@@ -32,6 +32,9 @@ export class MethodRegistry< ...@@ -32,6 +32,9 @@ export class MethodRegistry<
EXT extends any[] = [], EXT extends any[] = [],
> extends AbstractRegistry<MethodMap<M, R, EXT>> { > extends AbstractRegistry<MethodMap<M, R, EXT>> {
execute<K extends keyof M>(info: MappingStruct<M, K>, ...ext: EXT) { execute<K extends keyof M>(info: MappingStruct<M, K>, ...ext: EXT) {
if (!info) {
return;
}
const fun = this.get(info.type); const fun = this.get(info.type);
if (!fun) { if (!fun) {
return; return;
......
import { Argv, Command, Context, FieldCollector, Session, User } from 'koishi'; import { Argv, Command, Context, FieldCollector, Session, User } from 'koishi';
import { import {
CommandArgDef,
CommandOptionConfig, CommandOptionConfig,
GenerateMappingStruct, GenerateMappingStruct,
KoishiCommandPutDef, KoishiCommandPutDef,
...@@ -7,13 +8,19 @@ import { ...@@ -7,13 +8,19 @@ import {
TemplateConfig, TemplateConfig,
} from '../../def'; } from '../../def';
import { MethodRegistry } from '../abstract-registry'; import { MethodRegistry } from '../abstract-registry';
import { applyOptionToCommand, registerTemplate } from '../../utility'; import {
applyNativeTypeToArg,
applyOptionToCommand,
registerTemplate,
} from '../../utility';
import { Metadata } from '../../meta/metadata.decorators';
import { reflector } from '../../meta/meta-fetch';
// eslint-disable-next-line @typescript-eslint/no-namespace // eslint-disable-next-line @typescript-eslint/no-namespace
export namespace CommandPut { export namespace CommandPut {
export interface ConfigMap { export interface ConfigMap {
args: void; args: void;
arg: number; arg: CommandArgDef;
argv: void; argv: void;
argvField: keyof Argv; argvField: keyof Argv;
option: CommandOptionConfig; option: CommandOptionConfig;
...@@ -24,6 +31,7 @@ export namespace CommandPut { ...@@ -24,6 +31,7 @@ export namespace CommandPut {
sessionField: keyof Session; sessionField: keyof Session;
renderer: string | undefined; renderer: string | undefined;
template: TemplateConfig; template: TemplateConfig;
typeClass: void;
} }
export type Config<K extends keyof ConfigMap = keyof ConfigMap> = export type Config<K extends keyof ConfigMap = keyof ConfigMap> =
...@@ -32,13 +40,26 @@ export namespace CommandPut { ...@@ -32,13 +40,26 @@ export namespace CommandPut {
export const preRegistry = new MethodRegistry< export const preRegistry = new MethodRegistry<
ConfigMap, ConfigMap,
void, void,
[Command, Context] // eslint-disable-next-line @typescript-eslint/ban-types
[Command, Context, Function]
>(); >();
preRegistry.extend('option', (data, cmd, ctx) => preRegistry.extend('option', (data, cmd, ctx, nativeType) =>
applyOptionToCommand(ctx, cmd, data), applyOptionToCommand(ctx, cmd, data, nativeType),
); );
preRegistry.extend('arg', (data, cmd, ctx, nativeType) => {
let arg = cmd._arguments[data.index];
if (!arg) {
arg = {};
cmd._arguments[data.index] = arg;
}
applyNativeTypeToArg(arg, nativeType);
if (data.decl) {
Object.assign(arg, data.decl);
}
});
preRegistry.extend('user', (data, cmd) => { preRegistry.extend('user', (data, cmd) => {
if (data) { if (data) {
cmd.userFields(data); cmd.userFields(data);
...@@ -61,10 +82,29 @@ export namespace CommandPut { ...@@ -61,10 +82,29 @@ export namespace CommandPut {
registerTemplate(data, ctx, cmd), registerTemplate(data, ctx, cmd),
); );
export const registry = new MethodRegistry<ConfigMap, any, [Argv, any[]]>(); 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,
// eslint-disable-next-line @typescript-eslint/ban-types
[Argv, any[], Function]
>();
registry.extend('args', (data, argv, args) => args); registry.extend('args', (data, argv, args) => args);
registry.extend('arg', (data, argv, args) => args[data]); registry.extend('arg', (data, argv, args) => args[data.index]);
registry.extend('argv', (data, argv, args) => argv); registry.extend('argv', (data, argv, args) => argv);
registry.extend('argvField', (data, argv, args) => argv[data]); registry.extend('argvField', (data, argv, args) => argv[data]);
registry.extend('option', (data, argv, args) => argv.options[data.name]); registry.extend('option', (data, argv, args) => argv.options[data.name]);
...@@ -99,17 +139,47 @@ export namespace CommandPut { ...@@ -99,17 +139,47 @@ export namespace CommandPut {
(data, argv, args) => (params: object) => (data, argv, args) => (params: object) =>
argv.session.text(`.${data.name}`, params), 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>( export function decorate<T extends keyof ConfigMap>(
type: T, type: T,
data?: ConfigMap[T], data?: ConfigMap[T],
): ParameterDecorator { ): ParameterDecorator & PropertyDecorator {
return (obj, key: string, index) => { return (obj, key: string, index?: number) => {
const objClass = obj.constructor; const def = GenerateMappingStruct(type, data);
const list: Config<T>[] = if (typeof index === 'number') {
Reflect.getMetadata(KoishiCommandPutDef, objClass, key) || []; // As a parameter decorator
list[index] = GenerateMappingStruct(type, data); const objClass = obj.constructor;
Reflect.defineMetadata(KoishiCommandPutDef, list, objClass, key); 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;
}; };
} }
} }
...@@ -133,12 +133,17 @@ export namespace DoRegister { ...@@ -133,12 +133,17 @@ export namespace DoRegister {
obj[key](argv, ...args), obj[key](argv, ...args),
); );
} else { } else {
for (const putOption of data.putOptions) { for (let i = 0; i < data.putOptions.length; i++) {
CommandPut.preRegistry.execute(putOption, command, ctx); const putOption = data.putOptions[i];
if (!putOption) {
continue;
}
const nativeType = data.paramTypes[i];
CommandPut.preRegistry.execute(putOption, command, ctx, nativeType);
} }
command.action((argv: Argv, ...args: any[]) => { command.action((argv: Argv, ...args: any[]) => {
const params = data.putOptions.map((o) => const params = data.putOptions.map((o, i) =>
CommandPut.registry.execute(o, argv, args), CommandPut.registry.execute(o, argv, args, data.paramTypes[i]),
); );
return obj[key](...params); return obj[key](...params);
}); });
......
export * from './utility'; export * from './utility';
export * from './native-type-mapping';
import { Argv } from 'koishi';
// eslint-disable-next-line @typescript-eslint/ban-types
export const nativeTypeMapping = new Map<Function, Argv.Type>();
nativeTypeMapping.set(String, 'string');
nativeTypeMapping.set(Number, 'number');
nativeTypeMapping.set(Boolean, 'boolean');
nativeTypeMapping.set(Date, 'date');
export function applyNativeTypeToArg(
arg: Argv.Declaration,
// eslint-disable-next-line @typescript-eslint/ban-types
nativeType: Function,
) {
if (arg.type || !nativeType) {
return;
}
if (nativeTypeMapping.has(nativeType)) {
arg.type = nativeTypeMapping.get(nativeType);
}
}
...@@ -5,6 +5,7 @@ import { ...@@ -5,6 +5,7 @@ import {
OnContextFunction, OnContextFunction,
TemplateConfig, TemplateConfig,
} from '../def'; } from '../def';
import { applyNativeTypeToArg } from './native-type-mapping';
export function applySelector( export function applySelector(
ctx: Context, ctx: Context,
...@@ -59,8 +60,11 @@ export function applyOptionToCommand( ...@@ -59,8 +60,11 @@ export function applyOptionToCommand(
ctx: Context, ctx: Context,
cmd: Command, cmd: Command,
def: CommandOptionConfig, def: CommandOptionConfig,
// eslint-disable-next-line @typescript-eslint/ban-types
nativeType?: Function,
) { ) {
const { name, desc, config } = def; const { name, config } = def;
const { desc } = def;
if (config?.description) { if (config?.description) {
const desc = adaptLocaleDict(config.description); const desc = adaptLocaleDict(config.description);
for (const [locale, text] of Object.entries(desc)) { for (const [locale, text] of Object.entries(desc)) {
...@@ -69,5 +73,8 @@ export function applyOptionToCommand( ...@@ -69,5 +73,8 @@ export function applyOptionToCommand(
} }
const clonedConfig = { ...(config || {}) }; const clonedConfig = { ...(config || {}) };
delete clonedConfig.description; delete clonedConfig.description;
return cmd.option(name, desc, clonedConfig); cmd = cmd.option(name, desc, clonedConfig);
const option = cmd._options[name];
applyNativeTypeToArg(option, nativeType);
return cmd;
} }
import { import {
CommandUsage, CommandUsage,
PutArg,
PutObject,
PutOption, PutOption,
UseCommand, UseCommand,
UseEvent, UseEvent,
UseMiddleware, UseMiddleware,
} from '../src/decorators/decorators'; } from '../src/decorators';
import { App, Command, Next, Session } from 'koishi'; import { App, Command, Next, Session } from 'koishi';
import { Registrar } from '../src/register'; import { Registrar } from '../src/register';
import { EventNameAndPrepend } from '../src/def'; import { EventNameAndPrepend } from '../src/def';
class SkirtArg {
@PutArg(0)
count: number;
@PutOption('color', '-c <color>')
color: string;
}
class MyClass { class MyClass {
@UseMiddleware() @UseMiddleware()
async onPing(session: Session, next: Next) { async onPing(session: Session, next: Next) {
...@@ -27,9 +37,19 @@ class MyClass { ...@@ -27,9 +37,19 @@ class MyClass {
@UseCommand('echo', 'hi') @UseCommand('echo', 'hi')
@CommandUsage('foo') @CommandUsage('foo')
async onEcho(@PutOption('content', '-c <content:string>') content: string) { async onEcho(@PutOption('content', '-c <content>') content: string) {
return `bot: ${content}`; return `bot: ${content}`;
} }
@UseCommand('count')
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()); const registrar = new Registrar(new MyClass());
...@@ -54,14 +74,34 @@ describe('Register', () => { ...@@ -54,14 +74,34 @@ describe('Register', () => {
expect((result.data as EventNameAndPrepend).name).toBe('message'); expect((result.data as EventNameAndPrepend).name).toBe('message');
}); });
it('should register command', () => { it('should register command and infer option type', () => {
const result = registrar.register(app, 'onEcho'); const result = registrar.register(app, 'onEcho');
expect(result.type).toBe('command'); expect(result.type).toBe('command');
const command: Command = result.result; const command: Command = result.result;
expect(command._usage).toBe('foo'); expect(command._usage).toBe('foo');
expect(command._options.content.name).toBe('content'); expect(command._options.content.name).toBe('content');
expect(command._options.content.type).toBe('string');
expect(command.execute({ options: { content: 'hello' } })).resolves.toBe( expect(command.execute({ options: { content: 'hello' } })).resolves.toBe(
'bot: hello', 'bot: hello',
); );
}); });
it('should infer argument type', () => {
const result = registrar.register(app, 'onCount');
expect(result.type).toBe('command');
const command: Command = result.result;
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