From cc03d2b3160cf7fbf5a4dff3cfe262c419f67328 Mon Sep 17 00:00:00 2001 From: Maxwell Brown Date: Thu, 30 Nov 2023 18:36:54 -0500 Subject: [PATCH] Allow `--wizard` for subcommands (#399) --- src/Command.ts | 8 +++---- src/CommandDescriptor.ts | 8 +++---- src/internal/cliApp.ts | 36 +++++++++++++++++++++++++------ src/internal/command.ts | 6 +++--- src/internal/commandDescriptor.ts | 10 ++++----- 5 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/Command.ts b/src/Command.ts index 1e83799..951cca1 100644 --- a/src/Command.ts +++ b/src/Command.ts @@ -307,16 +307,16 @@ export const withSubcommands: { */ export const wizard: { ( - rootCommand: string, + prefix: ReadonlyArray, config: CliConfig ): ( self: Command - ) => Effect> + ) => Effect> ( self: Command, - rootCommand: string, + prefix: ReadonlyArray, config: CliConfig - ): Effect> + ): Effect> } = Internal.wizard /** diff --git a/src/CommandDescriptor.ts b/src/CommandDescriptor.ts index 99fb9a2..a08533a 100644 --- a/src/CommandDescriptor.ts +++ b/src/CommandDescriptor.ts @@ -258,14 +258,14 @@ export const withSubcommands: { */ export const wizard: { ( - rootCommand: string, + prefix: ReadonlyArray, config: CliConfig ): ( self: Command - ) => Effect> + ) => Effect> ( self: Command, - rootCommand: string, + prefix: ReadonlyArray, config: CliConfig - ): Effect> + ): Effect> } = Internal.wizard diff --git a/src/internal/cliApp.ts b/src/internal/cliApp.ts index 750dc2d..692e0ae 100644 --- a/src/internal/cliApp.ts +++ b/src/internal/cliApp.ts @@ -4,6 +4,7 @@ import * as Console from "effect/Console" import * as Context from "effect/Context" import * as Effect from "effect/Effect" import { dual, pipe } from "effect/Function" +import * as HashMap from "effect/HashMap" import * as Option from "effect/Option" import { pipeArguments } from "effect/Pipeable" import * as ReadonlyArray from "effect/ReadonlyArray" @@ -90,13 +91,13 @@ export const run = dual< Effect.catchSome((e) => InternalValidationError.isValidationError(e) && InternalValidationError.isHelpRequested(e) - ? Option.some(handleBuiltInOption(self, e.showHelp, execute, config)) + ? Option.some(handleBuiltInOption(self, args, e.showHelp, execute, config)) : Option.none() ) ) } case "BuiltIn": { - return handleBuiltInOption(self, directive.option, execute, config).pipe( + return handleBuiltInOption(self, args, directive.option, execute, config).pipe( Effect.catchSome((e) => InternalValidationError.isValidationError(e) ? Option.some(Effect.zipRight(printDocs(e.error), Effect.fail(e))) @@ -122,6 +123,7 @@ const isQuitException = (u: unknown): u is Terminal.QuitException => const handleBuiltInOption = ( self: CliApp.CliApp, + args: ReadonlyArray, builtIn: BuiltInOptions.BuiltInOptions, execute: (a: A) => Effect.Effect, config: CliConfig.CliConfig @@ -228,8 +230,9 @@ const handleBuiltInOption = ( ) const help = InternalHelpDoc.sequence(header, description) const text = InternalHelpDoc.toAnsiText(help) + const wizardPrefix = getWizardPrefix(builtIn, programName, args) return Console.log(text).pipe( - Effect.zipRight(InternalCommand.wizard(builtIn.command, programName, config)), + Effect.zipRight(InternalCommand.wizard(builtIn.command, wizardPrefix, config)), Effect.tap((args) => Console.log(InternalHelpDoc.toAnsiText(renderWizardArgs(args)))), Effect.flatMap((args) => InternalTogglePrompt.toggle({ @@ -237,11 +240,13 @@ const handleBuiltInOption = ( initial: true, active: "yes", inactive: "no" - }).pipe(Effect.flatMap((shouldRunCommand) => - shouldRunCommand - ? Console.log().pipe(Effect.zipRight(run(self, args.slice(1), execute))) + }).pipe(Effect.flatMap((shouldRunCommand) => { + return shouldRunCommand + ? Console.log().pipe( + Effect.zipRight(run(self, ReadonlyArray.drop(args, 1), execute)) + ) : Effect.unit - )) + })) ), Effect.catchAll((e) => { if (isQuitException(e)) { @@ -296,6 +301,23 @@ const prefixCommand = (self: Command.Command): ReadonlyArray => { return prefix } +const getWizardPrefix = ( + builtIn: BuiltInOptions.ShowWizard, + rootCommand: string, + commandLineArgs: ReadonlyArray +): ReadonlyArray => { + const subcommands = InternalCommand.getSubcommands(builtIn.command) + const [parentArgs, childArgs] = ReadonlyArray.span( + commandLineArgs, + (name) => !HashMap.has(subcommands, name) + ) + const args = ReadonlyArray.matchLeft(childArgs, { + onEmpty: () => ReadonlyArray.filter(parentArgs, (arg) => arg !== "--wizard"), + onNonEmpty: (head) => ReadonlyArray.append(parentArgs, head) + }) + return ReadonlyArray.prepend(args, rootCommand) +} + const renderWizardArgs = (args: ReadonlyArray) => { const params = pipe( ReadonlyArray.filter(args, (param) => param.length > 0), diff --git a/src/internal/command.ts b/src/internal/command.ts index 3ba0b4e..62c357c 100644 --- a/src/internal/command.ts +++ b/src/internal/command.ts @@ -388,7 +388,7 @@ export const withSubcommands = dual< /** @internal */ export const wizard = dual< ( - rootCommand: string, + prefix: ReadonlyArray, config: CliConfig ) => ( self: Command.Command @@ -399,14 +399,14 @@ export const wizard = dual< >, ( self: Command.Command, - rootCommand: string, + prefix: ReadonlyArray, config: CliConfig ) => Effect.Effect< FileSystem | Terminal, QuitException | ValidationError.ValidationError, ReadonlyArray > ->(3, (self, rootCommand, config) => InternalDescriptor.wizard(self.descriptor, rootCommand, config)) +>(3, (self, prefix, config) => InternalDescriptor.wizard(self.descriptor, prefix, config)) /** @internal */ export const run = dual< diff --git a/src/internal/commandDescriptor.ts b/src/internal/commandDescriptor.ts index afae48f..fd913f8 100644 --- a/src/internal/commandDescriptor.ts +++ b/src/internal/commandDescriptor.ts @@ -343,7 +343,7 @@ export const withSubcommands = dual< /** @internal */ export const wizard = dual< ( - rootCommand: string, + prefix: ReadonlyArray, config: CliConfig.CliConfig ) => (self: Descriptor.Command) => Effect.Effect< FileSystem.FileSystem | Terminal.Terminal, @@ -352,14 +352,14 @@ export const wizard = dual< >, ( self: Descriptor.Command, - rootCommand: string, + prefix: ReadonlyArray, config: CliConfig.CliConfig ) => Effect.Effect< FileSystem.FileSystem | Terminal.Terminal, Terminal.QuitException | ValidationError.ValidationError, ReadonlyArray > ->(3, (self, rootCommand, config) => wizardInternal(self as Instruction, rootCommand, config)) +>(3, (self, prefix, config) => wizardInternal(self as Instruction, prefix, config)) // ============================================================================= // Internals @@ -883,7 +883,7 @@ const optionsWizardHeader = InternalSpan.code("Options Wizard - ") const wizardInternal = ( self: Instruction, - rootCommand: string, + prefix: ReadonlyArray, config: CliConfig.CliConfig ): Effect.Effect< FileSystem.FileSystem | Terminal.Terminal, @@ -970,7 +970,7 @@ const wizardInternal = ( } } } - return Ref.make>(ReadonlyArray.of(rootCommand)).pipe( + return Ref.make(prefix).pipe( Effect.flatMap((commandLineRef) => loop(getWizardCommandSequence(self), commandLineRef).pipe( Effect.zipRight(Ref.get(commandLineRef))