From 5188f647099d24244cecf1888c2aa1146afc5f03 Mon Sep 17 00:00:00 2001 From: Shigma Date: Sat, 14 Oct 2023 14:53:45 +0800 Subject: [PATCH] feat(core): support command alias with args --- packages/core/src/command/command.ts | 39 +++++++++++------- packages/core/src/command/index.ts | 53 ++++++++++++++++++++----- packages/core/src/lifecycle.ts | 6 +-- packages/core/src/locales/zh-CN.yml | 2 +- packages/core/src/session.ts | 37 +---------------- packages/core/tests/runtime.spec.ts | 2 +- plugins/common/help/src/index.ts | 4 +- plugins/common/help/tests/index.spec.ts | 2 +- 8 files changed, 76 insertions(+), 69 deletions(-) diff --git a/packages/core/src/command/command.ts b/packages/core/src/command/command.ts index 4b4a94b046..0f6843c855 100644 --- a/packages/core/src/command/command.ts +++ b/packages/core/src/command/command.ts @@ -14,6 +14,11 @@ export type Extend = { } export namespace Command { + export interface Alias { + options?: Dict + args?: string[] + } + export interface Shortcut { i18n?: boolean name?: string | RegExp @@ -36,7 +41,7 @@ export class Command = {} _examples: string[] = [] _usage?: Command.Usage @@ -82,7 +87,7 @@ export class Command ({ name: arg.name, type: toStringType(arg.type), - description: { '': toStringType(arg.type) }, + description: this.ctx.i18n.get(`commands.${this.name}.arguments.${arg.name}`), required: arg.required, })), options: Object.entries(this._options).map(([name, option]) => ({ diff --git a/packages/core/src/command/index.ts b/packages/core/src/command/index.ts index 4eefd6f494..1ef6aebdc7 100644 --- a/packages/core/src/command/index.ts +++ b/packages/core/src/command/index.ts @@ -40,7 +40,6 @@ export class Commander extends Map { _commandList: Command[] = [] _commands = this - _shortcuts: Command.Shortcut[] = [] constructor(private ctx: Context, private config: Commander.Config = {}) { super() @@ -88,7 +87,7 @@ export class Commander extends Map { ctx.middleware((session, next) => { // execute command - if (!session.resolveCommand(session.argv)) return next() + if (!this.resolveCommand(session.argv)) return next() return session.execute(session.argv, next) }) @@ -149,7 +148,7 @@ export class Commander extends Map { available(session: Session) { return this._commandList .filter(cmd => cmd.match(session)) - .flatMap(cmd => cmd._aliases) + .flatMap(cmd => Object.keys(cmd._aliases)) } protected get caller(): Context { @@ -157,18 +156,50 @@ export class Commander extends Map { } resolve(key: string) { - if (!key) return + return this._resolve(key).command + } + + _resolve(key: string) { + if (!key) return {} const segments = key.toLowerCase().split('.') - let i = 1, name = segments[0], cmd: Command - while ((cmd = this.get(name)) && i < segments.length) { - name = cmd.name + '.' + segments[i++] + let i = 1, name = segments[0], command: Command + while ((command = this.get(name)) && i < segments.length) { + name = command.name + '.' + segments[i++] } - return cmd + return { command, name } } - /** @deprecated use `.get()` instead */ - getCommand(name: string) { - return this.get(name) + inferCommand(argv: Argv) { + if (argv.command) return argv.command + if (argv.name) return argv.command = this.resolve(argv.name) + + const { stripped, isDirect } = argv.session + // guild message should have prefix or appel to be interpreted as a command call + if (argv.root && !isDirect && stripped.prefix === null && !stripped.appel) return + const segments: string[] = [] + while (argv.tokens.length) { + const { content } = argv.tokens[0] + segments.push(content) + const { name, command } = this._resolve(segments.join('.')) + if (!command) break + argv.tokens.shift() + argv.command = command + argv.args = command._aliases[name].args + argv.options = command._aliases[name].options + if (command._arguments.length) break + } + return argv.command + } + + resolveCommand(argv: Argv) { + if (!this.inferCommand(argv)) return + if (argv.tokens?.every(token => !token.inters.length)) { + const { options, args, error } = argv.command.parse(argv) + argv.options = { ...argv.options, ...options } + argv.args = [...argv.args || [], ...args] + argv.error = error + } + return argv.command } command(def: string, ...args: [Command.Config?] | [string, Command.Config?]) { diff --git a/packages/core/src/lifecycle.ts b/packages/core/src/lifecycle.ts index dff13a7a63..8a8879ab6f 100644 --- a/packages/core/src/lifecycle.ts +++ b/packages/core/src/lifecycle.ts @@ -71,10 +71,10 @@ extend(Context.prototype as Context.Private, { createTimerDispose(timer) { const dispose = () => { clearTimeout(timer) - if (!this.state) return - return remove(this.state.disposables, dispose) + if (!this.scope) return + return remove(this.scope.disposables, dispose) } - this.state.disposables.push(dispose) + this.scope.disposables.push(dispose) return dispose }, diff --git a/packages/core/src/locales/zh-CN.yml b/packages/core/src/locales/zh-CN.yml index 77b05df895..3cf0e587eb 100644 --- a/packages/core/src/locales/zh-CN.yml +++ b/packages/core/src/locales/zh-CN.yml @@ -15,7 +15,7 @@ internal: $: 核心文本 error-encountered: 发生未知错误。 low-authority: 权限不足。 - prompt-argument: 请输入参数 {0}: + prompt-argument: 请发送{0}。 insufficient-arguments: 缺少参数,输入帮助以查看用法。 redunant-arguments: 存在多余参数,输入帮助以查看用法。 invalid-argument: 参数 {0} 输入无效,{1} diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 7462455af0..10ca158c47 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -37,8 +37,6 @@ declare module '@satorijs/core' { i18n(path: string | string[], params?: object): h[] text(path: string | string[], params?: object): string collect(key: T, argv: Argv, fields?: Set): Set - inferCommand(argv: Argv): Command - resolveCommand(argv: Argv): Command execute(content: string, next?: true | Next): Promise execute(argv: Argv, next?: true | Next): Promise middleware(middleware: Middleware): () => boolean @@ -372,7 +370,7 @@ extend(Session.prototype as Session.Private, { inters.forEach(collect) } } - if (!this.resolveCommand(argv)) return + if (!this.app.$commander.resolveCommand(argv)) return this.app.emit(argv.session, `command/before-attach-${key}` as any, argv, fields) collectFields(argv, Command[`_${key}Fields`] as any, fields) collectFields(argv, argv.command[`_${key}Fields`] as any, fields) @@ -381,37 +379,6 @@ extend(Session.prototype as Session.Private, { return fields }, - inferCommand(argv) { - if (argv.command) return argv.command - if (argv.name) return argv.command = this.app.$commander.resolve(argv.name) - - const { stripped, isDirect } = this - // guild message should have prefix or appel to be interpreted as a command call - if (argv.root && !isDirect && stripped.prefix === null && !stripped.appel) return - const segments: string[] = [] - while (argv.tokens.length) { - const { content } = argv.tokens[0] - segments.push(content) - const command = this.app.$commander.resolve(segments.join('.')) - if (!command) break - argv.tokens.shift() - argv.command = command - if (command._arguments.length) break - } - return argv.command - }, - - resolveCommand(argv) { - if (!this.inferCommand(argv)) return - if (argv.tokens?.every(token => !token.inters.length)) { - const { options, args, error } = argv.command.parse(argv) - argv.options = { ...argv.options, ...options } - argv.args = [...argv.args || [], ...args] - argv.error = error - } - return argv.command - }, - async execute(argv, next) { if (typeof argv === 'string') argv = Argv.parse(argv) @@ -431,7 +398,7 @@ extend(Session.prototype as Session.Private, { } arg.inters = [] } - if (!this.resolveCommand(argv)) return '' + if (!this.app.$commander.resolveCommand(argv)) return '' } else { argv.command ||= this.app.$commander.get(argv.name) if (!argv.command) { diff --git a/packages/core/tests/runtime.spec.ts b/packages/core/tests/runtime.spec.ts index 3210794ce3..eae19b0b9d 100644 --- a/packages/core/tests/runtime.spec.ts +++ b/packages/core/tests/runtime.spec.ts @@ -215,7 +215,7 @@ describe('Runtime', () => { cmd1.config.checkArgCount = true cmd1.config.showWarning = true await client4.shouldReply('cmd1 foo', 'cmd1:foo') - await client4.shouldReply('cmd1', '请输入参数 arg1:') + await client4.shouldReply('cmd1', '请发送arg1。') await client4.shouldReply('bar baz', 'cmd1:bar baz') await client4.shouldReply('cmd1 foo bar', '存在多余参数,输入帮助以查看用法。') cmd1.config.showWarning = false diff --git a/plugins/common/help/src/index.ts b/plugins/common/help/src/index.ts index 0a0e905f24..6272af9e92 100644 --- a/plugins/common/help/src/index.ts +++ b/plugins/common/help/src/index.ts @@ -258,8 +258,8 @@ async function showHelp(command: Command, session: Session<'authority'>, config: } } - if (command._aliases.length > 1) { - output.push(session.text('.command-aliases', [Array.from(command._aliases.slice(1)).join(',')])) + if (Object.keys(command._aliases).length > 1) { + output.push(session.text('.command-aliases', [Array.from(Object.keys(command._aliases).slice(1)).join(',')])) } session.app.emit(session, 'help/command', output, command, session) diff --git a/plugins/common/help/tests/index.spec.ts b/plugins/common/help/tests/index.spec.ts index 8142bf16a7..c3cbf2299b 100644 --- a/plugins/common/help/tests/index.spec.ts +++ b/plugins/common/help/tests/index.spec.ts @@ -156,7 +156,7 @@ describe('@koishijs/plugin-help', () => { await app.start() const client = app.mock.client('123') - await client.shouldReply('test', '请输入参数 arg:') + await client.shouldReply('test', '请发送arg。') await client.shouldReply('foo', 'pass') await client.shouldReply('test -h', '指令:test ') })