类型体操 - 提取命令参数名

最近在开发一款命令行代码生成器,使用的技术栈是unbuild + typescript + commander + inquirer。

项目我把每个命令分目录来存放,从index.ts导出,但是commander没有能给我很好提示,我预期效果是

force,local,packageVersion 是属于全局参数,想实现的就是

'-g, --generate [template]' -> { force: boolean, local: boolean, packageVersion: string } & {  generate: string | boolean }

因为在commander中[]表示可选参数,<>表示必填参数,因此上面的generate类型可能为string和boolean。

同理,得出以下用例

'-g, --generate [template]' -> { force: boolean, local: boolean, packageVersion: string } & {  generate:  boolean | string  }
'-u, --use-custom-template [template]' -> { force: boolean, local: boolean, packageVersion: string } & {  useCustomTemplate: boolean | string }
'-g, --generate <template>' -> { force: boolean, local: boolean, packageVersion: string } & {  generate: string }
'-g, --generate' -> { force: boolean, local: boolean, packageVersion: string } & {  generate: boolean }
'--generate' -> { force: boolean, local: boolean, packageVersion: string } & {  generate: boolean }

将以上问题进行拆分:
1、--generate 提取字段名

type CommandOptionName<T extends string> = T extends `--${infer Flag}` ? { [ _Key in Flag ]: boolean } 
type TARGET = TEST<'--generate'> // { generate: boolean } 

2、 --use-custom-template 转化字段名为小驼峰命名法

type LinkText<A extends string, B extends string> = `${A}${B}`
type CapFirst<S extends string> = S extends `${infer First}${infer Rest}`
  ? `${Uppercase<First>}${Rest}`
  : S;
type Underline2BigHump<T extends string> = T extends `${infer Prefix}-${infer Suffix}` ?  LinkText<Prefix, Underline2BigHump<CapFirst<Suffix>>>  : T;

type TARGET = Underline2BigHump<'use-custom-template'> // useCustomTemplate

3、将上面两步结合,并处理一些特殊情况,就得出以下类型

type CommandOptionName<T extends string> = 
T extends `-${infer _K}, --${infer Flag} [${infer _K}]`? { [ _Key in Underline2BigHump<Flag> ]: string | boolean } : // 处理 -x, --xxxx [xxxx]
T extends `-${infer _K}, --${infer Flag} <${infer _K}>`? { [ _Key in Underline2BigHump<Flag> ]: string } : // 处理 -x, --xxxx <xxxx>
T extends `-${infer _K}, --${infer Flag}`? { [ _Key in Underline2BigHump<Flag> ]: boolean } : // 处理 -x, --xxxx
T extends `--${infer Flag} [${infer _K}]` ? { [ _Key in Underline2BigHump<Flag> ]: string | boolean } : // 处理 --xxxx [xxxx]
T extends `--${infer Flag} <${infer _K}>` ? { [ _Key in Underline2BigHump<Flag> ]: string} :  // 处理 --xxxx <xxxx>
T extends `--${infer Flag}` ? { [ _Key in Underline2BigHump<Flag> ]: boolean } : // 处理 --xxxx
never

type A = Expect<CommandOptionName<'--init'>, { init: boolean }> // true
const a:A = true
type B = Expect<CommandOptionName<'--init [char]'>, { init: string | boolean }> // true
const b:B = true
type C = Expect<CommandOptionName<'--init <char>'>, { init: string }>  // true
const c:C = true
type D = Expect<CommandOptionName<'-c, --init'>, { init: boolean }>  // true
const d:D = true
type E = Expect<CommandOptionName<'-c, --init <char>'>, { init: string }>  // true
const e:E = true
type F = Expect<CommandOptionName<'-c, --init [char]'>, { init: string | boolean }>  // true
const f:F = true
type G = Expect<CommandOptionName<'-ct, --custom-template [char]'>, { customTemplate: string | boolean }>  // true
const g:G = true
type H = Expect<CommandOptionName<'-ct, --custom-template-admin [char]'>, { customTemplateAdmin: string | boolean }>  // true
const h:H = true
type H1 = Expect<CommandOptionName<'-ct, --custom-template-admin-test [char]'>, { customTemplateAdminTest: string | boolean }>  // true
const h1:H1 = true
type H2 = Expect<CommandOptionName<'-v, --version'>, { version: boolean }>  // true
const h2:H2 = true

posted @ 2024-11-24 17:05  TwoKe  阅读(19)  评论(0)    收藏  举报