Skip to content

Commit

Permalink
fix(core): fix incorrect parser behavior for boolean & terminator
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Mar 12, 2021
1 parent 30ea750 commit feb75cb
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 44 deletions.
1 change: 1 addition & 0 deletions .mocharc.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ module.exports = {
'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',
Expand Down
42 changes: 29 additions & 13 deletions packages/koishi-core/src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export namespace Domain {

create('string', source => source)
create('text', source => source)
create('boolean', () => true)

create('number', (source) => {
const value = +source
Expand Down Expand Up @@ -229,10 +230,13 @@ export namespace Domain {
description: syntax,
}

const fallbackType = typeof option.fallback
if ('value' in config) {
names.forEach(name => option.values[name] = config.value)
} else if (!bracket.trim()) {
option.type = 'boolean'
} else if (!option.type && fallbackType === 'string' || fallbackType === 'number') {
option.type = fallbackType
}

this._assignOption(option, names, this._namedOptions)
Expand Down Expand Up @@ -289,11 +293,16 @@ export namespace Domain {

// default behavior
if (implicit) return true
if (quoted) return source
const n = +source
return n * 0 === 0 ? n : source
}

parse(argv: Argv): Argv {
parse(argv: Argv): Argv
parse(source: string, terminator?: string): Argv
parse(argv: string | Argv, terminator?: string): Argv {
if (typeof argv === 'string') argv = Argv.parse(argv, terminator)

const args: string[] = []
const options: Record<string, any> = {}
const source = this.name + ' ' + Argv.stringify(argv)
Expand Down Expand Up @@ -386,7 +395,7 @@ export namespace Domain {
}

delete argv.tokens
return { options, args, source, error: this._error }
return { options, args, source, rest: argv.rest, error: this._error }
}

private stringifyArg(value: any) {
Expand Down Expand Up @@ -428,6 +437,7 @@ export interface Argv<U extends User.Field = never, G extends Channel.Field = ne
error?: string
source?: string
initiator?: string
terminator?: string
session?: Session<U, G>
command?: Command<U, G, A, O>
rest?: string
Expand Down Expand Up @@ -488,15 +498,17 @@ export namespace Argv {
parent.inters.push({ ...argv, pos: content.length, initiator: capture[0] })
} else {
const quoted = capture[0] === quote
const rest = source.slice(capture.index + +quoted).trimStart()
if (!quoted && quote) {
const rest = source.slice(capture.index + +quoted)
parent.rest = rest.trimStart()
parent.quoted = quoted
parent.terminator = capture[0]
if (quoted) {
parent.terminator += rest.slice(0, -parent.rest.length)
} else if (quote) {
content = leftQuotes[index] + content
parent.inters.forEach(inter => inter.pos += 1)
}
parent.rest = rest
parent.quoted = quoted
parent.content = content
parent.terminator = capture[0]
if (quote === "'") Argv.revert(parent)
return parent
}
Expand All @@ -521,11 +533,10 @@ export namespace Argv {
}

stringify(argv: Argv) {
return argv.tokens.map((token) => {
return /^\s+$/.test(token.terminator)
? token.content + token.terminator
: token.content
}).join('')
return argv.tokens.reduce((prev, token) => {
if (token.quoted) prev += leftQuotes[rightQuotes.indexOf(token.terminator[0])]
return prev + token.content + token.terminator
}, '')
}
}

Expand All @@ -536,7 +547,12 @@ export namespace Argv {
}

export function stringify(argv: Argv) {
return defaultTokenizer.stringify(argv)
const source = defaultTokenizer.stringify(argv)
if (argv.rest) {
const { terminator } = argv.tokens[argv.tokens.length - 1]
if (!leftQuotes.includes(terminator[0])) return source.slice(0, -terminator.length)
}
return source
}

export function revert(token: Token) {
Expand Down
62 changes: 31 additions & 31 deletions packages/koishi-core/tests/parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ describe('Parser API', () => {

describe('Register Options', () => {
it('register', () => {
cmd = app.command('cmd2 <foo> [bar...]')
cmd = app.command('cmd2 <foo> [bar:text]')
cmd.option('alpha', '-a')
cmd.option('beta', '-b <beta>')
cmd.option('gamma', '-c <gamma>', { fallback: 0 })
cmd.option('delta', '-d <gamma>', { type: 'string' })
cmd.option('delta', '-d <delta>', { type: 'string' })
})

it('option parser', () => {
Expand All @@ -44,27 +44,27 @@ describe('Parser API', () => {
expect(cmd.parse('--no-beta')).to.have.shape({ options: { beta: false } })
expect(cmd.parse('--alpha 1')).to.have.shape({ options: { alpha: true } })
expect(cmd.parse('--beta 1')).to.have.shape({ options: { beta: 1 } })
expect(cmd.parse('--beta "1"')).to.have.shape({ options: { beta: 1 } })
expect(cmd.parse('--beta -1')).to.have.shape({ options: { beta: true } })
expect(cmd.parse('--beta "1"')).to.have.shape({ options: { beta: '1' } })
expect(cmd.parse('--beta -1')).to.have.shape({ options: { beta: -1 } })
})

it('typed options', () => {
expect(cmd.parse('')).to.have.shape({ options: { gamma: 0 } })
expect(cmd.parse('--gamma')).to.have.shape({ options: { gamma: 0 } })
expect(cmd.parse('--gamma 1')).to.have.shape({ options: { gamma: 1 } })
expect(cmd.parse('--gamma -1')).to.have.shape({ options: { gamma: -1 } })
expect(cmd.parse('--gamma a')).to.have.shape({ options: { gamma: NaN } })
expect(cmd.parse('--delta')).to.have.shape({ options: { delta: '' } })
expect(cmd.parse('--delta 1')).to.have.shape({ options: { delta: '1' } })
expect(cmd.parse('--delta -1')).to.have.shape({ options: { delta: '-1' } })
expect(cmd.parse('')).to.have.shape({ error: '', options: { gamma: 0 } })
expect(cmd.parse('--gamma')).to.have.shape({ error: '', options: { gamma: 0 } })
expect(cmd.parse('--gamma 1')).to.have.shape({ error: '', options: { gamma: 1 } })
expect(cmd.parse('--gamma -1')).to.have.shape({ error: '', options: { gamma: -1 } })
expect(cmd.parse('--gamma a').error).to.be.ok
expect(cmd.parse('--delta')).to.have.shape({ error: '', options: { delta: '' } })
expect(cmd.parse('--delta 1')).to.have.shape({ error: '', options: { delta: '1' } })
expect(cmd.parse('--delta -1')).to.have.shape({ error: '', options: { delta: '-1' } })
})

it('short alias', () => {
expect(cmd.parse('-ab ""')).to.have.shape({ options: { alpha: true, beta: '' } })
expect(cmd.parse('-ab=')).to.have.shape({ options: { alpha: true, beta: true } })
expect(cmd.parse('-ab 1')).to.have.shape({ options: { alpha: true, beta: 1 } })
expect(cmd.parse('-ab=1')).to.have.shape({ options: { alpha: true, beta: 1 } })
expect(cmd.parse('-ab -1')).to.have.shape({ options: { alpha: true, beta: true } })
expect(cmd.parse('-ab -1')).to.have.shape({ options: { alpha: true, beta: -1 } })
expect(cmd.parse('-ab=-1')).to.have.shape({ options: { alpha: true, beta: -1 } })
})

Expand All @@ -79,15 +79,15 @@ describe('Parser API', () => {
})

it('valued options', () => {
cmd = app.command('cmd2 <foo> [bar...]')
cmd = app.command('cmd2 <foo> [bar:text]')
cmd.option('alpha', '-A, --no-alpha', { value: false })
cmd.option('gamma', '-C', { value: 1 })
expect(cmd.parse('-A')).to.have.shape({ options: { alpha: false } })
expect(cmd.parse('-a')).to.have.shape({ options: { alpha: true } })
expect(cmd.parse('--alpha')).to.have.shape({ options: { alpha: true } })
expect(cmd.parse('--no-alpha')).to.have.shape({ options: { alpha: false } })
expect(cmd.parse('-C')).to.have.shape({ options: { gamma: 1 } })
expect(cmd.parse('')).to.have.shape({ options: { gamma: 0 }, args: [], rest: '' })
expect(cmd.parse('')).to.have.shape({ options: { gamma: 0 }, args: [] })
})
})

Expand All @@ -109,28 +109,28 @@ describe('Parser API', () => {
})

it('rest option', () => {
cmd.option('rest', '-- <rest...>')
expect(cmd.parse('a b -- c d')).to.have.shape({ args: ['a', 'b'], options: { rest: 'c d' }, rest: '' })
expect(cmd.parse('a "b -- c" d')).to.have.shape({ args: ['a', 'b -- c', 'd'], options: {}, rest: '' })
expect(cmd.parse('a b -- "c d"')).to.have.shape({ args: ['a', 'b'], options: { rest: 'c d' }, rest: '' })
cmd.option('rest', '-- <rest:text>')
expect(cmd.parse('a b -- c d')).to.have.shape({ args: ['a', 'b'], options: { rest: 'c d' } })
expect(cmd.parse('a "b -- c" d')).to.have.shape({ args: ['a', 'b -- c', 'd'], options: {} })
expect(cmd.parse('a b -- "c d"')).to.have.shape({ args: ['a', 'b'], options: { rest: '"c d"' } })
})

it('terminator 1', () => {
expect(cmd.parse('foo bar baz', ';')).to.have.shape({ args: ['foo', 'bar', 'baz'], rest: '' })
expect(cmd.parse('"foo bar" baz', ';')).to.have.shape({ args: ['foo bar', 'baz'], rest: '' })
expect(cmd.parse('"foo bar "baz', ';')).to.have.shape({ args: ['"foo', 'bar', '"baz'], rest: '' })
expect(cmd.parse('foo" bar" baz', ';')).to.have.shape({ args: ['foo"', 'bar"', 'baz'], rest: '' })
expect(cmd.parse('foo;bar baz', ';')).to.have.shape({ args: ['foo'], rest: ';bar baz' })
expect(cmd.parse('"foo;bar";baz', ';')).to.have.shape({ args: ['foo;bar'], rest: ';baz' })
expect(cmd.parse('foo bar baz', ';')).to.have.shape({ args: ['foo', 'bar', 'baz'] })
expect(cmd.parse('"foo bar" baz', ';')).to.have.shape({ args: ['foo bar', 'baz'] })
expect(cmd.parse('"foo bar "baz', ';')).to.have.shape({ args: ['"foo bar "baz'] })
expect(cmd.parse('foo" bar" baz', ';')).to.have.shape({ args: ['foo"', 'bar"', 'baz'] })
expect(cmd.parse('foo;bar baz', ';')).to.have.shape({ args: ['foo'], rest: 'bar baz' })
expect(cmd.parse('"foo;bar";baz', ';')).to.have.shape({ args: ['foo;bar'], rest: 'baz' })
})

it('terminator 2', () => {
expect(cmd.parse('-- foo bar baz', ';')).to.have.shape({ options: { rest: 'foo bar baz' }, rest: '' })
expect(cmd.parse('-- "foo bar" baz', ';')).to.have.shape({ options: { rest: '"foo bar" baz' }, rest: '' })
expect(cmd.parse('-- "foo bar baz"', ';')).to.have.shape({ options: { rest: 'foo bar baz' }, rest: '' })
expect(cmd.parse('-- foo;bar baz', ';')).to.have.shape({ options: { rest: 'foo' }, rest: ';bar baz' })
expect(cmd.parse('-- "foo;bar" baz', ';')).to.have.shape({ options: { rest: '"foo' }, rest: ';bar" baz' })
expect(cmd.parse('-- "foo;bar";baz', ';')).to.have.shape({ options: { rest: 'foo;bar' }, rest: ';baz' })
expect(cmd.parse('-- foo bar baz', ';')).to.have.shape({ options: { rest: 'foo bar baz' } })
expect(cmd.parse('-- "foo bar" baz', ';')).to.have.shape({ options: { rest: '"foo bar" baz' } })
expect(cmd.parse('-- "foo bar baz"', ';')).to.have.shape({ options: { rest: '"foo bar baz"' } })
expect(cmd.parse('-- foo;bar baz', ';')).to.have.shape({ options: { rest: 'foo' }, rest: 'bar baz' })
expect(cmd.parse('-- "foo;bar" baz', ';')).to.have.shape({ options: { rest: '"foo;bar" baz' } })
expect(cmd.parse('-- "foo;bar";baz', ';')).to.have.shape({ options: { rest: '"foo;bar"' }, rest: 'baz' })
})
})
})

0 comments on commit feb75cb

Please sign in to comment.