diff --git a/packages/plugin/src/constants.ts b/packages/plugin/src/constants.ts index affc3a1e..58b02b1c 100644 --- a/packages/plugin/src/constants.ts +++ b/packages/plugin/src/constants.ts @@ -1,3 +1,6 @@ export const METADATA_KEY = { plugin: "@ms/plugin:plugin", + cmd: "@ms/plugin:cmd", + tab: "@ms/plugin:tab", + listener: "@ms/plugin:listener", }; diff --git a/packages/plugin/src/decorators.ts b/packages/plugin/src/decorators.ts index 58467b2f..f8c4c812 100644 --- a/packages/plugin/src/decorators.ts +++ b/packages/plugin/src/decorators.ts @@ -1,7 +1,7 @@ import { interfaces } from './interfaces' import { METADATA_KEY } from './constants' import { injectable, decorate } from "inversify"; -import { getPluginMetadatas } from './utils' +import { getPluginMetadatas, getPluginCommandMetadata, getPluginListenerMetadata } from './utils' /** * MiaoScript plugin @@ -12,8 +12,52 @@ export function plugin(metadata: interfaces.PluginMetadata) { target.description = metadata; metadata.target = target; decorate(injectable(), target); - Reflect.defineMetadata(METADATA_KEY.plugin, metadata, target); - const previousMetadata: interfaces.PluginMetadata[] = getPluginMetadatas(); - Reflect.defineMetadata(METADATA_KEY.plugin, [metadata, ...previousMetadata], Reflect); + Reflect.defineMetadata(METADATA_KEY.plugin, metadata, target.constructor); + const previousMetadata: Map = getPluginMetadatas(); + previousMetadata.set(metadata.name, metadata); + Reflect.defineMetadata(METADATA_KEY.plugin, previousMetadata, Reflect); }; } + +/** + * MiaoScript Command + * @param metadata CommandMetadata + */ +export function cmd(metadata: interfaces.CommandMetadata = {}) { + return function(target: any, key: string, value: any) { + metadata.name = metadata.name || key; + metadata.executor = key; + metadata.paramtypes = Reflect.getMetadata("design:paramtypes", target, key) + const previousMetadata: Map = getPluginCommandMetadata(target) + previousMetadata.set(metadata.name, metadata); + Reflect.defineMetadata(METADATA_KEY.cmd, previousMetadata, target.constructor); + }; +} + +/** + * MiaoScript Command + * @param metadata CommandMetadata + */ +export function tab(metadata: interfaces.TabCompleterMetadata = {}) { + return function(target: any, key: string, value: any) { + metadata.name = metadata.name || key; + metadata.executor = key; + metadata.paramtypes = Reflect.getMetadata("design:paramtypes", target, key) + const previousMetadata: Map = getPluginCommandMetadata(target) + previousMetadata.set(metadata.name, metadata) + Reflect.defineMetadata(METADATA_KEY.tab, previousMetadata, target.constructor); + }; +} + +/** + * MiaoScript Listener + * @param metadata ListenerMetadata + */ +export function listener(metadata: interfaces.ListenerMetadata = {}) { + return function(target: any, key: string, value: any) { + metadata.name = metadata.name || key; + metadata.executor = key; + const previousMetadata: interfaces.ListenerMetadata[] = getPluginListenerMetadata(target) + Reflect.defineMetadata(METADATA_KEY.listener, [metadata, ...previousMetadata], target.constructor); + }; +} \ No newline at end of file diff --git a/packages/plugin/src/interfaces.ts b/packages/plugin/src/interfaces.ts index 72e1fa13..cef81e3d 100644 --- a/packages/plugin/src/interfaces.ts +++ b/packages/plugin/src/interfaces.ts @@ -1,5 +1,4 @@ import { injectable, postConstruct } from "inversify"; -import { getPluginMetadata } from './utils' export namespace interfaces { @injectable() @@ -15,6 +14,22 @@ export namespace interfaces { name: string; version: string; author: string | string[]; + source: string; target?: any; } + export interface CommandMetadata { + name?: string; + executor?: string; + paramtypes?: string[]; + } + export interface TabCompleterMetadata { + name?: string; + executor?: string; + paramtypes?: string[]; + } + export interface ListenerMetadata { + name?: string; + executor?: string; + } + export type PluginLike = Plugin | string; } diff --git a/packages/plugin/src/manager.ts b/packages/plugin/src/manager.ts index 09092ad5..f1900a9f 100644 --- a/packages/plugin/src/manager.ts +++ b/packages/plugin/src/manager.ts @@ -1,8 +1,8 @@ -import { plugin, server, MiaoScriptConsole } from '@ms/api' -import { injectable, inject, postConstruct, Container } from '@ms/container' +import { plugin, server, command, event, MiaoScriptConsole } from '@ms/api' +import { injectable, inject, postConstruct, Container, DefaultContainer as container } from '@ms/container' import * as fs from '@ms/common/dist/fs' -import { getPluginMetadatas } from './utils' +import { getPluginMetadatas, getPluginCommandMetadata, getPluginListenerMetadata, getPlugin, getPluginTabCompleterMetadata } from './utils' import { interfaces } from './interfaces'; @injectable() @@ -13,6 +13,10 @@ export class PluginManagerImpl implements plugin.PluginManager { private serverType: string; @inject(server.Console) private Console: MiaoScriptConsole; + @inject(command.Command) + private CommandManager: command.Command; + @inject(event.Event) + private EventManager: event.Event; private pluginMap: Map; @@ -22,6 +26,7 @@ export class PluginManagerImpl implements plugin.PluginManager { // 如果plugin不等于null 则代表是正式环境 console.info(`Initialization MiaoScript Plugin System: ${this.pluginInstance} ...`); this.pluginMap = new Map(); + console.info(`${this.EventManager.mapEventName().toFixed(0)} ${this.serverType} Event Mapping Complate...`); } } @@ -35,17 +40,35 @@ export class PluginManagerImpl implements plugin.PluginManager { this.loadPlugins(files); } - load(container: Container): void { + build(container: Container): void { this.buildPlugins(container); } - enable(): void { - this.pluginMap.forEach(pl => this.runCatch(pl, 'load')); - this.pluginMap.forEach(pl => this.runCatch(pl, 'enable')); + load(...args: any[]): void { + this.checkAndGet(args[0]).forEach(pl => this.runCatch(pl, 'load')); } - disable(): void { - throw new Error("Method not implemented."); + enable(...args: any[]): void { + this.checkAndGet(args[0]).forEach(pl => this.runCatch(pl, 'enable')); + } + + disable(...args: any[]): void { + this.checkAndGet(args[0]).forEach(pl => { + this.runCatch(pl, 'disable'); + this.EventManager.disable(pl); + }); + + } + + reload(...args: any[]): void { + this.checkAndGet(arguments[0]).forEach((pl: interfaces.Plugin) => { + this.disable(pl); + this.EventManager.disable(pl); + this.loadPlugin(pl.description.source); + pl = this.buildPlugin(getPlugin(pl.description.name)); + this.load(pl); + this.enable(pl); + }) } private runCatch(pl: any, func: string) { @@ -57,13 +80,22 @@ export class PluginManagerImpl implements plugin.PluginManager { } } + private checkAndGet(name: string | interfaces.Plugin | undefined): Map | interfaces.Plugin[] { + if (name == undefined) { + return this.pluginMap; + } + if (!(name instanceof interfaces.Plugin) && !this.pluginMap.has(name)) { + throw new Error(`Plugin ${name} not exist!`); + } + // 如果是插件 则直接返回 + return [name as interfaces.Plugin]; + } + private scanFloder(plugin: any): string[] { var files = []; - console.info(`Start Scan Plugins in ${plugin} ...`); + console.info(`Scanning Plugins in ${plugin} ...`); this.checkUpdateFolder(plugin); - fs.list(plugin).forEach(function searchPlugin(file) { - files.push(file.toFile()); - }); + fs.list(plugin).forEach((file: any) => files.push(file.toFile())); return files; } @@ -86,19 +118,17 @@ export class PluginManagerImpl implements plugin.PluginManager { * JS类型插件预加载 */ private loadJsPlugins(files: any[]) { - files.filter(file => file.name.endsWith(".js")).forEach(file => { - try { - this.loadPlugin(file) - } catch (ex) { - console.console(`§6插件 §b${file.name} §6初始化时发生错误 §4${ex.message}`); - console.ex(ex); - } - }) + files.filter(file => file.name.endsWith(".js")).forEach(file => this.loadPlugin(file)) } private loadPlugin(file: any) { - this.updatePlugin(file); - this.createPlugin(file); + try { + this.updatePlugin(file); + this.createPlugin(file); + } catch (ex) { + console.console(`§6插件 §b${file.name} §6初始化时发生错误 §4${ex.message}`); + console.ex(ex); + } } private updatePlugin(file: any) { @@ -127,13 +157,48 @@ export class PluginManagerImpl implements plugin.PluginManager { private buildPlugins(container: Container) { let pluginMetadatas = getPluginMetadatas(); - for (const metadata of pluginMetadatas) { + pluginMetadatas.forEach(metadata => { + this.buildPlugin(metadata); + }); + } + + private buildPlugin(metadata: interfaces.PluginMetadata) { + try { + let pluginInstance = container.getNamed(plugin.Plugin, metadata.name) + if (pluginInstance.description.source + '' !== metadata.source + '') { + console.warn(`find duplicate plugin ${pluginInstance.description.source} and ${metadata.source}. the first plugin will be ignore!`) + } + container.rebind(plugin.Plugin).to(metadata.target).inSingletonScope().whenTargetNamed(metadata.name); + } catch{ container.bind(plugin.Plugin).to(metadata.target).inSingletonScope().whenTargetNamed(metadata.name); - this.pluginMap.set(metadata.name, container.getNamed(plugin.Plugin, metadata.name)); - let pluginInstance = this.pluginMap.get(metadata.name) - pluginInstance.description = metadata; - // @ts-ignore - pluginInstance.logger = new this.Console(metadata.name); + } + let pluginInstance = container.getNamed(plugin.Plugin, metadata.name) + this.pluginMap.set(metadata.name, pluginInstance); + pluginInstance.description = metadata; + // @ts-ignore + pluginInstance.logger = new this.Console(metadata.name); + this.registryCommand(pluginInstance); + this.registryListener(pluginInstance); + return pluginInstance; + } + + private registryCommand(pluginInstance: interfaces.Plugin) { + let cmds = getPluginCommandMetadata(pluginInstance); + let tabs = getPluginTabCompleterMetadata(pluginInstance); + cmds.forEach(cmd => { + let tab = tabs.get(cmd.name); + this.CommandManager.on(pluginInstance, cmd.name, { + cmd: pluginInstance[cmd.executor].bind(pluginInstance), + tab: tab ? pluginInstance[tab.executor].bind(pluginInstance) : undefined + }); + }) + } + + private registryListener(pluginInstance: interfaces.Plugin) { + let events = getPluginListenerMetadata(pluginInstance) + for (const event of events) { + // here must bind this to pluginInstance + this.EventManager.listen(pluginInstance, event.name, pluginInstance[event.executor].bind(pluginInstance)); } } } diff --git a/packages/plugin/src/utils.ts b/packages/plugin/src/utils.ts index dfa9d6f3..5d31e93f 100644 --- a/packages/plugin/src/utils.ts +++ b/packages/plugin/src/utils.ts @@ -2,27 +2,59 @@ import { interfaces } from './interfaces' import { METADATA_KEY } from './constants' function getPlugins() { - return getPluginMetadatas().map((target) => target.target); + return [...getPluginMetadatas().values()].map((target) => target.target); +} + +function getPlugin(name: string) { + return getPluginMetadatas().get(name); } function getPluginMetadatas() { - let pluginMetadatas: interfaces.PluginMetadata[] = Reflect.getMetadata( + let pluginMetadatas: Map = Reflect.getMetadata( METADATA_KEY.plugin, Reflect - ) || []; + ) || new Map(); return pluginMetadatas; } function getPluginMetadata(target: any) { let pluginMetadata: interfaces.PluginMetadata = Reflect.getMetadata( METADATA_KEY.plugin, - target + target.constructor ) || {}; return pluginMetadata; } +function getPluginCommandMetadata(target: any) { + let commandMetadata: Map = Reflect.getMetadata( + METADATA_KEY.cmd, + target.constructor + ) || new Map(); + return commandMetadata; +} + +function getPluginTabCompleterMetadata(target: any) { + let tabcompleterMetadata: Map = Reflect.getMetadata( + METADATA_KEY.tab, + target.constructor + ) || new Map(); + return tabcompleterMetadata; +} + +function getPluginListenerMetadata(target: any) { + let listnerMetadata: interfaces.ListenerMetadata[] = Reflect.getMetadata( + METADATA_KEY.listener, + target.constructor + ) || []; + return listnerMetadata; +} + export { + getPlugin, getPlugins, getPluginMetadatas, - getPluginMetadata + getPluginMetadata, + getPluginCommandMetadata, + getPluginTabCompleterMetadata, + getPluginListenerMetadata, }