Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@benev/argv

Package Overview
Dependencies
Maintainers
1
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@benev/argv - npm Package Compare versions

Comparing version 0.3.0 to 0.3.1

s/areas/analysis/utils/analyze-command.ts

2

package.json
{
"name": "@benev/argv",
"version": "0.3.0",
"version": "0.3.1",
"description": "command line argument parser",

@@ -5,0 +5,0 @@ "license": "MIT",

@@ -19,3 +19,3 @@

![](https://imgur.com/Mpsujsv.png)
![](https://imgur.com/aSAP8cb.png)

@@ -71,92 +71,3 @@ ```sh

## 🌳 tree of multiple `commands`
- the `commands` object is a recursive tree with `command` leaves
```ts
const {tree} = cli(process.argv, {
name: "converter",
commands: {
image: command({
args: [],
params: {
quality: param.required(number),
},
}),
media: {
audio: command({
args: [],
params: {
mono: param.required(boolean),
},
}),
video: command({
args: [],
params: {
codec: param.required(string),
},
})
},
},
})
```
### flat strategy
- you get this `tree` object that reflects its shape
```ts
tree.image?.params.quality // 9
tree.media.audio?.mono // false
tree.media.video?.codec // "av1"
```
- all the commands are `undefined` except for the "selected" command
- and yes, all the typings work
### command-execution strategy
- you can choose to provide each command with an async `execute` function
```ts
command({
args: [],
params: {
active: param.required(boolean),
count: param.default(number, "101"),
},
async execute({params}) {
params.active // true
params.count // 101
},
})
```
- your execute function receives fully-typed `args`, `params`, and some more stuff
- if you choose to use this command-execution strategy, then you need to call your cli's final `execute` function
```ts
// 👇 awaiting cli execution
await cli(process.argv, {
name: "pizza",
commands: {
meatlovers: command({
args: [],
params: {
meatiness: param.required(number),
},
async execute({params}) {
console.log(params.meatiness) // 9
},
}),
hawaiian: command({
args: [],
params: {
pineappleyness: param.required(number),
},
async execute({params}) {
console.log(params.pineappleyness) // 8
},
}),
},
}).execute()
// ☝️ calling cli final execute
```
<br/>
## 🧑‍🔧 the handy `helpers`
### `command`, `arg`, and `param`
## 🧑‍🔧 configuring your cli's `args` and `params`
- let's start by making a command

@@ -235,6 +146,8 @@ ```ts

arg("name").optional(string, {help: `
see this multi-line string?
it will be trimmed all nicely on the help page.
`}),
arg("name").optional(string, {
help: `
see this multi-line string?
it will be trimmed all nicely on the help page.
`
}),
],

@@ -291,2 +204,97 @@

## 🌳 tree of multiple `commands`
- the `commands` object is a recursive tree with `command` leaves
```ts
const {tree} = cli(process.argv, {
name: "converter",
commands: {
image: command({
args: [],
params: {
quality: param.required(number),
},
}),
media: {
audio: command({
args: [],
params: {
mono: param.required(boolean),
},
}),
video: command({
args: [],
params: {
codec: param.required(string),
},
})
},
},
})
```
### flat strategy
- you get this `tree` object that reflects its shape
```ts
tree.image?.params.quality // 9
tree.media.audio?.mono // false
tree.media.video?.codec // "av1"
```
- all the commands are `undefined` except for the "selected" command
- and yes, all the typings work
### command-execution strategy
- you can choose to provide each command with an async `execute` function
```ts
command({
args: [],
params: {
active: param.required(boolean),
count: param.default(number, "101"),
},
async execute({params}) {
params.active // true
params.count // 101
},
})
```
- your execute function receives fully-typed `args`, `params`, and some more stuff
- your `execute` function can opt-into pretty-printing errors (with colors) by throwing an `ExecutionError`
```ts
import {ExecutionError, command} from "@benev/argv"
async execute({params}) {
throw new ExecutionError("scary error printed in red!")
}
```
- if you choose to use this command-execution strategy, then you need to call your cli's final `execute` function
```ts
// 👇 awaiting cli execution
await cli(process.argv, {
name: "pizza",
commands: {
meatlovers: command({
args: [],
params: {
meatiness: param.required(number),
},
async execute({params}) {
console.log(params.meatiness) // 9
},
}),
hawaiian: command({
args: [],
params: {
pineappleyness: param.required(number),
},
async execute({params}) {
console.log(params.pineappleyness) // 8
},
}),
},
}).execute()
// ☝️ calling cli final execute
```
<br/>
## 🛠️ custom types

@@ -322,5 +330,45 @@ - i can't believe i got all the types working for everything with custom types

## 🦚 custom themes
- you can set the theme for your --help pages
```ts
import {themes} from
await cli(process.argv, {
// the default theme
theme: themes.standard,
...otherStuff,
}).execute()
```
- maybe try `themes.seaside` for a more chill vibe
- if you hate fun, use `themes.noColor` to disable ansi colors
- make your own theme like this
```ts
import {theme, color} from
const seaside = theme({
plain: [color.white],
error: [color.brightRed, color.bold],
program: [color.brightCyan, color.bold],
command: [color.cyan, color.bold],
property: [color.blue],
link: [color.brightBlue, color.underline],
arg: [color.brightBlue, color.bold],
param: [color.brightBlue, color.bold],
flag: [color.brightBlue],
required: [color.cyan],
mode: [color.blue],
type: [color.brightBlue],
value: [color.cyan],
})
```
<br/>
## 🌠 give me a github star!
- i worked way too hard on this
- please submit issues for any problems or questions
- maybe make a cool help theme and submit a PR for it
import {parse} from "../parsing/parse.js"
import {CommandTree} from "./types/commands.js"
import {analyzeCommand} from "./utils/analyze-command.js"
import {produceTreeAnalysis} from "./utils/analyze-tree.js"
import {Analysis, AnalyzeOptions} from "./types/analysis.js"
import {analyzeCommand, selectCommand, produceTreeAnalysis, getBooleanParams} from "./utils/utils.js"
import {selectCommand, getBooleanParams} from "./utils/utils.js"
import {CommandNotFoundError, UnknownFlagError, UnknownParamError} from "../../errors/kinds/mistakes.js"

@@ -7,0 +9,0 @@

@@ -99,2 +99,3 @@

help, type,
fallback,
mode: "default",

@@ -128,2 +129,3 @@ ingest: ingestors.default({coerce, validate}, fallback),

name, help, type,
fallback,
mode: "default",

@@ -130,0 +132,0 @@ ingest: ingestors.default({coerce, validate}, fallback),

@@ -7,13 +7,12 @@

export function listAllCommands(commands: CommandTree) {
const commandList: Cmd[] = []
const list: Cmd[] = []
function recurse(c: CommandTree, path: string[]): any {
if (c instanceof Command)
commandList.push({path, command: c})
else
for (const [key, c2] of Object.entries(c))
recurse(c2, [...path, key])
list.push({path, command: c})
else for (const [key, c2] of Object.entries(c))
recurse(c2, [...path, key])
}
recurse(commands, [])
return commandList
return list
}
import {parse} from "../../parsing/parse.js"
import {Parsed} from "../../parsing/types.js"
import {MistakeError} from "../../../errors/basic.js"
import {SelectedCommand} from "../types/analysis.js"
import {listAllCommands} from "./list-all-commands.js"
import {Command, CommandTree} from "../types/commands.js"
import {CommandAnalysis, SelectedCommand, TreeAnalysis} from "../types/analysis.js"
export function produceTreeAnalysis<C extends CommandTree>(
commands: C,
command: Command,
commandAnalysis: CommandAnalysis<Command>,
): TreeAnalysis<C> {
function recurse(c: CommandTree, path: string[]): any {
if (c instanceof Command)
return (c === command)
? commandAnalysis
: undefined
else
return Object.fromEntries(
Object.entries(c)
.map(([key, c2]) => [key, recurse(c2, [...path, key])])
)
}
return recurse(commands, [])
export function listRelevantCommands(argw: string[], commands: CommandTree) {
const list = listAllCommands(commands)
const {args} = parse(argw, {booleanParams: ["help"]})
return (args.length === 0)
? list
: list.filter(({path}) => {
const pathFits = path.every((p, i) => p === args[i])
const argsFits = args.every((a, i) => a === path[i])
return pathFits || argsFits
})
}

@@ -46,46 +36,2 @@

export function analyzeCommand(
path: string[],
command: Command,
parsed: Parsed,
): CommandAnalysis<Command> {
function handleError<T>(subject: string, name: string, fn: () => T) {
try { return fn() }
catch (error) {
const message = (error instanceof Error)
? error.message
: "unknown error"
throw new MistakeError(`${subject} "${name}": ${message}`)
}
}
const args = Object.fromEntries(
command.args.map((arg, index) => {
const input = parsed.args.at(index)
return handleError("arg", arg.name, () =>
[arg.name, arg.ingest(input)]
)
})
)
const params = Object.fromEntries(
Object.entries(command.params)
.map(([name, param]) => {
if (param.flag && parsed.flags.has(param.flag))
return [name, true]
const input = parsed.params.get(name)
return handleError("param", name, () =>
[name, param.ingest(input)]
)
})
)
const extraArgs = (parsed.args.length > command.args.length)
? parsed.args.slice(command.args.length)
: []
return {path, args, params, extraArgs}
}
export function getBooleanParams(command: Command) {

@@ -101,4 +47,3 @@ return Object.entries(command.params)

///////////////////////////
///////////////////////////
//////////////////

@@ -105,0 +50,0 @@ function isCommandMatching(argw: string[], path: string[]) {

import {cli} from "./cli.js"
import {argv} from "../../testing/argv.js"
import {FakeExit} from "../../errors/basic.js"
import {ExitFail} from "../../errors/basic.js"
import {CliConfig, cliConfig} from "./types.js"

@@ -27,3 +27,3 @@ import {expect} from "../../testing/framework/expect.js"

catch (error) {
if (error instanceof FakeExit)
if (error instanceof ExitFail)
return data

@@ -30,0 +30,0 @@ else

import {themes} from "./themes.js"
import {analyze} from "../analysis/analyze.js"
import {CliConfig, CliResult} from "./types.js"
import {printHelp} from "./printing/print-help.js"
import {checkHelp} from "../parsing/check-help.js"
import {printHelp} from "./printing/print-help.js"
import {Tn, tnFinal} from "../../tooling/text/tn.js"
import {printError} from "./printing/print-error.js"
import {selectCommand} from "../analysis/utils/utils.js"
import {FakeExit, MistakeError} from "../../errors/basic.js"
import {Command, CommandTree} from "../analysis/types/commands.js"
import {tnFinal} from "../../tooling/text/tn.js"
import {makePalette} from "../../tooling/text/theming.js"
import {CommandTree} from "../analysis/types/commands.js"
import {CommandNotFoundError} from "../../errors/kinds/mistakes.js"
import {ArgvError, ExitFail, MistakeError} from "../../errors/basic.js"
import {listRelevantCommands, selectCommand} from "../analysis/utils/utils.js"

@@ -28,2 +30,3 @@ /**

indent = " ",
theme = themes.standard,
columns = process.stdout.columns,

@@ -35,23 +38,38 @@ onExit = code => process.exit(code),

const format = (tn: Tn) => tnFinal(columns, indent, tn)
const palette = makePalette(theme)
const exit = (code: number) => {
onExit(code)
throw new ExitFail("cli 'onExit' failed to end process")
}
try {
const userAskForHelp = checkHelp(argw)
const userAskedForHelp = checkHelp(argw)
const selectedCommand = selectCommand(argw, commands)
const singleRootCommand = commands instanceof Command
const relevantCommands = listRelevantCommands(argw, commands)
const userNeedsHelp = !selectedCommand
const userNeedsHelp = !singleRootCommand
&& !selectedCommand
&& argw.length === 0
if (relevantCommands.length === 0)
throw new CommandNotFoundError()
if (userAskForHelp || userNeedsHelp) {
const help = format(
printHelp({...config, commands, selectedCommand})
) + "\n"
onHelp(help)
onExit(0)
if (userAskedForHelp || userNeedsHelp) {
const help = printHelp({...config, selectedCommand, relevantCommands, palette})
const formatted = tnFinal(columns, indent, help)
onHelp(formatted + "\n")
return exit(0)
}
const analysis = analyze(argw, {commands})
const execute = () => analysis.commandSpec.execute(analysis.command)
const execute = async() => {
try {
return await analysis.commandSpec.execute(analysis.command)
}
catch (error) {
if (error instanceof ArgvError) {
console.error(palette.error(error.message))
return exit(1)
}
else throw error
}
}

@@ -67,14 +85,12 @@ return {

if (error instanceof MistakeError) {
const mistake = format(printError(error))
onMistake(mistake)
onExit(1)
onMistake(
error.message
? palette.error(error.message)
: error.name
)
return exit(1)
}
else throw error
}
// if the user-supplied `onExit` fails to actually end the process,
// we need to throw an error, so that typescript sees that this function
// will never return undefined.
throw new FakeExit("cli 'exit' failed to end process")
}
import {ArgvTheme} from "../themes.js"
import {Palette} from "../../../tooling/text/theming.js"
import {Command} from "../../analysis/types/commands.js"
import {Args, Params} from "../../analysis/types/units.js"
import {Cmd} from "../../analysis/utils/list-all-commands.js"
import {makePalette} from "../../../tooling/text/coloring.js"
import {normalize} from "../../../tooling/text/formatting.js"
import {tnConnect, tnIndent} from "../../../tooling/text/tn.js"
export function helpWiz(theme: ArgvTheme) {
const palette = makePalette(theme)
export function helpWiz(palette: Palette<ArgvTheme>) {
function commandHeadline(programName: string, {command, path}: Cmd) {
function commandHeadline(
programName: string,
{command, path}: Cmd,
summarize: boolean,
) {
return tnConnect(" ", [

@@ -26,9 +29,11 @@

.map(arg => arg.name)
.map(n => palette.arg(`<${n}>`))
.map(n => palette.arg(`${n}`))
.join(" "),
// params
Object.keys(command.params).length === 0
? null
: palette.param(`{params}`),
summarize
? palette.param(`--help`)
: Object.keys(command.params).length === 0
? null
: palette.param(`{params}`),
])

@@ -39,3 +44,3 @@ }

return command.help
&& normalize(command.help)
&& palette.plain(normalize(command.help))
}

@@ -67,3 +72,3 @@

arg.help
&& tnIndent(1, normalize(arg.help)),
&& tnIndent(1, palette.plain(normalize(arg.help))),
])))

@@ -101,3 +106,3 @@ }

param.help
&& tnIndent(1, normalize(param.help)),
&& tnIndent(1, palette.plain(normalize(param.help))),
]))

@@ -118,3 +123,3 @@ )

readme && `${palette.property("readme")} ${palette.link(readme.trim())}`,
help && normalize(help),
help && palette.plain(normalize(help)),
])

@@ -121,0 +126,0 @@ }

import {themes} from "../themes.js"
import {ArgvTheme} from "../themes.js"
import {helpWiz} from "./help-wiz.js"
import {CliConfig} from "../types.js"
import {Palette} from "../../../tooling/text/theming.js"
import {tnConnect, tnIndent} from "../../../tooling/text/tn.js"
import {SelectedCommand} from "../../analysis/types/analysis.js"
import {Command, CommandTree} from "../../analysis/types/commands.js"
import {listAllCommands} from "../../analysis/utils/list-all-commands.js"
import {Cmd, listAllCommands} from "../../analysis/utils/list-all-commands.js"
export function printHelp({
readme,
palette,
commands,
selectedCommand,
relevantCommands,
name: programName,
help: programHelp,
theme = themes.standard,
summarize = true,
}: {
commands: CommandTree
relevantCommands: Cmd[]
palette: Palette<ArgvTheme>
selectedCommand: SelectedCommand | undefined
} & CliConfig<CommandTree>) {
const wiz = helpWiz(theme)
const wiz = helpWiz(palette)
const commandList = listAllCommands(commands)
const singleRootCommand = commands instanceof Command
// general help, this cli has one single command
// this cli has one root command
if (singleRootCommand) {

@@ -32,3 +37,3 @@ const selectedCommand = commandList[0]

tnConnect("\n", [
wiz.commandHeadline(programName, selectedCommand),
wiz.commandHeadline(programName, selectedCommand, false),
tnIndent(1, tnConnect("\n", [

@@ -46,3 +51,3 @@ wiz.programHelp(programHelp, readme),

// user is asking for help about a specific command
// user asks about one specific command
else if (selectedCommand) {

@@ -52,3 +57,3 @@ const {command} = selectedCommand

tnConnect("\n", [
wiz.commandHeadline(programName, selectedCommand),
wiz.commandHeadline(programName, selectedCommand, false),
tnIndent(1, wiz.commandHelp(command)),

@@ -63,23 +68,45 @@ ]),

// general help, this cli has multiple commands
else {
// help home page, multiple commands are available
else if (relevantCommands.length === commandList.length) {
const actuallySummarize = summarize && relevantCommands.length > 1
return tnConnect("\n\n", [
tnConnect("\n", [
wiz.programHeadline(programName, commandList),
wiz.programHeadline(programName, relevantCommands),
tnIndent(1, wiz.programHelp(programHelp))
]),
...commandList
...relevantCommands
.map(cmd => tnIndent(1, tnConnect("\n\n", [
tnConnect("\n", [
wiz.commandHeadline(programName, cmd),
wiz.commandHeadline(programName, cmd, actuallySummarize),
tnIndent(1, wiz.commandHelp(cmd.command)),
]),
tnIndent(1, tnConnect("\n\n", [
actuallySummarize
? null
: tnIndent(1, tnConnect("\n\n", [
wiz.commandArgs(cmd.command.args),
wiz.commandParams(cmd.command.params),
])),
])))
])
}
// a subset of commands
else {
const actuallySummarize = summarize && relevantCommands.length > 1
return tnConnect("\n\n", relevantCommands
.map(cmd => tnConnect("\n\n", [
tnConnect("\n", [
wiz.commandHeadline(programName, cmd, actuallySummarize),
tnIndent(1, wiz.commandHelp(cmd.command)),
]),
actuallySummarize
? null
: tnIndent(1, tnConnect("\n\n", [
wiz.commandArgs(cmd.command.args),
wiz.commandParams(cmd.command.params),
])),
])))
])
]))
)
}
}
import {color, colorHex} from "../../tooling/text/coloring.js"
import {Theme} from "../../tooling/text/theming.js"
import {color} from "../../tooling/text/coloring.js"
//
// theme type
//
export type ArgvTheme = typeof standard
//
// standard theme (default)

@@ -15,5 +10,7 @@ //

const standard = {
plain: [color.white],
error: [color.brightRed, color.bold],
program: [color.brightCyan, color.bold],
command: [color.cyan, color.bold],
property: [color.magenta, color.bold],
property: [color.magenta],
link: [color.brightBlue, color.underline],

@@ -25,6 +22,21 @@ arg: [color.brightGreen, color.bold],

mode: [color.blue],
type: [color.magenta],
value: [color.blue],
type: [color.brightBlue, color.bold],
value: [color.magenta],
} satisfies Theme
//
// theme type
//
export type ArgvTheme = typeof standard
export function asTheme<T extends ArgvTheme>(t: T) {
return t
}
export function asThemes<T extends Record<string, ArgvTheme>>(t: T) {
return t
}
//

@@ -34,4 +46,3 @@ // more themes!

export const themes = {
export const themes = asThemes({
standard,

@@ -44,10 +55,18 @@

// todo
dracula: {
...standard,
program: [colorHex("#80f"), color.bold],
command: [color.blue, color.bold],
seaside: {
plain: [color.white],
error: [color.brightRed, color.bold],
program: [color.brightCyan, color.bold],
command: [color.cyan, color.bold],
property: [color.blue],
link: [color.brightBlue, color.underline],
arg: [color.brightBlue, color.bold],
param: [color.brightBlue, color.bold],
flag: [color.brightBlue],
required: [color.cyan],
mode: [color.blue],
type: [color.brightBlue],
value: [color.cyan],
},
})
} satisfies Record<string, ArgvTheme>

@@ -14,2 +14,3 @@

theme?: ArgvTheme
summarize?: boolean
onExit?: (code: number) => void

@@ -16,0 +17,0 @@ onHelp?: (help: string) => void

@@ -14,3 +14,7 @@

/** developer error configuring argv. */
export class ConfigError extends ArgvError {}
export class ConfigError extends ArgvError {
constructor(message: string) {
super(`(Argv Config Error) ${message}`)
}
}

@@ -24,5 +28,8 @@ /** user error in supplying command line inputs. */

/** cli is expeceted to throw this during testing scenarios. */
export class FakeExit extends ArgvError {}
/** devs should throw this error in an `execute` function, if they want argv to pretty-print the error. */
export class ExecutionError extends ArgvError {}
/** happens when user-provided onExit doesn't actually end the process (also useful in testing). */
export class ExitFail extends ArgvError {}
// i just think this is fun

@@ -29,0 +36,0 @@ function pleaseSeekHelp() {

#!/usr/bin/env node
import {cli} from "../areas/cli/cli.js"
import {themes} from "../areas/cli/themes.js"
import {arg, choice, command, number, param, string} from "../areas/analysis/helpers.js"

@@ -5,0 +6,0 @@

#!/usr/bin/env node
import {cli} from "../areas/cli/cli.js"
import {themes} from "../areas/cli/themes.js"
import {arg, choice, command, param, type} from "../areas/analysis/helpers.js"

@@ -5,0 +6,0 @@

@@ -0,3 +1,4 @@

#!/usr/bin/env node
console.log(process.argv)

@@ -87,11 +87,14 @@

export const color = <{[key in keyof typeof codes]: (s: string) => string}>(
Object.fromEntries(
Object.entries(codes)
.map(([key, code]) => [
key,
(s: string) => `${code}${s}${codes.reset}`,
])
)
)
export const color = {
none: (s: string) => s,
...<{[key in keyof typeof codes]: (s: string) => string}>(
Object.fromEntries(
Object.entries(codes)
.map(([key, code]) => [
key,
(s: string) => `${code}${s}${codes.reset}`,
])
)
),
}

@@ -105,21 +108,1 @@ export function uncolor(s: string) {

export type Theme = Record<string, ThemeFns>
export type ThemeFns = ((s: string) => string)[]
export function theme<T extends Theme>(t: T) {
return t
}
export function makePalette<T extends Theme>(theme: T) {
return Object.fromEntries(
Object.entries(theme).map(([key, fns]) => [
key,
(s: string) => {
for (const fn of fns)
s = fn(s)
return s
},
])
) as {[K in keyof T]: ((s: string) => string)}
}
import { parse } from "../parsing/parse.js";
import { analyzeCommand, selectCommand, produceTreeAnalysis, getBooleanParams } from "./utils/utils.js";
import { analyzeCommand } from "./utils/analyze-command.js";
import { produceTreeAnalysis } from "./utils/analyze-tree.js";
import { selectCommand, getBooleanParams } from "./utils/utils.js";
import { CommandNotFoundError, UnknownFlagError, UnknownParamError } from "../../errors/kinds/mistakes.js";

@@ -4,0 +6,0 @@ /**

@@ -76,2 +76,3 @@ import { obmap } from "../../tooling/obmap.js";

help, type,
fallback,
mode: "default",

@@ -94,2 +95,3 @@ ingest: ingestors.default({ coerce, validate }, fallback),

name, help, type,
fallback,
mode: "default",

@@ -96,0 +98,0 @@ ingest: ingestors.default({ coerce, validate }, fallback),

import { Command } from "../types/commands.js";
export function listAllCommands(commands) {
const commandList = [];
const list = [];
function recurse(c, path) {
if (c instanceof Command)
commandList.push({ path, command: c });
list.push({ path, command: c });
else

@@ -12,4 +12,4 @@ for (const [key, c2] of Object.entries(c))

recurse(commands, []);
return commandList;
return list;
}
//# sourceMappingURL=list-all-commands.js.map

@@ -1,7 +0,5 @@

import { Parsed } from "../../parsing/types.js";
import { SelectedCommand } from "../types/analysis.js";
import { Command, CommandTree } from "../types/commands.js";
import { CommandAnalysis, SelectedCommand, TreeAnalysis } from "../types/analysis.js";
export declare function produceTreeAnalysis<C extends CommandTree>(commands: C, command: Command, commandAnalysis: CommandAnalysis<Command>): TreeAnalysis<C>;
export declare function listRelevantCommands(argw: string[], commands: CommandTree): import("./list-all-commands.js").Cmd[];
export declare function selectCommand(argw: string[], commands: CommandTree): SelectedCommand | undefined;
export declare function analyzeCommand(path: string[], command: Command, parsed: Parsed): CommandAnalysis<Command>;
export declare function getBooleanParams(command: Command): string[];
import { parse } from "../../parsing/parse.js";
import { MistakeError } from "../../../errors/basic.js";
import { listAllCommands } from "./list-all-commands.js";
import { Command } from "../types/commands.js";
export function produceTreeAnalysis(commands, command, commandAnalysis) {
function recurse(c, path) {
if (c instanceof Command)
return (c === command)
? commandAnalysis
: undefined;
else
return Object.fromEntries(Object.entries(c)
.map(([key, c2]) => [key, recurse(c2, [...path, key])]));
}
return recurse(commands, []);
export function listRelevantCommands(argw, commands) {
const list = listAllCommands(commands);
const { args } = parse(argw, { booleanParams: ["help"] });
return (args.length === 0)
? list
: list.filter(({ path }) => {
const pathFits = path.every((p, i) => p === args[i]);
const argsFits = args.every((a, i) => a === path[i]);
return pathFits || argsFits;
});
}

@@ -33,30 +32,2 @@ export function selectCommand(argw, commands) {

}
export function analyzeCommand(path, command, parsed) {
function handleError(subject, name, fn) {
try {
return fn();
}
catch (error) {
const message = (error instanceof Error)
? error.message
: "unknown error";
throw new MistakeError(`${subject} "${name}": ${message}`);
}
}
const args = Object.fromEntries(command.args.map((arg, index) => {
const input = parsed.args.at(index);
return handleError("arg", arg.name, () => [arg.name, arg.ingest(input)]);
}));
const params = Object.fromEntries(Object.entries(command.params)
.map(([name, param]) => {
if (param.flag && parsed.flags.has(param.flag))
return [name, true];
const input = parsed.params.get(name);
return handleError("param", name, () => [name, param.ingest(input)]);
}));
const extraArgs = (parsed.args.length > command.args.length)
? parsed.args.slice(command.args.length)
: [];
return { path, args, params, extraArgs };
}
export function getBooleanParams(command) {

@@ -69,4 +40,3 @@ return Object.entries(command.params)

}
///////////////////////////
///////////////////////////
//////////////////
function isCommandMatching(argw, path) {

@@ -73,0 +43,0 @@ const { args } = parse(argw, { booleanParams: ["help"] });

@@ -0,9 +1,10 @@

import { themes } from "./themes.js";
import { analyze } from "../analysis/analyze.js";
import { printHelp } from "./printing/print-help.js";
import { checkHelp } from "../parsing/check-help.js";
import { printHelp } from "./printing/print-help.js";
import { tnFinal } from "../../tooling/text/tn.js";
import { printError } from "./printing/print-error.js";
import { selectCommand } from "../analysis/utils/utils.js";
import { FakeExit, MistakeError } from "../../errors/basic.js";
import { Command } from "../analysis/types/commands.js";
import { makePalette } from "../../tooling/text/theming.js";
import { CommandNotFoundError } from "../../errors/kinds/mistakes.js";
import { ArgvError, ExitFail, MistakeError } from "../../errors/basic.js";
import { listRelevantCommands, selectCommand } from "../analysis/utils/utils.js";
/**

@@ -17,18 +18,35 @@ * read input for a command line program.

const [bin, script, ...argw] = argv;
const { commands, indent = " ", columns = process.stdout.columns, onExit = code => process.exit(code), onHelp = help => console.log(help), onMistake = mistake => console.error(mistake), } = config;
const format = (tn) => tnFinal(columns, indent, tn);
const { commands, indent = " ", theme = themes.standard, columns = process.stdout.columns, onExit = code => process.exit(code), onHelp = help => console.log(help), onMistake = mistake => console.error(mistake), } = config;
const palette = makePalette(theme);
const exit = (code) => {
onExit(code);
throw new ExitFail("cli 'onExit' failed to end process");
};
try {
const userAskForHelp = checkHelp(argw);
const userAskedForHelp = checkHelp(argw);
const selectedCommand = selectCommand(argw, commands);
const singleRootCommand = commands instanceof Command;
const userNeedsHelp = !singleRootCommand
&& !selectedCommand
&& argw.length === 0;
if (userAskForHelp || userNeedsHelp) {
const help = format(printHelp({ ...config, commands, selectedCommand })) + "\n";
onHelp(help);
onExit(0);
const relevantCommands = listRelevantCommands(argw, commands);
const userNeedsHelp = !selectedCommand;
if (relevantCommands.length === 0)
throw new CommandNotFoundError();
if (userAskedForHelp || userNeedsHelp) {
const help = printHelp({ ...config, selectedCommand, relevantCommands, palette });
const formatted = tnFinal(columns, indent, help);
onHelp(formatted + "\n");
return exit(0);
}
const analysis = analyze(argw, { commands });
const execute = () => analysis.commandSpec.execute(analysis.command);
const execute = async () => {
try {
return await analysis.commandSpec.execute(analysis.command);
}
catch (error) {
if (error instanceof ArgvError) {
console.error(palette.error(error.message));
return exit(1);
}
else
throw error;
}
};
return {

@@ -43,5 +61,6 @@ ...analysis,

if (error instanceof MistakeError) {
const mistake = format(printError(error));
onMistake(mistake);
onExit(1);
onMistake(error.message
? palette.error(error.message)
: error.name);
return exit(1);
}

@@ -51,7 +70,3 @@ else

}
// if the user-supplied `onExit` fails to actually end the process,
// we need to throw an error, so that typescript sees that this function
// will never return undefined.
throw new FakeExit("cli 'exit' failed to end process");
}
//# sourceMappingURL=cli.js.map
import { cli } from "./cli.js";
import { argv } from "../../testing/argv.js";
import { FakeExit } from "../../errors/basic.js";
import { ExitFail } from "../../errors/basic.js";
import { cliConfig } from "./types.js";

@@ -24,3 +24,3 @@ import { expect } from "../../testing/framework/expect.js";

catch (error) {
if (error instanceof FakeExit)
if (error instanceof ExitFail)
return data;

@@ -27,0 +27,0 @@ else

import { ArgvTheme } from "../themes.js";
import { Palette } from "../../../tooling/text/theming.js";
import { Command } from "../../analysis/types/commands.js";
import { Args, Params } from "../../analysis/types/units.js";
import { Cmd } from "../../analysis/utils/list-all-commands.js";
export declare function helpWiz(theme: ArgvTheme): {
export declare function helpWiz(palette: Palette<ArgvTheme>): {
programHeadline: (name: string, commandList: Cmd[]) => string | null;
programHelp: (help?: string, readme?: string) => string | null;
commandHeadline: (programName: string, { command, path }: Cmd) => string | null;
commandHeadline: (programName: string, { command, path }: Cmd, summarize: boolean) => string | null;
commandHelp: (command: Command) => string | undefined;

@@ -10,0 +11,0 @@ commandArgs: (args: Args) => string | null;

@@ -1,7 +0,5 @@

import { makePalette } from "../../../tooling/text/coloring.js";
import { normalize } from "../../../tooling/text/formatting.js";
import { tnConnect, tnIndent } from "../../../tooling/text/tn.js";
export function helpWiz(theme) {
const palette = makePalette(theme);
function commandHeadline(programName, { command, path }) {
export function helpWiz(palette) {
function commandHeadline(programName, { command, path }, summarize) {
return tnConnect(" ", [

@@ -16,8 +14,10 @@ // program name

.map(arg => arg.name)
.map(n => palette.arg(`<${n}>`))
.map(n => palette.arg(`${n}`))
.join(" "),
// params
Object.keys(command.params).length === 0
? null
: palette.param(`{params}`),
summarize
? palette.param(`--help`)
: Object.keys(command.params).length === 0
? null
: palette.param(`{params}`),
]);

@@ -27,3 +27,3 @@ }

return command.help
&& normalize(command.help);
&& palette.plain(normalize(command.help));
}

@@ -48,3 +48,3 @@ function commandArgs(args) {

arg.help
&& tnIndent(1, normalize(arg.help)),
&& tnIndent(1, palette.plain(normalize(arg.help))),
])));

@@ -74,3 +74,3 @@ }

param.help
&& tnIndent(1, normalize(param.help)),
&& tnIndent(1, palette.plain(normalize(param.help))),
])));

@@ -88,3 +88,3 @@ }

readme && `${palette.property("readme")} ${palette.link(readme.trim())}`,
help && normalize(help),
help && palette.plain(normalize(help)),
]);

@@ -91,0 +91,0 @@ }

@@ -0,7 +1,12 @@

import { ArgvTheme } from "../themes.js";
import { CliConfig } from "../types.js";
import { Palette } from "../../../tooling/text/theming.js";
import { SelectedCommand } from "../../analysis/types/analysis.js";
import { CommandTree } from "../../analysis/types/commands.js";
export declare function printHelp({ readme, commands, selectedCommand, name: programName, help: programHelp, theme, }: {
import { Cmd } from "../../analysis/utils/list-all-commands.js";
export declare function printHelp({ readme, palette, commands, selectedCommand, relevantCommands, name: programName, help: programHelp, summarize, }: {
commands: CommandTree;
relevantCommands: Cmd[];
palette: Palette<ArgvTheme>;
selectedCommand: SelectedCommand | undefined;
} & CliConfig<CommandTree>): string | null;

@@ -1,2 +0,1 @@

import { themes } from "../themes.js";
import { helpWiz } from "./help-wiz.js";

@@ -6,7 +5,7 @@ import { tnConnect, tnIndent } from "../../../tooling/text/tn.js";

import { listAllCommands } from "../../analysis/utils/list-all-commands.js";
export function printHelp({ readme, commands, selectedCommand, name: programName, help: programHelp, theme = themes.standard, }) {
const wiz = helpWiz(theme);
export function printHelp({ readme, palette, commands, selectedCommand, relevantCommands, name: programName, help: programHelp, summarize = true, }) {
const wiz = helpWiz(palette);
const commandList = listAllCommands(commands);
const singleRootCommand = commands instanceof Command;
// general help, this cli has one single command
// this cli has one root command
if (singleRootCommand) {

@@ -17,3 +16,3 @@ const selectedCommand = commandList[0];

tnConnect("\n", [
wiz.commandHeadline(programName, selectedCommand),
wiz.commandHeadline(programName, selectedCommand, false),
tnIndent(1, tnConnect("\n", [

@@ -30,3 +29,3 @@ wiz.programHelp(programHelp, readme),

}
// user is asking for help about a specific command
// user asks about one specific command
else if (selectedCommand) {

@@ -36,3 +35,3 @@ const { command } = selectedCommand;

tnConnect("\n", [
wiz.commandHeadline(programName, selectedCommand),
wiz.commandHeadline(programName, selectedCommand, false),
tnIndent(1, wiz.commandHelp(command)),

@@ -46,23 +45,43 @@ ]),

}
// general help, this cli has multiple commands
else {
// help home page, multiple commands are available
else if (relevantCommands.length === commandList.length) {
const actuallySummarize = summarize && relevantCommands.length > 1;
return tnConnect("\n\n", [
tnConnect("\n", [
wiz.programHeadline(programName, commandList),
wiz.programHeadline(programName, relevantCommands),
tnIndent(1, wiz.programHelp(programHelp))
]),
...commandList
...relevantCommands
.map(cmd => tnIndent(1, tnConnect("\n\n", [
tnConnect("\n", [
wiz.commandHeadline(programName, cmd),
wiz.commandHeadline(programName, cmd, actuallySummarize),
tnIndent(1, wiz.commandHelp(cmd.command)),
]),
tnIndent(1, tnConnect("\n\n", [
actuallySummarize
? null
: tnIndent(1, tnConnect("\n\n", [
wiz.commandArgs(cmd.command.args),
wiz.commandParams(cmd.command.params),
])),
])))
]);
}
// a subset of commands
else {
const actuallySummarize = summarize && relevantCommands.length > 1;
return tnConnect("\n\n", relevantCommands
.map(cmd => tnConnect("\n\n", [
tnConnect("\n", [
wiz.commandHeadline(programName, cmd, actuallySummarize),
tnIndent(1, wiz.commandHelp(cmd.command)),
]),
actuallySummarize
? null
: tnIndent(1, tnConnect("\n\n", [
wiz.commandArgs(cmd.command.args),
wiz.commandParams(cmd.command.params),
])),
])))
]);
])));
}
}
//# sourceMappingURL=print-help.js.map

@@ -1,3 +0,4 @@

export type ArgvTheme = typeof standard;
declare const standard: {
plain: ((s: string) => string)[];
error: ((s: string) => string)[];
program: ((s: string) => string)[];

@@ -15,4 +16,9 @@ command: ((s: string) => string)[];

};
export type ArgvTheme = typeof standard;
export declare function asTheme<T extends ArgvTheme>(t: T): T;
export declare function asThemes<T extends Record<string, ArgvTheme>>(t: T): T;
export declare const themes: {
standard: {
plain: ((s: string) => string)[];
error: ((s: string) => string)[];
program: ((s: string) => string)[];

@@ -31,2 +37,4 @@ command: ((s: string) => string)[];

noColor: {
plain: ((s: string) => string)[];
error: ((s: string) => string)[];
program: ((s: string) => string)[];

@@ -44,3 +52,5 @@ command: ((s: string) => string)[];

};
dracula: {
seaside: {
plain: ((s: string) => string)[];
error: ((s: string) => string)[];
program: ((s: string) => string)[];

@@ -47,0 +57,0 @@ command: ((s: string) => string)[];

@@ -1,2 +0,2 @@

import { color, colorHex } from "../../tooling/text/coloring.js";
import { color } from "../../tooling/text/coloring.js";
//

@@ -6,5 +6,7 @@ // standard theme (default)

const standard = {
plain: [color.white],
error: [color.brightRed, color.bold],
program: [color.brightCyan, color.bold],
command: [color.cyan, color.bold],
property: [color.magenta, color.bold],
property: [color.magenta],
link: [color.brightBlue, color.underline],

@@ -16,19 +18,34 @@ arg: [color.brightGreen, color.bold],

mode: [color.blue],
type: [color.magenta],
value: [color.blue],
type: [color.brightBlue, color.bold],
value: [color.magenta],
};
export function asTheme(t) {
return t;
}
export function asThemes(t) {
return t;
}
//
// more themes!
//
export const themes = {
export const themes = asThemes({
standard,
noColor: Object.fromEntries(Object.entries(standard)
.map(([key]) => [key, [(s) => s]])),
// todo
dracula: {
...standard,
program: [colorHex("#80f"), color.bold],
command: [color.blue, color.bold],
seaside: {
plain: [color.white],
error: [color.brightRed, color.bold],
program: [color.brightCyan, color.bold],
command: [color.cyan, color.bold],
property: [color.blue],
link: [color.brightBlue, color.underline],
arg: [color.brightBlue, color.bold],
param: [color.brightBlue, color.bold],
flag: [color.brightBlue],
required: [color.cyan],
mode: [color.blue],
type: [color.brightBlue],
value: [color.cyan],
},
};
});
//# sourceMappingURL=themes.js.map

@@ -12,2 +12,3 @@ import { ArgvTheme } from "./themes.js";

theme?: ArgvTheme;
summarize?: boolean;
onExit?: (code: number) => void;

@@ -14,0 +15,0 @@ onHelp?: (help: string) => void;

@@ -10,2 +10,3 @@ /**

export declare class ConfigError extends ArgvError {
constructor(message: string);
}

@@ -16,4 +17,7 @@ /** user error in supplying command line inputs. */

}
/** cli is expeceted to throw this during testing scenarios. */
export declare class FakeExit extends ArgvError {
/** devs should throw this error in an `execute` function, if they want argv to pretty-print the error. */
export declare class ExecutionError extends ArgvError {
}
/** happens when user-provided onExit doesn't actually end the process (also useful in testing). */
export declare class ExitFail extends ArgvError {
}

@@ -13,2 +13,5 @@ /**

export class ConfigError extends ArgvError {
constructor(message) {
super(`(Argv Config Error) ${message}`);
}
}

@@ -21,5 +24,8 @@ /** user error in supplying command line inputs. */

}
/** cli is expeceted to throw this during testing scenarios. */
export class FakeExit extends ArgvError {
/** devs should throw this error in an `execute` function, if they want argv to pretty-print the error. */
export class ExecutionError extends ArgvError {
}
/** happens when user-provided onExit doesn't actually end the process (also useful in testing). */
export class ExitFail extends ArgvError {
}
// i just think this is fun

@@ -26,0 +32,0 @@ function pleaseSeekHelp() {

@@ -0,1 +1,2 @@

#!/usr/bin/env node
export {};

@@ -0,3 +1,4 @@

#!/usr/bin/env node
console.log(process.argv);
export {};
//# sourceMappingURL=echo.js.map

@@ -5,47 +5,44 @@ export declare function colorHex(hex: string): (s: string) => string;

export declare const color: {
readonly black: (s: string) => string;
readonly red: (s: string) => string;
readonly green: (s: string) => string;
readonly yellow: (s: string) => string;
readonly blue: (s: string) => string;
readonly magenta: (s: string) => string;
readonly cyan: (s: string) => string;
readonly white: (s: string) => string;
readonly brightBlack: (s: string) => string;
readonly brightRed: (s: string) => string;
readonly brightGreen: (s: string) => string;
readonly brightYellow: (s: string) => string;
readonly brightBlue: (s: string) => string;
readonly brightMagenta: (s: string) => string;
readonly brightCyan: (s: string) => string;
readonly brightWhite: (s: string) => string;
readonly bgBlack: (s: string) => string;
readonly bgRed: (s: string) => string;
readonly bgGreen: (s: string) => string;
readonly bgYellow: (s: string) => string;
readonly bgBlue: (s: string) => string;
readonly bgMagenta: (s: string) => string;
readonly bgCyan: (s: string) => string;
readonly bgWhite: (s: string) => string;
readonly bgBrightBlack: (s: string) => string;
readonly bgBrightRed: (s: string) => string;
readonly bgBrightGreen: (s: string) => string;
readonly bgBrightYellow: (s: string) => string;
readonly bgBrightBlue: (s: string) => string;
readonly bgBrightMagenta: (s: string) => string;
readonly bgBrightCyan: (s: string) => string;
readonly bgBrightWhite: (s: string) => string;
readonly bold: (s: string) => string;
readonly dim: (s: string) => string;
readonly italic: (s: string) => string;
readonly underline: (s: string) => string;
readonly inverse: (s: string) => string;
readonly hidden: (s: string) => string;
readonly strikethrough: (s: string) => string;
readonly reset: (s: string) => string;
black: (s: string) => string;
red: (s: string) => string;
green: (s: string) => string;
yellow: (s: string) => string;
blue: (s: string) => string;
magenta: (s: string) => string;
cyan: (s: string) => string;
white: (s: string) => string;
brightBlack: (s: string) => string;
brightRed: (s: string) => string;
brightGreen: (s: string) => string;
brightYellow: (s: string) => string;
brightBlue: (s: string) => string;
brightMagenta: (s: string) => string;
brightCyan: (s: string) => string;
brightWhite: (s: string) => string;
bgBlack: (s: string) => string;
bgRed: (s: string) => string;
bgGreen: (s: string) => string;
bgYellow: (s: string) => string;
bgBlue: (s: string) => string;
bgMagenta: (s: string) => string;
bgCyan: (s: string) => string;
bgWhite: (s: string) => string;
bgBrightBlack: (s: string) => string;
bgBrightRed: (s: string) => string;
bgBrightGreen: (s: string) => string;
bgBrightYellow: (s: string) => string;
bgBrightBlue: (s: string) => string;
bgBrightMagenta: (s: string) => string;
bgBrightCyan: (s: string) => string;
bgBrightWhite: (s: string) => string;
bold: (s: string) => string;
dim: (s: string) => string;
italic: (s: string) => string;
underline: (s: string) => string;
inverse: (s: string) => string;
hidden: (s: string) => string;
strikethrough: (s: string) => string;
reset: (s: string) => string;
none: (s: string) => string;
};
export declare function uncolor(s: string): string;
export type Theme = Record<string, ThemeFns>;
export type ThemeFns = ((s: string) => string)[];
export declare function theme<T extends Theme>(t: T): T;
export declare function makePalette<T extends Theme>(theme: T): { [K in keyof T]: (s: string) => string; };

@@ -74,23 +74,13 @@ const codes = Object.freeze({

}
export const color = (Object.fromEntries(Object.entries(codes)
.map(([key, code]) => [
key,
(s) => `${code}${s}${codes.reset}`,
])));
export const color = {
none: (s) => s,
...(Object.fromEntries(Object.entries(codes)
.map(([key, code]) => [
key,
(s) => `${code}${s}${codes.reset}`,
]))),
};
export function uncolor(s) {
return s.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
}
export function theme(t) {
return t;
}
export function makePalette(theme) {
return Object.fromEntries(Object.entries(theme).map(([key, fns]) => [
key,
(s) => {
for (const fn of fns)
s = fn(s);
return s;
},
]));
}
//# sourceMappingURL=coloring.js.map

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc