Skip to content

Commit

Permalink
feat(core): support command patch
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Mar 20, 2021
1 parent a1f11d6 commit 8c2ce5a
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 36 deletions.
51 changes: 28 additions & 23 deletions .mocharc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,48 @@ const { readdirSync } = require('fs')
process.env.TS_NODE_PROJECT = 'tsconfig.test.json'

const prefixes = ['koishi-', 'adapter-', 'plugin-', '']
const packageMap = {}
const libraries = {}

for (const name of readdirSync(__dirname + '/packages')) {
for (const prefix of prefixes) {
if (name.startsWith(prefix)) {
packageMap[name.slice(prefix.length)] = name
libraries[name.slice(prefix.length)] = name
break
}
}
}

const specs = [
// 'packages/koishi-core/tests/*.spec.ts',
'packages/koishi-core/tests/command.spec.ts',
'packages/koishi-core/tests/context.spec.ts',
'packages/koishi-core/tests/help.spec.ts',
'packages/koishi-core/tests/hook.spec.ts',
'packages/koishi-core/tests/session.spec.ts',
'packages/koishi-core/tests/parser.spec.ts',
'packages/koishi-utils/tests/*.spec.ts',
'packages/koishi-test-utils/tests/*.spec.ts',
'packages/plugin-common/tests/*.spec.ts',
'packages/plugin-eval/tests/*.spec.ts',
'packages/plugin-github/tests/*.spec.ts',
'packages/plugin-teach/tests/*.spec.ts',
]

function getSpecFromArgv() {
if (!process.env.npm_config_argv) return
if (!process.env.npm_config_argv) return specs
const { original } = JSON.parse(process.env.npm_config_argv)
if (original.length === 1) return
if (original.length === 1) return specs
process.argv.splice(1 - original.length, Infinity)
return original.slice(1).map((path) => {
const [name] = path.split('/')
return `packages/${packageMap[name]}/tests/${path.slice(name.length) || '*'}.spec.ts`
})
return original.slice(1).flatMap((path) => {
const [lib] = path.split('/')
const target = path.slice(lib.length)
const prefix = `packages/${libraries[lib]}/tests/`
if (target) return prefix + target + `.spec.ts`
return specs.filter(name => name.startsWith(prefix))
}, 1)
}

module.exports = {
exit: true,
spec: getSpecFromArgv() || [
// 'packages/koishi-core/tests/*.spec.ts',
'packages/koishi-core/tests/command.spec.ts',
'packages/koishi-core/tests/context.spec.ts',
'packages/koishi-core/tests/help.spec.ts',
'packages/koishi-core/tests/hook.spec.ts',
'packages/koishi-core/tests/session.spec.ts',
'packages/koishi-core/tests/parser.spec.ts',
'packages/koishi-utils/tests/*.spec.ts',
'packages/koishi-test-utils/tests/*.spec.ts',
'packages/plugin-common/tests/*.spec.ts',
'packages/plugin-eval/tests/*.spec.ts',
'packages/plugin-github/tests/*.spec.ts',
'packages/plugin-teach/tests/*.spec.ts',
],
spec: getSpecFromArgv(),
}
22 changes: 20 additions & 2 deletions packages/koishi-core/src/command.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Logger, coerce, Time, template, remove } from 'koishi-utils'
import { Argv, Domain } from './parser'
import { Context, NextFunction } from './context'
import { Context, Disposable, NextFunction } from './context'
import { User, Channel } from './database'
import { FieldCollector, Session } from './session'
import { inspect, format } from 'util'
Expand Down Expand Up @@ -33,6 +33,8 @@ export namespace Command {
maxUsage?: UserType<number>
/** min interval */
minInterval?: UserType<number>
/** depend on existing commands */
patch?: boolean
}

export interface Shortcut {
Expand Down Expand Up @@ -61,6 +63,8 @@ export class Command<U extends User.Field = never, G extends Channel.Field = nev
_aliases: string[] = []
_examples: string[] = []
_usage?: Command.Usage
_disposed?: boolean
_disposables?: Disposable[]

private _userFields: FieldCollector<'user'>[] = []
private _channelFields: FieldCollector<'channel'>[] = []
Expand Down Expand Up @@ -129,25 +133,35 @@ export class Command<U extends User.Field = never, G extends Channel.Field = nev
}

alias(...names: string[]) {
if (this._disposed) return this
for (const name of names) {
this._registerAlias(name)
this._disposables?.push(() => {
remove(this._aliases, name)
delete this.app._commandMap[name]
})
}
return this
}

shortcut(name: string | RegExp, config: Command.Shortcut = {}) {
if (this._disposed) return this
config.name = name
config.command = this
config.authority ||= this.config.authority
this.app._shortcuts.push(config)
this._disposables?.push(() => remove(this.app._shortcuts, config))
return this
}

subcommand<D extends string>(def: D, config?: Command.Config): Command<never, never, Domain.ArgumentType<D>>
subcommand<D extends string>(def: D, desc: string, config?: Command.Config): Command<never, never, Domain.ArgumentType<D>>
subcommand(def: string, ...args: any[]) {
def = this.name + (def.charCodeAt(0) === 46 ? '' : '/') + def
return this.context.command(def, ...args)
const desc = typeof args[0] === 'string' ? args.shift() as string : ''
const config = args[0] as Command.Config || {}
if (this._disposed) config.patch = true
return this.context.command(def, desc, config)
}

usage(text: Command.Usage<U, G>) {
Expand All @@ -162,6 +176,7 @@ export class Command<U extends User.Field = never, G extends Channel.Field = nev

option<K extends string, D extends string, T extends Domain.Type>(name: K, desc: D, config: Domain.OptionConfig<T> = {}) {
this._createOption(name, desc, config)
this._disposables?.push(() => this.removeOption(name))
return this as Command<U, G, A, Extend<O, K, Domain.OptionType<D, T>>>
}

Expand All @@ -182,6 +197,7 @@ export class Command<U extends User.Field = never, G extends Channel.Field = nev
} else {
this._checkers.push(callback)
}
this._disposables?.push(() => remove(this._checkers, callback))
return this
}

Expand All @@ -191,6 +207,7 @@ export class Command<U extends User.Field = never, G extends Channel.Field = nev
} else {
this._actions.unshift(callback)
}
this._disposables?.push(() => remove(this._actions, callback))
return this
}

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

dispose() {
this._disposed = true
for (const cmd of this.children.slice()) {
cmd.dispose()
}
Expand Down
16 changes: 12 additions & 4 deletions packages/koishi-core/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,9 +374,9 @@ export class Context {
const config = args[0] as Command.Config
const path = def.split(' ', 1)[0].toLowerCase()
const decl = def.slice(path.length)
const segments = path.split(/(?=[\\./])/)
const segments = path.split(/(?=[./])/g)

let parent: Command = null
let parent: Command, root: Command
segments.forEach((segment, index) => {
const code = segment.charCodeAt(0)
const name = code === 46 ? parent.name + segment : code === 47 ? segment.slice(1) : segment
Expand All @@ -398,6 +398,7 @@ export class Context {
return parent = command
}
command = new Command(name, decl, index === segments.length - 1 ? desc : '', this)
if (!root) root = command
if (parent) {
command.parent = parent
command.config.authority = parent.config.authority
Expand All @@ -408,8 +409,15 @@ export class Context {

if (desc) parent.description = desc
Object.assign(parent.config, config)
this.state.disposables.push(() => parent.dispose())
return parent
if (!config?.patch) {
if (root) this.state.disposables.unshift(() => root.dispose())
return parent
}

if (root) root.dispose()
const command = Object.create(parent)
command._disposables = this.state.disposables
return command
}

getBot(platform: Platform, selfId?: string) {
Expand Down
36 changes: 29 additions & 7 deletions packages/koishi-core/tests/command.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,15 +130,17 @@ describe('Command API', () => {
expect(() => app.command('c/b')).to.throw()
expect(() => app.command('a/d')).not.to.throw()
})
})

it('dispose commands', () => {
const app = new App()
const foo = app.command('foo')
const bar = foo.subcommand('bar')
const test = bar.subcommand('test')
bar.alias('baz').shortcut('1')
test.alias('it').shortcut('2')
describe('Disposable Commands', () => {
const app = new App()
const foo = app.command('foo')
const bar = foo.subcommand('bar')
const test = bar.subcommand('test')
bar.alias('baz').shortcut('1')
test.alias('it').shortcut('2')

it('basic support', () => {
// don't forget help
expect(app._commands).to.have.length(4)
expect(app._shortcuts).to.have.length(3)
Expand All @@ -148,6 +150,26 @@ describe('Command API', () => {
expect(app._shortcuts).to.have.length(1)
expect(foo.children).to.have.length(0)
})

it('patch command', () => {
app.plugin((ctx) => {
ctx.command('foo', 'desc', { patch: true }).alias('fooo').option('opt', 'option 1')
ctx.command('abc', 'desc', { patch: true }).alias('abcd').option('opt', 'option 1')

const { foo, fooo, abc, abcd } = app._commandMap
expect(foo).to.be.ok
expect(fooo).to.be.ok
expect(foo.description).to.equal('desc')
expect(Object.keys(foo._options)).to.have.length(2)
expect(abc).to.be.undefined
expect(abcd).to.be.undefined

ctx.dispose()
expect(app._commandMap.foo).to.be.ok
expect(app._commandMap.fooo).to.be.undefined
expect(Object.keys(foo._options)).to.have.length(1)
})
})
})

describe('Error Handling', () => {
Expand Down
16 changes: 16 additions & 0 deletions packages/koishi-core/tests/context.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { App } from 'koishi-test-utils'
import { Session, Context } from 'koishi-core'
import { noop } from 'koishi-utils'
import { expect } from 'chai'
import { inspect } from 'util'
import jest from 'jest-mock'

const app = new App()
Expand Down Expand Up @@ -165,6 +166,21 @@ describe('Context API', () => {
expect(() => app.plugin({} as any)).to.throw()
expect(() => app.plugin({ apply: {} } as any)).to.throw()
})

it('context inspect', () => {
expect(inspect(app)).to.equal('Context <root>')

app.plugin(function foo(ctx) {
expect(inspect(ctx)).to.equal('Context <unknown>')
})

app.plugin({
name: 'bar',
apply: (ctx) => {
expect(inspect(ctx)).to.equal('Context <bar>')
},
})
})
})

describe('Disposable API', () => {
Expand Down

0 comments on commit 8c2ce5a

Please sign in to comment.