Skip to content
This repository has been archived by the owner on Jul 16, 2024. It is now read-only.

Commit

Permalink
Make help documentation print built-in options by default (#416)
Browse files Browse the repository at this point in the history
  • Loading branch information
IMax153 authored Dec 8, 2023
1 parent 108e867 commit 234c3f7
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 40 deletions.
8 changes: 8 additions & 0 deletions .changeset/tough-walls-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@effect/cli": patch
---

Make help documentation print built-in options by default

The printing of built-in options in the help documentation can be disabled by providing a custom
`CliConfig` to your CLI application with `showBuiltIns` set to `false`.
7 changes: 7 additions & 0 deletions src/CliConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ export interface CliConfig {
* Defaults to `true`.
*/
readonly showAllNames: boolean
/**
* Whether or not to display built-in options in the help documentation
* generated for a `Command`.
*
* Defaults to `true`.
*/
readonly showBuiltIns: boolean
/**
* Whether or not to display the type of an option in the usage of a
* particular command.
Expand Down
6 changes: 4 additions & 2 deletions src/Command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,10 @@ export const fromDescriptor: {
* @since 1.0.0
* @category accessors
*/
export const getHelp: <Name extends string, R, E, A>(self: Command<Name, R, E, A>) => HelpDoc =
Internal.getHelp
export const getHelp: <Name extends string, R, E, A>(
self: Command<Name, R, E, A>,
config: CliConfig
) => HelpDoc = Internal.getHelp

/**
* @since 1.0.0
Expand Down
2 changes: 1 addition & 1 deletion src/CommandDescriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export declare namespace Command {
* @since 1.0.0
* @category combinators
*/
export const getHelp: <A>(self: Command<A>) => HelpDoc = Internal.getHelp
export const getHelp: <A>(self: Command<A>, config: CliConfig) => HelpDoc = Internal.getHelp

/**
* @since 1.0.0
Expand Down
1 change: 1 addition & 0 deletions src/internal/cliConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const defaultConfig: CliConfig.CliConfig = {
autoCorrectLimit: 2,
finalCheckBuiltIn: false,
showAllNames: true,
showBuiltIns: true,
showTypes: true
}

Expand Down
51 changes: 26 additions & 25 deletions src/internal/command.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import type { FileSystem } from "@effect/platform/FileSystem"
import type { QuitException, Terminal } from "@effect/platform/Terminal"
import type * as FileSystem from "@effect/platform/FileSystem"
import type * as Terminal from "@effect/platform/Terminal"
import * as Context from "effect/Context"
import * as Effect from "effect/Effect"
import * as Effectable from "effect/Effectable"
import { dual } from "effect/Function"
import { globalValue } from "effect/GlobalValue"
import type { HashMap } from "effect/HashMap"
import type { HashSet } from "effect/HashSet"
import type * as HashMap from "effect/HashMap"
import type * as HashSet from "effect/HashSet"
import type * as Option from "effect/Option"
import { pipeArguments } from "effect/Pipeable"
import * as ReadonlyArray from "effect/ReadonlyArray"
import type * as Types from "effect/Types"
import type * as Args from "../Args.js"
import type * as CliApp from "../CliApp.js"
import type { CliConfig } from "../CliConfig.js"
import type * as CliConfig from "../CliConfig.js"
import type * as Command from "../Command.js"
import type * as Descriptor from "../CommandDescriptor.js"
import type { HelpDoc } from "../HelpDoc.js"
import type { Span } from "../HelpDoc/Span.js"
import type * as HelpDoc from "../HelpDoc.js"
import type * as Span from "../HelpDoc/Span.js"
import type * as Options from "../Options.js"
import type * as Prompt from "../Prompt.js"
import type { Usage } from "../Usage.js"
import type * as Usage from "../Usage.js"
import * as ValidationError from "../ValidationError.js"
import * as InternalArgs from "./args.js"
import * as InternalCliApp from "./cliApp.js"
Expand Down Expand Up @@ -223,13 +223,14 @@ export const make: {

/** @internal */
export const getHelp = <Name extends string, R, E, A>(
self: Command.Command<Name, R, E, A>
): HelpDoc => InternalDescriptor.getHelp(self.descriptor)
self: Command.Command<Name, R, E, A>,
config: CliConfig.CliConfig
): HelpDoc.HelpDoc => InternalDescriptor.getHelp(self.descriptor, config)

/** @internal */
export const getNames = <Name extends string, R, E, A>(
self: Command.Command<Name, R, E, A>
): HashSet<string> => InternalDescriptor.getNames(self.descriptor)
): HashSet.HashSet<string> => InternalDescriptor.getNames(self.descriptor)

/** @internal */
export const getBashCompletions = <Name extends string, R, E, A>(
Expand All @@ -255,13 +256,13 @@ export const getZshCompletions = <Name extends string, R, E, A>(
/** @internal */
export const getSubcommands = <Name extends string, R, E, A>(
self: Command.Command<Name, R, E, A>
): HashMap<string, Descriptor.Command<unknown>> =>
): HashMap.HashMap<string, Descriptor.Command<unknown>> =>
InternalDescriptor.getSubcommands(self.descriptor)

/** @internal */
export const getUsage = <Name extends string, R, E, A>(
self: Command.Command<Name, R, E, A>
): Usage => InternalDescriptor.getUsage(self.descriptor)
): Usage.Usage => InternalDescriptor.getUsage(self.descriptor)

const mapDescriptor = dual<
<A>(f: (_: Descriptor.Command<A>) => Descriptor.Command<A>) => <Name extends string, R, E>(
Expand Down Expand Up @@ -290,13 +291,13 @@ export const prompt = <Name extends string, A, R, E>(
/** @internal */
export const withDescription = dual<
(
help: string | HelpDoc
help: string | HelpDoc.HelpDoc
) => <Name extends string, R, E, A>(
self: Command.Command<Name, R, E, A>
) => Command.Command<Name, R, E, A>,
<Name extends string, R, E, A>(
self: Command.Command<Name, R, E, A>,
help: string | HelpDoc
help: string | HelpDoc.HelpDoc
) => Command.Command<Name, R, E, A>
>(2, (self, help) => mapDescriptor(self, InternalDescriptor.withDescription(help)))

Expand Down Expand Up @@ -389,21 +390,21 @@ export const withSubcommands = dual<
export const wizard = dual<
(
prefix: ReadonlyArray<string>,
config: CliConfig
config: CliConfig.CliConfig
) => <Name extends string, R, E, A>(
self: Command.Command<Name, R, E, A>
) => Effect.Effect<
FileSystem | Terminal,
QuitException | ValidationError.ValidationError,
FileSystem.FileSystem | Terminal.Terminal,
Terminal.QuitException | ValidationError.ValidationError,
ReadonlyArray<string>
>,
<Name extends string, R, E, A>(
self: Command.Command<Name, R, E, A>,
prefix: ReadonlyArray<string>,
config: CliConfig
config: CliConfig.CliConfig
) => Effect.Effect<
FileSystem | Terminal,
QuitException | ValidationError.ValidationError,
FileSystem.FileSystem | Terminal.Terminal,
Terminal.QuitException | ValidationError.ValidationError,
ReadonlyArray<string>
>
>(3, (self, prefix, config) => InternalDescriptor.wizard(self.descriptor, prefix, config))
Expand All @@ -413,8 +414,8 @@ export const run = dual<
(config: {
readonly name: string
readonly version: string
readonly summary?: Span | undefined
readonly footer?: HelpDoc | undefined
readonly summary?: Span.Span | undefined
readonly footer?: HelpDoc.HelpDoc | undefined
}) => <Name extends string, R, E, A>(
self: Command.Command<Name, R, E, A>
) => (
Expand All @@ -423,8 +424,8 @@ export const run = dual<
<Name extends string, R, E, A>(self: Command.Command<Name, R, E, A>, config: {
readonly name: string
readonly version: string
readonly summary?: Span | undefined
readonly footer?: HelpDoc | undefined
readonly summary?: Span.Span | undefined
readonly footer?: HelpDoc.HelpDoc | undefined
}) => (
args: ReadonlyArray<string>
) => Effect.Effect<R | CliApp.CliApp.Environment, E | ValidationError.ValidationError, void>
Expand Down
23 changes: 14 additions & 9 deletions src/internal/commandDescriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,10 @@ export const prompt = <Name extends string, A>(
// =============================================================================

/** @internal */
export const getHelp = <A>(self: Descriptor.Command<A>): HelpDoc.HelpDoc =>
getHelpInternal(self as Instruction)
export const getHelp = <A>(
self: Descriptor.Command<A>,
config: CliConfig.CliConfig
): HelpDoc.HelpDoc => getHelpInternal(self as Instruction, config)

/** @internal */
export const getNames = <A>(self: Descriptor.Command<A>): HashSet.HashSet<string> =>
Expand Down Expand Up @@ -314,7 +316,7 @@ export const wizard = dual<
// Internals
// =============================================================================

const getHelpInternal = (self: Instruction): HelpDoc.HelpDoc => {
const getHelpInternal = (self: Instruction, config: CliConfig.CliConfig): HelpDoc.HelpDoc => {
switch (self._tag) {
case "Standard": {
const header = InternalHelpDoc.isEmpty(self.description)
Expand All @@ -324,7 +326,10 @@ const getHelpInternal = (self: Instruction): HelpDoc.HelpDoc => {
const argsSection = InternalHelpDoc.isEmpty(argsHelp)
? InternalHelpDoc.empty
: InternalHelpDoc.sequence(InternalHelpDoc.h1("ARGUMENTS"), argsHelp)
const optionsHelp = InternalOptions.getHelp(self.options)
const options = config.showBuiltIns
? Options.all([self.options, InternalBuiltInOptions.builtIns])
: self.options
const optionsHelp = InternalOptions.getHelp(options)
const optionsSection = InternalHelpDoc.isEmpty(optionsHelp)
? InternalHelpDoc.empty
: InternalHelpDoc.sequence(InternalHelpDoc.h1("OPTIONS"), optionsHelp)
Expand All @@ -336,7 +341,7 @@ const getHelpInternal = (self: Instruction): HelpDoc.HelpDoc => {
: InternalHelpDoc.sequence(InternalHelpDoc.h1("DESCRIPTION"), self.description)
}
case "Map": {
return getHelpInternal(self.command)
return getHelpInternal(self.command, config)
}
case "Subcommands": {
const getUsage = (
Expand Down Expand Up @@ -405,7 +410,7 @@ const getHelpInternal = (self: Instruction): HelpDoc.HelpDoc => {
throw new Error("[BUG]: Subcommands.usage - received empty list of subcommands to print")
}
return InternalHelpDoc.sequence(
getHelpInternal(self.parent),
getHelpInternal(self.parent, config),
InternalHelpDoc.sequence(
InternalHelpDoc.h1("COMMANDS"),
printSubcommands(ReadonlyArray.flatMap(
Expand Down Expand Up @@ -534,7 +539,7 @@ const parseInternal = (
const normalizedArgv0 = InternalCliConfig.normalizeCase(config, argv0)
const normalizedCommandName = InternalCliConfig.normalizeCase(config, self.name)
if (normalizedArgv0 === normalizedCommandName) {
const help = getHelpInternal(self)
const help = getHelpInternal(self, config)
const usage = getUsageInternal(self)
const options = InternalBuiltInOptions.builtInOptions(self, usage, help)
const argsWithoutCommand = ReadonlyArray.drop(args, 1)
Expand Down Expand Up @@ -680,7 +685,7 @@ const parseInternal = (
const helpDirectiveForParent = Effect.sync(() => {
return InternalCommandDirective.builtIn(InternalBuiltInOptions.showHelp(
getUsageInternal(self),
getHelpInternal(self)
getHelpInternal(self, config)
))
})
const helpDirectiveForChild = parseChildren.pipe(
Expand Down Expand Up @@ -1357,7 +1362,7 @@ export const helpRequestedError = <A>(
op.error = InternalHelpDoc.empty
op.showHelp = InternalBuiltInOptions.showHelp(
getUsageInternal(command as Instruction),
getHelpInternal(command as Instruction)
getHelpInternal(command as Instruction, InternalCliConfig.defaultConfig)
)
return op
}
9 changes: 6 additions & 3 deletions test/CommandDescriptor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ describe("Command", () => {
describe("Help Documentation", () => {
it("should allow adding help documentation to a command", () =>
Effect.gen(function*(_) {
const config = CliConfig.make({ showBuiltIns: false })
const cmd = Descriptor.make("tldr").pipe(Descriptor.withDescription("this is some help"))
const args = ReadonlyArray.of("tldr")
const result = yield* _(Descriptor.parse(cmd, args, CliConfig.defaultConfig))
Expand All @@ -276,18 +277,20 @@ describe("Command", () => {
HelpDoc.p("this is some help")
)
expect(result).toEqual(CommandDirective.userDefined(ReadonlyArray.empty(), expectedValue))
expect(Descriptor.getHelp(cmd)).toEqual(expectedDoc)
expect(Descriptor.getHelp(cmd, config)).toEqual(expectedDoc)
}).pipe(runEffect))

it("should allow adding help documentation to subcommands", () => {
const config = CliConfig.make({ showBuiltIns: false })
const cmd = Descriptor.make("command").pipe(Descriptor.withSubcommands([
["sub", Descriptor.make("sub").pipe(Descriptor.withDescription("this is some help"))]
]))
const expected = HelpDoc.sequence(HelpDoc.h1("DESCRIPTION"), HelpDoc.p("this is some help"))
expect(Descriptor.getHelp(cmd)).not.toEqual(expected)
expect(Descriptor.getHelp(cmd, config)).not.toEqual(expected)
})

it("should correctly display help documentation for a command", () => {
const config = CliConfig.make({ showBuiltIns: false })
const child2 = Descriptor.make("child2").pipe(
Descriptor.withDescription("help 2")
)
Expand All @@ -299,7 +302,7 @@ describe("Command", () => {
Descriptor.withSubcommands([["child1", child1]])
)
const result = Render.prettyDefault(
Doc.unAnnotate(HelpDoc.toAnsiDoc(Descriptor.getHelp(parent)))
Doc.unAnnotate(HelpDoc.toAnsiDoc(Descriptor.getHelp(parent, config)))
)
expect(result).toBe(String.stripMargin(
`|COMMANDS
Expand Down

0 comments on commit 234c3f7

Please sign in to comment.