diff --git a/src/core/rule.ts b/src/core/rule.ts index aeb8bc1..dc38875 100644 --- a/src/core/rule.ts +++ b/src/core/rule.ts @@ -71,7 +71,7 @@ function commandMatchesSchema(command: Command, schema: CommandSchema): boolean return false; } - const requiredOptions = schema.options.filter(o => o.required); + const requiredOptions = Object.values(schema.options).filter(o => o.required); for (const opt of requiredOptions) { const hasOption = opt.name in command.options || (opt.short && opt.short in command.options); if (!hasOption) { @@ -131,7 +131,7 @@ function handleGeneratorResult( ctx.state = 'invoking'; return invokeChildRule(host, yielded.rule, yielded.command, ctx as RuleContext); } else { - ctx.schema = parseYieldedSchema({ name: '', params: [], options: [], flags: [] }); + ctx.schema = parseYieldedSchema({ name: '', params: [], options: {}, flags: {} }); ctx.state = 'yielded'; } } else { diff --git a/src/utils/command/command-apply.ts b/src/utils/command/command-apply.ts index 1a2b41b..20375c9 100644 --- a/src/utils/command/command-apply.ts +++ b/src/utils/command/command-apply.ts @@ -19,7 +19,7 @@ function validateCommandCore(command: Command, schema: CommandSchema): string[] errors.push(`参数过多:最多 ${schema.params.length} 个参数,实际 ${command.params.length} 个`); } - const requiredOptions = schema.options.filter(o => o.required); + const requiredOptions = Object.values(schema.options).filter(o => o.required); for (const opt of requiredOptions) { const hasOption = opt.name in command.options || (opt.short && opt.short in command.options); if (!hasOption) { @@ -58,7 +58,7 @@ export function applyCommandSchema( const parsedOptions: Record = { ...command.options }; for (const [key, value] of Object.entries(command.options)) { - const optSchema = schema.options.find(o => o.name === key || o.short === key); + const optSchema = schema.options[key] ?? (key.length === 1 ? Object.values(schema.options).find(o => o.short === key) : undefined); if (optSchema?.schema && typeof value === 'string') { try { parsedOptions[key] = optSchema.schema.parse(value); diff --git a/src/utils/command/schema-parse.ts b/src/utils/command/schema-parse.ts index 1eec9d3..7fdca3d 100644 --- a/src/utils/command/schema-parse.ts +++ b/src/utils/command/schema-parse.ts @@ -5,8 +5,8 @@ export function parseCommandSchema(schemaStr: string, name?: string): CommandSch const schema: CommandSchema = { name: name ?? '', params: [], - options: [], - flags: [], + options: {}, + flags: {}, }; const tokens = tokenizeSchema(schemaStr); @@ -27,28 +27,28 @@ export function parseCommandSchema(schemaStr: string, name?: string): CommandSch if (inner.startsWith('--')) { const result = parseOptionToken(inner.slice(2), false); if (result.isFlag) { - schema.flags.push({ name: result.name, short: result.short }); + schema.flags[result.name] = { name: result.name, short: result.short }; } else { - schema.options.push({ + schema.options[result.name] = { name: result.name, short: result.short, required: false, defaultValue: result.defaultValue, schema: result.schema, - }); + }; } } else if (inner.startsWith('-') && inner.length > 1 && !inner.includes('--')) { const result = parseOptionToken(inner.slice(1), false); if (result.isFlag) { - schema.flags.push({ name: result.name, short: result.short || result.name }); + schema.flags[result.name] = { name: result.name, short: result.short || result.name }; } else { - schema.options.push({ + schema.options[result.name] = { name: result.name, short: result.short || result.name, required: false, defaultValue: result.defaultValue, schema: result.schema, - }); + }; } } else { const isVariadic = inner.endsWith('...'); @@ -78,29 +78,29 @@ export function parseCommandSchema(schemaStr: string, name?: string): CommandSch } else if (token.startsWith('--')) { const result = parseOptionToken(token.slice(2), true); if (result.isFlag) { - schema.flags.push({ name: result.name, short: result.short }); + schema.flags[result.name] = { name: result.name, short: result.short }; } else { - schema.options.push({ + schema.options[result.name] = { name: result.name, short: result.short, required: true, defaultValue: result.defaultValue, schema: result.schema, - }); + }; } i++; } else if (token.startsWith('-') && token.length > 1 && !/^-?\d+$/.test(token)) { const result = parseOptionToken(token.slice(1), true); if (result.isFlag) { - schema.flags.push({ name: result.name, short: result.short || result.name }); + schema.flags[result.name] = { name: result.name, short: result.short || result.name }; } else { - schema.options.push({ + schema.options[result.name] = { name: result.name, short: result.short || result.name, required: true, defaultValue: result.defaultValue, schema: result.schema, - }); + }; } i++; } else if (token.startsWith('<') && token.endsWith('>')) { diff --git a/src/utils/command/types.ts b/src/utils/command/types.ts index d1c24f6..84e6d02 100644 --- a/src/utils/command/types.ts +++ b/src/utils/command/types.ts @@ -30,8 +30,8 @@ export type CommandFlagSchema = { export type CommandSchema = { name: string; params: CommandParamSchema[]; - options: CommandOptionSchema[]; - flags: CommandFlagSchema[]; + options: Record; + flags: Record; } export interface ParsedOptionResult { diff --git a/tests/core/rule.test.ts b/tests/core/rule.test.ts index 32daa43..8410edf 100644 --- a/tests/core/rule.test.ts +++ b/tests/core/rule.test.ts @@ -28,8 +28,8 @@ describe('Rule System', () => { expect(rule.schema.params[0].required).toBe(true); expect(rule.schema.params[1].name).toBe('to'); expect(rule.schema.params[1].required).toBe(true); - expect(rule.schema.flags).toHaveLength(1); - expect(rule.schema.flags[0].name).toBe('force'); + expect(Object.keys(rule.schema.flags)).toHaveLength(1); + expect(rule.schema.flags.force.name).toBe('force'); }); it('should create a generator when called', () => { diff --git a/tests/utils/command-inline-schema.test.ts b/tests/utils/command-inline-schema.test.ts index 57e5c65..4b9d634 100644 --- a/tests/utils/command-inline-schema.test.ts +++ b/tests/utils/command-inline-schema.test.ts @@ -21,11 +21,11 @@ describe('parseCommandSchema with inline-schema', () => { it('should parse schema with typed options', () => { const schema = parseCommandSchema('move [--all: boolean] [--count: number]'); expect(schema.name).toBe('move'); - expect(schema.flags).toHaveLength(1); - expect(schema.options).toHaveLength(1); - expect(schema.flags[0].name).toBe('all'); - expect(schema.options[0].name).toBe('count'); - expect(schema.options[0].schema).toBeDefined(); + expect(Object.keys(schema.flags)).toHaveLength(1); + expect(Object.keys(schema.options)).toHaveLength(1); + expect(schema.flags.all.name).toBe('all'); + expect(schema.options.count.name).toBe('count'); + expect(schema.options.count.schema).toBeDefined(); }); it('should parse schema with tuple type', () => { @@ -58,7 +58,7 @@ describe('parseCommandSchema with inline-schema', () => { ); expect(schema.name).toBe('move'); expect(schema.params).toHaveLength(2); - expect(schema.options).toHaveLength(1); + expect(Object.keys(schema.options)).toHaveLength(1); }); it('should parse schema with optional typed param', () => { @@ -73,9 +73,9 @@ describe('parseCommandSchema with inline-schema', () => { it('should parse schema with optional typed option', () => { const schema = parseCommandSchema('move [--speed: number]'); expect(schema.name).toBe('move'); - expect(schema.options).toHaveLength(1); - expect(schema.options[0].required).toBe(false); - expect(schema.options[0].schema).toBeDefined(); + expect(Object.keys(schema.options)).toHaveLength(1); + expect(schema.options.speed.required).toBe(false); + expect(schema.options.speed.schema).toBeDefined(); }); }); diff --git a/tests/utils/command-schema.test.ts b/tests/utils/command-schema.test.ts index 8047e48..4f0661a 100644 --- a/tests/utils/command-schema.test.ts +++ b/tests/utils/command-schema.test.ts @@ -7,18 +7,8 @@ describe('parseCommandSchema', () => { expect(schema).toEqual({ name: '', params: [], - options: [], - flags: [], - }); - }); - - it('should parse command name only', () => { - const schema = parseCommandSchema('move'); - expect(schema).toEqual({ - name: 'move', - params: [], - options: [], - flags: [], + options: {}, + flags: {}, }); }); @@ -55,34 +45,36 @@ describe('parseCommandSchema', () => { it('should parse long flags', () => { const schema = parseCommandSchema('move [--force] [--quiet]'); - expect(schema.flags).toEqual([ - { name: 'force' }, - { name: 'quiet' }, - ]); + expect(schema.flags).toEqual({ + force: { name: 'force', short: undefined }, + quiet: { name: 'quiet', short: undefined }, + }); }); it('should parse short flags', () => { const schema = parseCommandSchema('move [-f] [-q]'); - expect(schema.flags).toEqual([ - { name: 'f', short: 'f' }, - { name: 'q', short: 'q' }, - ]); + expect(schema.flags).toEqual({ + f: { name: 'f', short: 'f' }, + q: { name: 'q', short: 'q' }, + }); }); it('should parse long options', () => { const schema = parseCommandSchema('move --x: string [--y: string]'); - expect(schema.options).toEqual([ - { name: 'x', required: true, schema: expect.any(Object) }, - { name: 'y', required: false, schema: expect.any(Object) }, - ]); + expect(Object.keys(schema.options)).toEqual(['x', 'y']); + expect(schema.options.x).toMatchObject({ name: 'x', required: true }); + expect(schema.options.x.schema).toBeDefined(); + expect(schema.options.y).toMatchObject({ name: 'y', required: false }); + expect(schema.options.y.schema).toBeDefined(); }); it('should parse short options', () => { const schema = parseCommandSchema('move -x: string [-y: string]'); - expect(schema.options).toEqual([ - { name: 'x', short: 'x', required: true, schema: expect.any(Object) }, - { name: 'y', short: 'y', required: false, schema: expect.any(Object) }, - ]); + expect(Object.keys(schema.options)).toEqual(['x', 'y']); + expect(schema.options.x).toMatchObject({ name: 'x', short: 'x', required: true }); + expect(schema.options.x.schema).toBeDefined(); + expect(schema.options.y).toMatchObject({ name: 'y', short: 'y', required: false }); + expect(schema.options.y.schema).toBeDefined(); }); it('should parse mixed schema', () => { @@ -93,13 +85,13 @@ describe('parseCommandSchema', () => { { name: 'from', required: true, variadic: false, schema: undefined }, { name: 'to', required: true, variadic: false, schema: undefined }, ], - flags: [ - { name: 'force' }, - { name: 'f', short: 'f' }, - ], - options: [ - { name: 'speed', short: 's', required: false, schema: expect.any(Object), defaultValue: undefined }, - ], + flags: { + force: { name: 'force', short: undefined }, + f: { name: 'f', short: 'f' }, + }, + options: { + speed: { name: 'speed', short: 's', required: false, schema: expect.any(Object), defaultValue: undefined }, + }, }); }); @@ -107,8 +99,8 @@ describe('parseCommandSchema', () => { const schema = parseCommandSchema('place [x...] [--rotate: number] [--force] [-f]'); expect(schema.name).toBe('place'); expect(schema.params).toHaveLength(3); - expect(schema.flags).toHaveLength(2); - expect(schema.options).toHaveLength(1); + expect(Object.keys(schema.flags)).toHaveLength(2); + expect(Object.keys(schema.options)).toHaveLength(1); }); }); @@ -237,39 +229,8 @@ describe('integration', () => { it('should parse short alias syntax', () => { const schema = parseCommandSchema('move [--verbose: boolean -v]'); - expect(schema.flags).toHaveLength(1); - expect(schema.flags[0]).toEqual({ name: 'verbose', short: 'v' }); - }); - - it('should parse short alias for options', () => { - const schema = parseCommandSchema('move [--speed: number -s]'); - expect(schema.options).toHaveLength(1); - expect(schema.options[0]).toEqual({ - name: 'speed', - short: 's', - required: false, - schema: expect.any(Object), - defaultValue: undefined, - }); - }); - - it('should parse default value syntax', () => { - const schema = parseCommandSchema('move [--speed: number = 10]'); - expect(schema.options).toHaveLength(1); - expect(schema.options[0].defaultValue).toBe(10); - }); - - it('should parse default string value', () => { - const schema = parseCommandSchema('move [--name: string = "default"]'); - expect(schema.options).toHaveLength(1); - expect(schema.options[0].defaultValue).toBe('default'); - }); - - it('should parse short alias with default value', () => { - const schema = parseCommandSchema('move [--speed: number -s = 5]'); - expect(schema.options).toHaveLength(1); - expect(schema.options[0].short).toBe('s'); - expect(schema.options[0].defaultValue).toBe(5); + expect(Object.keys(schema.flags)).toHaveLength(1); + expect(schema.flags.verbose).toEqual({ name: 'verbose', short: 'v' }); }); it('should parse command with short alias', () => { @@ -288,3 +249,5 @@ describe('integration', () => { expect(command.options.s).toBe('100'); }); }); + +