Skip to content

Commit

Permalink
feat(core): support command alias with args
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Oct 14, 2023
1 parent 9d831f6 commit 5188f64
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 69 deletions.
39 changes: 24 additions & 15 deletions packages/core/src/command/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ export type Extend<O extends {}, K extends string, T> = {
}

export namespace Command {
export interface Alias {
options?: Dict
args?: string[]
}

export interface Shortcut {
i18n?: boolean
name?: string | RegExp
Expand All @@ -36,7 +41,7 @@ export class Command<U extends User.Field = never, G extends Channel.Field = nev
children: Command[] = []

_parent: Command = null
_aliases: string[] = []
_aliases: Dict<Command.Alias> = {}
_examples: string[] = []
_usage?: Command.Usage

Expand Down Expand Up @@ -82,7 +87,7 @@ export class Command<U extends User.Field = never, G extends Channel.Field = nev
}

get displayName() {
return this._aliases[0]
return Object.keys(this._aliases)[0]
}

set displayName(name) {
Expand All @@ -108,22 +113,21 @@ export class Command<U extends User.Field = never, G extends Channel.Field = nev
}
}

private _registerAlias(name: string, prepend = false) {
private _registerAlias(name: string, prepend = false, options: Command.Alias = {}) {
name = name.toLowerCase()
if (name.startsWith('.')) name = this.parent.name + name

// add to list
const done = this._aliases.includes(name)
if (done) {
const existing = this._aliases[name]
if (existing) {
if (prepend) {
remove(this._aliases, name)
this._aliases.unshift(name)
this._aliases = { [name]: existing, ...this._aliases }
}
return
} else if (prepend) {
this._aliases.unshift(name)
this._aliases = { [name]: options, ...this._aliases }
} else {
this._aliases.push(name)
this._aliases[name] = options
}

// register global
Expand All @@ -149,9 +153,15 @@ export class Command<U extends User.Field = never, G extends Channel.Field = nev
return this as any
}

alias(...names: string[]) {
for (const name of names) {
this._registerAlias(name)
alias(...names: string[]): this
alias(name: string, options: Command.Alias): this
alias(...args: any[]) {
if (typeof args[1] === 'object') {
this._registerAlias(args[0], false, args[1])
} else {
for (const name of args) {
this._registerAlias(name)
}
}
return this
}
Expand Down Expand Up @@ -334,7 +344,7 @@ export class Command<U extends User.Field = never, G extends Channel.Field = nev
for (const cmd of this.children.slice()) {
cmd.dispose()
}
for (const name of this._aliases) {
for (const name in this._aliases) {
this.ctx.$commander.delete(name)
}
remove(this.ctx.$commander._commandList, this)
Expand All @@ -344,12 +354,11 @@ export class Command<U extends User.Field = never, G extends Channel.Field = nev
toJSON(): Universal.Command {
return {
name: this.name,
aliases: this._aliases,
description: this.ctx.i18n.get(`commands.${this.name}.description`),
arguments: this._arguments.map(arg => ({
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]) => ({
Expand Down
53 changes: 42 additions & 11 deletions packages/core/src/command/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export class Commander extends Map<string, Command> {

_commandList: Command[] = []
_commands = this
_shortcuts: Command.Shortcut[] = []

constructor(private ctx: Context, private config: Commander.Config = {}) {
super()
Expand Down Expand Up @@ -88,7 +87,7 @@ export class Commander extends Map<string, Command> {

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)
})

Expand Down Expand Up @@ -149,26 +148,58 @@ export class Commander extends Map<string, Command> {
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 {
return this[Context.current]
}

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?]) {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
},

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/locales/zh-CN.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal:
$: 核心文本
error-encountered: 发生未知错误。
low-authority: 权限不足。
prompt-argument: 请输入参数 {0}
prompt-argument: 请发送{0}
insufficient-arguments: 缺少参数,输入帮助以查看用法。
redunant-arguments: 存在多余参数,输入帮助以查看用法。
invalid-argument: 参数 {0} 输入无效,{1}
Expand Down
37 changes: 2 additions & 35 deletions packages/core/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ declare module '@satorijs/core' {
i18n(path: string | string[], params?: object): h[]
text(path: string | string[], params?: object): string
collect<T extends 'user' | 'channel'>(key: T, argv: Argv, fields?: Set<keyof Tables[T]>): Set<keyof Tables[T]>
inferCommand(argv: Argv): Command
resolveCommand(argv: Argv): Command
execute(content: string, next?: true | Next): Promise<string>
execute(argv: Argv, next?: true | Next): Promise<string>
middleware(middleware: Middleware): () => boolean
Expand Down Expand Up @@ -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)
Expand All @@ -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)

Expand All @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/tests/runtime.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions plugins/common/help/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion plugins/common/help/tests/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <arg>')
})
Expand Down

0 comments on commit 5188f64

Please sign in to comment.