Skip to content

Commit 96acbc7

Browse files
Eliazegoist
authored andcommitted
feat: add support for Array types (#42)
* feat: add surpport for Array types * fix: add conditional statements * fix: make dot nested options behave the same * docs: add the description of config.type
1 parent 2c5fa5e commit 96acbc7

File tree

5 files changed

+85
-7
lines changed

5 files changed

+85
-7
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ Add a global option.
310310
The option also accepts a third argument `config` for addtional option config:
311311

312312
- `config.default`: Default value for the option.
313+
- `config.type`: `any[]` When set to `[]`, the option value returns an array type. You can also use a conversion function such as `[String]`, which will invoke the option value with `String`.
313314

314315
#### cli.parse(argv?)
315316

src/CAC.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Command, {
88
CommandExample
99
} from './Command'
1010
import { OptionConfig } from './Option'
11-
import { getMriOptions, camelcase, setDotProp } from './utils'
11+
import { getMriOptions, camelcase, setDotProp, setByType } from './utils'
1212

1313
interface ParsedArgv {
1414
args: ReadonlyArray<string>
@@ -263,12 +263,23 @@ class CAC extends EventEmitter {
263263
? command.config.ignoreOptionDefaultValue
264264
: this.globalCommand.config.ignoreOptionDefaultValue
265265

266-
if (!ignoreDefault) {
267-
for (const cliOption of cliOptions) {
268-
if (cliOption.config.default !== undefined) {
269-
for (const name of cliOption.names) {
270-
options[name] = cliOption.config.default
271-
}
266+
let transforms = Object.create(null)
267+
268+
for (const cliOption of cliOptions) {
269+
if (!ignoreDefault && cliOption.config.default !== undefined) {
270+
for (const name of cliOption.names) {
271+
options[name] = cliOption.config.default
272+
}
273+
}
274+
275+
// If options type is defined
276+
if (Array.isArray(cliOption.config.type)) {
277+
if (transforms[cliOption.name] === undefined) {
278+
transforms[cliOption.name] = Object.create(null)
279+
280+
transforms[cliOption.name]['shouldTransform'] = true
281+
transforms[cliOption.name]['transformFunction'] =
282+
cliOption.config.type[0]
272283
}
273284
}
274285
}
@@ -279,6 +290,7 @@ class CAC extends EventEmitter {
279290
return i === 0 ? camelcase(v) : v
280291
})
281292
setDotProp(options, keys, parsed[key])
293+
setByType(options, transforms)
282294
}
283295

284296
return {

src/Option.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { removeBrackets } from './utils'
22

33
interface OptionConfig {
44
default?: any
5+
type?: any[]
56
}
67

78
export default class Option {

src/__test__/index.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,50 @@ test('negated optional validation', () => {
9191
cli.globalCommand.checkOptionValue()
9292
expect(options.config).toBe(false)
9393
})
94+
95+
test('array types without transformFunction', () => {
96+
const cli = cac()
97+
98+
cli
99+
.option(
100+
'--externals <external>',
101+
'Add externals(can be used for multiple times',
102+
{
103+
type: []
104+
}
105+
)
106+
.option('--scale [level]', 'Scaling level')
107+
108+
const { options: options1 } = cli.parse(
109+
`node bin --externals.env.prod production --scale`.split(' ')
110+
)
111+
expect(options1.externals).toEqual([{ env: { prod: 'production' } }])
112+
expect(options1.scale).toEqual(true)
113+
114+
const { options: options2 } = cli.parse(
115+
`node bin --externals foo --externals bar`.split(' ')
116+
)
117+
expect(options2.externals).toEqual(['foo', 'bar'])
118+
119+
const { options: options3 } = cli.parse(
120+
`node bin --externals.env foo --externals.env bar`.split(' ')
121+
)
122+
expect(options3.externals).toEqual([{ env: ['foo', 'bar'] }])
123+
})
124+
125+
test('array types with transformFunction', () => {
126+
const cli = cac()
127+
128+
cli
129+
.command('build [entry]', 'Build your app')
130+
.option('--config <configFlie>', 'Use config file for building', {
131+
type: [String]
132+
})
133+
.option('--scale [level]', 'Scaling level')
134+
135+
const { options } = cli.parse(
136+
`node bin build app.js --config config.js --scale`.split(' ')
137+
)
138+
expect(options.config).toEqual(['config.js'])
139+
expect(options.scale).toEqual(true)
140+
})

src/utils.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,20 @@ export const setDotProp = (
115115
: []
116116
}
117117
}
118+
119+
export const setByType = (
120+
obj: { [k: string]: any },
121+
transforms: { [k: string]: any }
122+
) => {
123+
for (const key of Object.keys(transforms)) {
124+
const transform = transforms[key]
125+
126+
if (transform.shouldTransform) {
127+
obj[key] = Array.prototype.concat.call([], obj[key])
128+
129+
if (typeof transform.transformFunction === 'function') {
130+
obj[key] = obj[key].map(transform.transformFunction)
131+
}
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)