Skip to content

Commit c042a34

Browse files
committed
feat: example lines, help style updates
1 parent 214f808 commit c042a34

7 files changed

Lines changed: 291 additions & 160 deletions

File tree

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
"scripts": {
1818
"build": "tsc -p tsconfig.build.json && cp package.json README.md build",
1919
"dev": "tsc --watch",
20-
"example": "ts-node src/example.ts",
20+
"cmd": "ts-node src/sample.ts",
2121
"test": "jest",
22-
"docs": "typedoc --out docs src --plugin typedoc-plugin-zod --theme default"
22+
"docgen": "typedoc --out docs src --plugin typedoc-plugin-zod --theme default"
2323
},
2424
"devDependencies": {
2525
"@types/jest": "^29.5.8",

src/command.ts

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import MassargOption, {
88
MassargHelpFlag,
99
} from './option'
1010
import { setOrPush } from './utils'
11+
import MassargExample, { ExampleConfig } from './example'
1112

1213
export const CommandConfig = <RunArgs extends z.ZodType>(args: RunArgs) =>
1314
z.object({
@@ -54,8 +55,9 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
5455
description: string
5556
aliases: string[]
5657
private _run?: Runner<Args>
57-
options: MassargOption[] = []
5858
commands: MassargCommand<any>[] = []
59+
options: MassargOption[] = []
60+
examples: MassargExample[] = []
5961
args: Partial<Args> = {}
6062

6163
constructor(options: CommandConfig<Args>) {
@@ -79,6 +81,14 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
7981
): MassargCommand<Args> {
8082
try {
8183
const command = config instanceof MassargCommand ? config : new MassargCommand(config)
84+
const existing = this.commands.find((c) => c.name === command.name)
85+
if (existing) {
86+
throw new ValidationError({
87+
code: 'duplicate_command',
88+
message: `Command "${command.name}" already exists`,
89+
path: [this.name, command.name],
90+
})
91+
}
8292
this.commands.push(command)
8393
return this
8494
} catch (e) {
@@ -93,11 +103,23 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
93103
}
94104
}
95105

96-
flag(config: Omit<OptionConfig<boolean>, 'parse'>): MassargCommand<Args>
106+
flag(config: Omit<OptionConfig<boolean>, 'parse' | 'isDefault'>): MassargCommand<Args>
97107
flag(config: MassargFlag): MassargCommand<Args>
98-
flag(config: Omit<OptionConfig<boolean>, 'parse'> | MassargFlag): MassargCommand<Args> {
108+
flag(
109+
config: Omit<OptionConfig<boolean>, 'parse' | 'isDefault'> | MassargFlag,
110+
): MassargCommand<Args> {
99111
try {
100112
const flag = config instanceof MassargFlag ? config : new MassargFlag(config)
113+
if (flag.isDefault) {
114+
const defaultOption = this.options.find((o) => o.isDefault)
115+
if (defaultOption) {
116+
throw new ValidationError({
117+
code: 'duplicate_default_option',
118+
message: `Option "${flag.name}" cannot be set as default because option "${defaultOption.name}" is already set as default`,
119+
path: [this.name, flag.name],
120+
})
121+
}
122+
}
101123
this.options.push(flag as MassargOption)
102124
return this
103125
} catch (e) {
@@ -118,6 +140,16 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
118140
try {
119141
const option =
120142
config instanceof MassargOption ? config : MassargOption.fromTypedConfig(config)
143+
if (option.isDefault) {
144+
const defaultOption = this.options.find((o) => o.isDefault)
145+
if (defaultOption) {
146+
throw new ValidationError({
147+
code: 'duplicate_default_option',
148+
message: `Option "${option.name}" cannot be set as default because option "${defaultOption.name}" is already set as default`,
149+
path: [this.name, option.name],
150+
})
151+
}
152+
}
121153
this.options.push(option as MassargOption)
122154
return this
123155
} catch (e) {
@@ -132,6 +164,11 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
132164
}
133165
}
134166

167+
example(config: ExampleConfig): MassargCommand<Args> {
168+
this.examples.push(new MassargExample(config))
169+
return this
170+
}
171+
135172
main<A extends ArgsObject = Args>(run: Runner<A>): MassargCommand<Args> {
136173
this._run = run
137174
return this

src/example.ts

Lines changed: 30 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,34 @@
1-
import { massarg } from '.'
2-
import MassargCommand from './command'
3-
import { ParseError } from './error'
1+
import z from 'zod'
2+
import { ValidationError } from './error'
43

5-
type A = { test: boolean }
6-
const echoCmd = massarg<any>({
7-
name: 'echo',
8-
description: 'Echo back the arguments',
9-
aliases: ['e'],
10-
run: (opts) => {
11-
console.log('Echoing back', opts)
12-
},
4+
export const ExampleConfig = z.object({
5+
description: z.string().optional(),
6+
input: z.string().optional(),
7+
output: z.string().optional(),
138
})
14-
const addCmd = massarg<{ component: string }>({
15-
name: 'add',
16-
description: 'Add a component',
17-
aliases: ['a'],
18-
run: (opts, parser) => {
19-
parser.printHelp()
20-
console.log('Adding component', opts.component)
21-
},
22-
})
23-
.option({
24-
name: 'component',
25-
description:
26-
'Component to add. Ut consectetur eu et occaecat enim magna amet eiusmod laboris deserunt proident culpa nulla ipsum adipiscing ullamco laboris sed est',
27-
aliases: ['c'],
28-
// aliases: "" as never,
29-
})
30-
.option({
31-
name: 'classes',
32-
description: 'Classes to add',
33-
aliases: ['l'],
34-
array: true,
35-
})
36-
.option({
37-
name: 'custom',
38-
description: 'Custom option',
39-
aliases: ['x'],
40-
parse: (value) => {
41-
const asNumber = Number(value)
42-
if (isNaN(asNumber)) {
43-
throw new ParseError({
44-
path: ['custom'],
45-
message: 'Custom option must be a number',
46-
code: 'invalid_number',
47-
})
48-
}
49-
return {
50-
value: asNumber,
51-
half: asNumber / 2,
52-
double: asNumber * 2,
53-
}
54-
},
55-
})
56-
57-
const removeCmd = new MassargCommand<{ component: string }>({
58-
name: 'remove',
59-
description: 'Remove a component',
60-
aliases: ['r'],
61-
run: (opts) => {
62-
console.log('Removing component', opts.component)
63-
},
64-
}).option({
65-
name: 'component',
66-
description: 'Component to remove',
67-
aliases: ['c'],
68-
})
69-
70-
const args = massarg<A>({
71-
name: 'my-cli',
72-
description: 'This is an example CLI',
73-
bindHelpOption: true,
74-
bindHelpCommand: true,
75-
})
76-
.main((opts, parser) => {
77-
console.log('Main command - printing all opts')
78-
console.log(opts, '\n')
79-
parser.printHelp()
80-
})
81-
.command(echoCmd)
82-
.command(addCmd)
83-
.command(removeCmd)
84-
.flag({
85-
name: 'bool',
86-
description: 'Example boolean option',
87-
aliases: ['b'],
88-
})
89-
.option({
90-
name: 'number',
91-
description: 'Example number option',
92-
aliases: ['n'],
93-
type: 'number',
94-
})
9+
export type ExampleConfig = z.infer<typeof ExampleConfig>
9510

96-
// console.log("Opts:", args.getArgs(process.argv.slice(2)), "\n")
11+
export default class MassargExample {
12+
description: string | undefined
13+
input: string | undefined
14+
output: string | undefined
9715

98-
args.parse(process.argv.slice(2))
16+
constructor(config: ExampleConfig) {
17+
ExampleConfig.parse(config)
18+
if (
19+
config.description === undefined &&
20+
config.input === undefined &&
21+
config.output === undefined
22+
) {
23+
throw new ValidationError({
24+
code: 'invalid_example',
25+
message: 'Example must have at least one of description, input, or output',
26+
path: ['example'],
27+
})
28+
}
29+
this.description = config.description
30+
this.input = config.input
31+
this.output = config.output
32+
}
33+
}
34+
export { MassargExample }

0 commit comments

Comments
 (0)