Skip to content

Commit

Permalink
feat: example lines, help style updates
Browse files Browse the repository at this point in the history
  • Loading branch information
chenasraf committed Jan 31, 2024
1 parent 214f808 commit c042a34
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 160 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
"scripts": {
"build": "tsc -p tsconfig.build.json && cp package.json README.md build",
"dev": "tsc --watch",
"example": "ts-node src/example.ts",
"cmd": "ts-node src/sample.ts",
"test": "jest",
"docs": "typedoc --out docs src --plugin typedoc-plugin-zod --theme default"
"docgen": "typedoc --out docs src --plugin typedoc-plugin-zod --theme default"
},
"devDependencies": {
"@types/jest": "^29.5.8",
Expand Down
43 changes: 40 additions & 3 deletions src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import MassargOption, {
MassargHelpFlag,
} from './option'
import { setOrPush } from './utils'
import MassargExample, { ExampleConfig } from './example'

export const CommandConfig = <RunArgs extends z.ZodType>(args: RunArgs) =>
z.object({
Expand Down Expand Up @@ -54,8 +55,9 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
description: string
aliases: string[]
private _run?: Runner<Args>
options: MassargOption[] = []
commands: MassargCommand<any>[] = []
options: MassargOption[] = []
examples: MassargExample[] = []
args: Partial<Args> = {}

constructor(options: CommandConfig<Args>) {
Expand All @@ -79,6 +81,14 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
): MassargCommand<Args> {
try {
const command = config instanceof MassargCommand ? config : new MassargCommand(config)
const existing = this.commands.find((c) => c.name === command.name)
if (existing) {
throw new ValidationError({
code: 'duplicate_command',
message: `Command "${command.name}" already exists`,
path: [this.name, command.name],
})
}
this.commands.push(command)
return this
} catch (e) {
Expand All @@ -93,11 +103,23 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
}
}

flag(config: Omit<OptionConfig<boolean>, 'parse'>): MassargCommand<Args>
flag(config: Omit<OptionConfig<boolean>, 'parse' | 'isDefault'>): MassargCommand<Args>
flag(config: MassargFlag): MassargCommand<Args>
flag(config: Omit<OptionConfig<boolean>, 'parse'> | MassargFlag): MassargCommand<Args> {
flag(
config: Omit<OptionConfig<boolean>, 'parse' | 'isDefault'> | MassargFlag,
): MassargCommand<Args> {
try {
const flag = config instanceof MassargFlag ? config : new MassargFlag(config)
if (flag.isDefault) {
const defaultOption = this.options.find((o) => o.isDefault)
if (defaultOption) {
throw new ValidationError({
code: 'duplicate_default_option',
message: `Option "${flag.name}" cannot be set as default because option "${defaultOption.name}" is already set as default`,
path: [this.name, flag.name],
})
}
}
this.options.push(flag as MassargOption)
return this
} catch (e) {
Expand All @@ -118,6 +140,16 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
try {
const option =
config instanceof MassargOption ? config : MassargOption.fromTypedConfig(config)
if (option.isDefault) {
const defaultOption = this.options.find((o) => o.isDefault)
if (defaultOption) {
throw new ValidationError({
code: 'duplicate_default_option',
message: `Option "${option.name}" cannot be set as default because option "${defaultOption.name}" is already set as default`,
path: [this.name, option.name],
})
}
}
this.options.push(option as MassargOption)
return this
} catch (e) {
Expand All @@ -132,6 +164,11 @@ export default class MassargCommand<Args extends ArgsObject = ArgsObject> {
}
}

example(config: ExampleConfig): MassargCommand<Args> {
this.examples.push(new MassargExample(config))
return this
}

main<A extends ArgsObject = Args>(run: Runner<A>): MassargCommand<Args> {
this._run = run
return this
Expand Down
124 changes: 30 additions & 94 deletions src/example.ts
Original file line number Diff line number Diff line change
@@ -1,98 +1,34 @@
import { massarg } from '.'
import MassargCommand from './command'
import { ParseError } from './error'
import z from 'zod'
import { ValidationError } from './error'

type A = { test: boolean }
const echoCmd = massarg<any>({
name: 'echo',
description: 'Echo back the arguments',
aliases: ['e'],
run: (opts) => {
console.log('Echoing back', opts)
},
export const ExampleConfig = z.object({
description: z.string().optional(),
input: z.string().optional(),
output: z.string().optional(),
})
const addCmd = massarg<{ component: string }>({
name: 'add',
description: 'Add a component',
aliases: ['a'],
run: (opts, parser) => {
parser.printHelp()
console.log('Adding component', opts.component)
},
})
.option({
name: 'component',
description:
'Component to add. Ut consectetur eu et occaecat enim magna amet eiusmod laboris deserunt proident culpa nulla ipsum adipiscing ullamco laboris sed est',
aliases: ['c'],
// aliases: "" as never,
})
.option({
name: 'classes',
description: 'Classes to add',
aliases: ['l'],
array: true,
})
.option({
name: 'custom',
description: 'Custom option',
aliases: ['x'],
parse: (value) => {
const asNumber = Number(value)
if (isNaN(asNumber)) {
throw new ParseError({
path: ['custom'],
message: 'Custom option must be a number',
code: 'invalid_number',
})
}
return {
value: asNumber,
half: asNumber / 2,
double: asNumber * 2,
}
},
})

const removeCmd = new MassargCommand<{ component: string }>({
name: 'remove',
description: 'Remove a component',
aliases: ['r'],
run: (opts) => {
console.log('Removing component', opts.component)
},
}).option({
name: 'component',
description: 'Component to remove',
aliases: ['c'],
})

const args = massarg<A>({
name: 'my-cli',
description: 'This is an example CLI',
bindHelpOption: true,
bindHelpCommand: true,
})
.main((opts, parser) => {
console.log('Main command - printing all opts')
console.log(opts, '\n')
parser.printHelp()
})
.command(echoCmd)
.command(addCmd)
.command(removeCmd)
.flag({
name: 'bool',
description: 'Example boolean option',
aliases: ['b'],
})
.option({
name: 'number',
description: 'Example number option',
aliases: ['n'],
type: 'number',
})
export type ExampleConfig = z.infer<typeof ExampleConfig>

// console.log("Opts:", args.getArgs(process.argv.slice(2)), "\n")
export default class MassargExample {
description: string | undefined
input: string | undefined
output: string | undefined

args.parse(process.argv.slice(2))
constructor(config: ExampleConfig) {
ExampleConfig.parse(config)
if (
config.description === undefined &&
config.input === undefined &&
config.output === undefined
) {
throw new ValidationError({
code: 'invalid_example',
message: 'Example must have at least one of description, input, or output',
path: ['example'],
})
}
this.description = config.description
this.input = config.input
this.output = config.output
}
}
export { MassargExample }
Loading

0 comments on commit c042a34

Please sign in to comment.