Skip to content

Commit

Permalink
refa: standalone help and suggest plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed May 27, 2022
1 parent 2dceb95 commit 5c5de46
Show file tree
Hide file tree
Showing 10 changed files with 87 additions and 92 deletions.
6 changes: 0 additions & 6 deletions packages/core/src/command/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,6 @@ export class Commander {
return this._commands.get(name)
}

getCommandNames(session: Session) {
return this._commandList
.filter(cmd => cmd.match(session) && !cmd.config.hidden)
.flatMap(cmd => cmd._aliases)
}

command(def: string, ...args: [Command.Config?] | [string, Command.Config?]) {
const desc = typeof args[0] === 'string' ? args.shift() as string : ''
const config = args[0] as Command.Config
Expand Down
17 changes: 8 additions & 9 deletions packages/core/tests/command.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ describe('Command API', () => {
ctx1.command('b')
ctx2.command('c')

// a, b, c, help
expect(app.$commander._commandList).to.have.length(4)
// a, b, c
expect(app.$commander._commandList).to.have.length(3)
expect(app.$commander._commands.get('a').ctx).to.equal(app)
expect(app.$commander._commands.get('b').ctx).to.equal(ctx1)
expect(app.$commander._commands.get('c').ctx).to.equal(ctx2)
Expand Down Expand Up @@ -146,13 +146,12 @@ describe('Command API', () => {
test.alias('it').shortcut('2')

it('basic support', () => {
// don't forget help
expect(app.$commander._commandList).to.have.length(4)
expect(app.$commander._shortcuts).to.have.length(3)
expect(app.$commander._commandList).to.have.length(3)
expect(app.$commander._shortcuts).to.have.length(2)
expect(foo.children).to.have.length(1)
bar.dispose()
expect(app.$commander._commandList).to.have.length(2)
expect(app.$commander._shortcuts).to.have.length(1)
expect(app.$commander._commandList).to.have.length(1)
expect(app.$commander._shortcuts).to.have.length(0)
expect(foo.children).to.have.length(0)
})

Expand All @@ -165,14 +164,14 @@ describe('Command API', () => {
const foo = app.$commander._commands.get('foo')
expect(foo).to.be.ok
expect(app.$commander._commands.get('fooo')).to.be.ok
expect(Object.keys(foo._options)).to.have.length(2)
expect(Object.keys(foo._options)).to.have.length(1)
expect(app.$commander._commands.get('abc')).to.be.undefined
expect(app.$commander._commands.get('abcd')).to.be.undefined

dispose()
expect(app.$commander._commands.get('foo')).to.be.ok
expect(app.$commander._commands.get('fooo')).to.be.undefined
expect(Object.keys(foo._options)).to.have.length(1)
expect(Object.keys(foo._options)).to.have.length(0)
})
})

Expand Down
5 changes: 1 addition & 4 deletions packages/core/tests/runtime.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { App, User, Channel, Command, sleep } from 'koishi'
import mock, { DEFAULT_SELF_ID } from '@koishijs/plugin-mock'

const app = new App({
// @ts-ignore disable suggestions
minSimilarity: 0,
})
const app = new App()

app.plugin('database-memory')
app.plugin(mock)
Expand Down
2 changes: 0 additions & 2 deletions packages/koishi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@
"dependencies": {
"@koa/router": "^10.1.1",
"@koishijs/core": "^4.7.1",
"@koishijs/plugin-help": "^1.0.1",
"@koishijs/plugin-suggest": "^1.0.1",
"@koishijs/utils": "^5.4.5",
"@types/koa": "*",
"@types/koa__router": "*",
Expand Down
2 changes: 0 additions & 2 deletions packages/koishi/src/patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ declare module '@koishijs/core' {
export class Patch {
constructor(ctx: Context) {
ctx.app.baseDir ??= process.cwd()
ctx.plugin('suggest', ctx.app.options)
ctx.plugin('help', ctx.app.options['help'])
}
}

Expand Down
4 changes: 1 addition & 3 deletions plugins/a11y/commands/tests/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import mock from '@koishijs/plugin-mock'
import * as commands from '@koishijs/plugin-commands'
import { expect } from 'chai'

const app = new App({
minSimilarity: 0,
})
const app = new App()

app.plugin(mock)

Expand Down
90 changes: 52 additions & 38 deletions plugins/a11y/suggest/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import { distance } from 'fastest-levenshtein'
import { Awaitable } from 'cosmokit'
import { App, Context, Next, Schema, Session } from 'koishi'
import { Context, Next, Schema, Session } from 'koishi'

declare module 'koishi' {
namespace App {
namespace Config {
interface Basic extends SuggestConfig {}
}
interface Context {
$suggest: SuggestionService
}

interface Session {
suggest(options: SuggestOptions): Promise<void>
}
}

App.Config.Basic.dict.minSimilarity = Schema.percent().default(0.4).description('用于模糊匹配的相似系数,应该是一个 0 到 1 之间的数值。数值越高,模糊匹配越严格。设置为 1 可以完全禁用模糊匹配。')
Context.service('$suggest')

export interface SuggestOptions {
target: string
Expand All @@ -26,10 +24,6 @@ export interface SuggestOptions {
apply: (this: Session, suggestion: string, next: Next) => Awaitable<void | string>
}

export interface SuggestConfig {
minSimilarity?: number
}

Session.prototype.suggest = function suggest(this: Session, options) {
const {
target,
Expand All @@ -38,7 +32,7 @@ Session.prototype.suggest = function suggest(this: Session, options) {
suffix,
apply,
next = Next.compose,
minSimilarity = this.app.options.minSimilarity ?? 0.4,
minSimilarity = this.app.$suggest.config.minSimilarity,
} = options

const sendNext = async (callback: Next) => {
Expand Down Expand Up @@ -79,33 +73,53 @@ Session.prototype.suggest = function suggest(this: Session, options) {
})
}

export const name = 'suggest'

export function apply(ctx: Context) {
ctx.i18n.define('zh', require('./locales/zh'))
ctx.i18n.define('en', require('./locales/en'))
ctx.i18n.define('ja', require('./locales/ja'))
ctx.i18n.define('fr', require('./locales/fr'))
ctx.i18n.define('zh-tw', require('./locales/zh-tw'))

ctx.middleware((session, next) => {
// use `!prefix` instead of `prefix === null` to prevent from blocking other middlewares
// we need to make sure that the user truly has the intension to call a command
const { argv, quote, subtype, parsed: { content, prefix, appel } } = session
if (argv.command || subtype !== 'private' && !prefix && !appel) return next()
const target = content.split(/\s/, 1)[0].toLowerCase()
if (!target) return next()

return session.suggest({
target,
next,
items: ctx.$commander.getCommandNames(session),
prefix: session.text('suggest.command-prefix'),
suffix: session.text('suggest.command-suffix'),
async apply(suggestion, next) {
const newMessage = suggestion + content.slice(target.length) + (quote ? ' ' + quote.content : '')
return this.execute(newMessage, next)
},
class SuggestionService {
constructor(public ctx: Context, public config: SuggestionService.Config) {
ctx.$suggest = this

ctx.i18n.define('zh', require('./locales/zh'))
ctx.i18n.define('en', require('./locales/en'))
ctx.i18n.define('ja', require('./locales/ja'))
ctx.i18n.define('fr', require('./locales/fr'))
ctx.i18n.define('zh-tw', require('./locales/zh-tw'))

ctx.middleware((session, next) => {
// use `!prefix` instead of `prefix === null` to prevent from blocking other middlewares
// we need to make sure that the user truly has the intension to call a command
const { argv, quote, subtype, parsed: { content, prefix, appel } } = session
if (argv.command || subtype !== 'private' && !prefix && !appel) return next()
const target = content.split(/\s/, 1)[0].toLowerCase()
if (!target) return next()

return session.suggest({
target,
next,
items: this.getCommandNames(session),
prefix: session.text('suggest.command-prefix'),
suffix: session.text('suggest.command-suffix'),
async apply(suggestion, next) {
const newMessage = suggestion + content.slice(target.length) + (quote ? ' ' + quote.content : '')
return this.execute(newMessage, next)
},
})
})
}

getCommandNames(session: Session) {
return this.ctx.$commander._commandList
.filter(cmd => cmd.match(session) && !cmd.config.hidden)
.flatMap(cmd => cmd._aliases)
}
}

namespace SuggestionService {
export interface Config {
minSimilarity?: number
}

export const Config: Schema<Config> = Schema.object({
minSimilarity: Schema.percent().default(0.4).description('用于模糊匹配的相似系数,应该是一个 0 到 1 之间的数值。数值越高,模糊匹配越严格。设置为 1 可以完全禁用模糊匹配。'),
})
}

export default SuggestionService
6 changes: 5 additions & 1 deletion plugins/a11y/suggest/tests/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { App } from 'koishi'
import {} from '@koishijs/plugin-suggest'
import suggest from '@koishijs/plugin-suggest'
import mock from '@koishijs/plugin-mock'

describe('Command Suggestion', () => {
const app = new App({ prefix: '/' })
app.plugin(mock)
app.plugin(suggest)

const client1 = app.mock.client('456')
const client2 = app.mock.client('789', '987')

Expand Down Expand Up @@ -62,6 +64,8 @@ describe('Command Suggestion', () => {
describe('Other Session Methods', () => {
const app = new App({ prefix: '.' })
app.plugin(mock)
app.plugin(suggest)

const client = app.mock.client('123', '456')
const items = ['foo', 'bar']

Expand Down
24 changes: 11 additions & 13 deletions plugins/common/help/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import { Argv, Channel, Command, Context, FieldCollector, Session, Tables, User } from 'koishi'
import { Argv, Channel, Command, Context, FieldCollector, Schema, Session, Tables, User } from 'koishi'
import {} from '@koishijs/plugin-suggest'

declare module 'koishi' {
namespace App {
namespace Config {
interface Features {
help?: false | HelpConfig
}
}
}

interface Events {
'help/command'(output: string[], command: Command, session: Session): void
'help/option'(output: string, option: Argv.OptionDeclaration, command: Command, session: Session): string
Expand All @@ -21,11 +13,16 @@ interface HelpOptions {
authority?: boolean
}

export interface HelpConfig extends Command.Config {
export interface Config extends Command.Config {
shortcut?: boolean
options?: boolean
}

export const Config: Schema<Config> = Schema.object({
shortcut: Schema.boolean().default(true).description('是否启用快捷调用。'),
options: Schema.boolean().default(true).description('是否为每个指令添加 `-h, --help` 选项。'),
})

export function enableHelp<U extends User.Field, G extends Channel.Field, A extends any[], O extends {}>(cmd: Command<U, G, A, O>) {
return cmd.option('help', '-h', {
hidden: true,
Expand All @@ -35,14 +32,15 @@ export function enableHelp<U extends User.Field, G extends Channel.Field, A exte

export const name = 'help'

export function apply(ctx: Context, config: HelpConfig = {}) {
export function apply(ctx: Context, config: Config = {}) {
ctx.i18n.define('zh', require('./locales/zh'))
ctx.i18n.define('en', require('./locales/en'))
ctx.i18n.define('ja', require('./locales/ja'))
ctx.i18n.define('fr', require('./locales/fr'))
ctx.i18n.define('zh-tw', require('./locales/zh-tw'))

if (config.options !== false) {
ctx.$commander._commands.forEach(cmd => cmd.use(enableHelp))
ctx.on('command-added', cmd => cmd.use(enableHelp))
}

Expand Down Expand Up @@ -80,12 +78,12 @@ export function apply(ctx: Context, config: HelpConfig = {}) {

const command = findCommand(target)
if (!command?.ctx.match(session)) {
if (!session.suggest) {
if (!ctx.$suggest) {
return session.text('.suggest-prefix')
}
return session.suggest({
target,
items: $.getCommandNames(session),
items: ctx.$suggest.getCommandNames(session),
prefix: session.text('.suggest-prefix'),
suffix: session.text('.suggest-suffix'),
async apply(suggestion) {
Expand Down
23 changes: 9 additions & 14 deletions plugins/common/help/tests/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { App } from 'koishi'
import {} from '@koishijs/plugin-help'
import * as help from '@koishijs/plugin-help'
import suggest from '@koishijs/plugin-suggest'
import mock from '@koishijs/plugin-mock'

const app = new App()

app.plugin(mock)
app.plugin(help)
app.plugin(suggest)
app.plugin('database-memory')

app.i18n.define('$zh', 'commands.help.messages.global-epilog', 'EPILOG')
Expand Down Expand Up @@ -128,6 +131,7 @@ describe('Help Command', () => {

it('no database', async () => {
const app = new App()
app.plugin(help)
app.plugin(mock)
app.i18n.define('$zh', 'commands.help.messages.global-epilog', '')
await app.start()
Expand All @@ -136,19 +140,9 @@ describe('Help Command', () => {
await client.shouldReply('help', '当前可用的指令有:\n help 显示帮助信息')
})

it('disable help command', async () => {
const app = new App({ help: false })
app.plugin(mock)
app.command('foo').action(() => {})
await app.start()

const client = app.mock.client('123')
await client.shouldNotReply('help')
await client.shouldNotReply('foo -h')
})

it('disable help options', async () => {
const app = new App({ help: { options: false } })
const app = new App()
app.plugin(help, { options: false })
app.plugin(mock)
app.command('foo').action(() => {})
await app.start()
Expand All @@ -159,7 +153,8 @@ describe('Help Command', () => {
})

it('disable help shortcut', async () => {
const app = new App({ help: { shortcut: false } })
const app = new App()
app.plugin(help, { shortcut: false })
app.plugin(mock)
await app.start()

Expand Down

0 comments on commit 5c5de46

Please sign in to comment.