diff --git a/examples/naval-fate.ts b/examples/naval-fate.ts
index b2354d8..7f64248 100644
--- a/examples/naval-fate.ts
+++ b/examples/naval-fate.ts
@@ -32,12 +32,20 @@ const speedOption = Options.integer("speed").pipe(
Options.withDefault(10)
)
+const shipCommandParent = HandledCommand.makeRequestHelp("ship", {
+ options: Options.withDefault(Options.boolean("verbose"), false)
+})
+
const newShipCommand = HandledCommand.make("new", {
args: nameArg
}, ({ args: name }) =>
Effect.gen(function*(_) {
+ const { options: verbose } = yield* _(shipCommandParent)
yield* _(createShip(name))
yield* _(Console.log(`Created ship: '${name}'`))
+ if (verbose) {
+ yield* _(Console.log(`Verbose mode enabled`))
+ }
}))
const moveShipCommand = HandledCommand.make("move", {
@@ -57,13 +65,13 @@ const shootShipCommand = HandledCommand.make("shoot", {
yield* _(Console.log(`Shot cannons at coordinates (${x}, ${y})`))
}))
-const shipCommand = HandledCommand.makeUnit("ship").pipe(
- HandledCommand.withSubcommands([
- newShipCommand,
- moveShipCommand,
- shootShipCommand
- ])
-)
+const shipCommand = HandledCommand.withSubcommands(shipCommandParent, [
+ newShipCommand,
+ moveShipCommand,
+ shootShipCommand
+])
+
+const mineCommandParent = HandledCommand.makeRequestHelp("mine")
const setMineCommand = HandledCommand.make("set", {
args: coordinatesArg,
@@ -84,12 +92,10 @@ const removeMineCommand = HandledCommand.make("remove", {
yield* _(Console.log(`Removing mine at coordinates (${x}, ${y}), if present`))
}))
-const mineCommand = HandledCommand.makeUnit("mine").pipe(
- HandledCommand.withSubcommands([
- setMineCommand,
- removeMineCommand
- ])
-)
+const mineCommand = HandledCommand.withSubcommands(mineCommandParent, [
+ setMineCommand,
+ removeMineCommand
+])
const run = Command.make("naval_fate").pipe(
Command.withDescription("An implementation of the Naval Fate CLI application."),
diff --git a/src/HandledCommand.ts b/src/HandledCommand.ts
index a859fa5..5af5b0b 100644
--- a/src/HandledCommand.ts
+++ b/src/HandledCommand.ts
@@ -1,7 +1,9 @@
/**
* @since 1.0.0
*/
+import * as Context from "effect/Context"
import * as Effect from "effect/Effect"
+import * as Effectable from "effect/Effectable"
import { dual } from "effect/Function"
import type * as Option from "effect/Option"
import { type Pipeable, pipeArguments } from "effect/Pipeable"
@@ -10,7 +12,7 @@ import * as CliApp from "./CliApp.js"
import * as Command from "./Command.js"
import type { HelpDoc } from "./HelpDoc.js"
import type { Span } from "./HelpDoc/Span.js"
-import type { ValidationError } from "./ValidationError.js"
+import * as ValidationError from "./ValidationError.js"
/**
* @since 1.0.0
@@ -28,14 +30,21 @@ export type TypeId = typeof TypeId
* @since 1.0.0
* @category models
*/
-export interface HandledCommand extends Pipeable {
+export interface HandledCommand
+ extends Pipeable, Effect.Effect, never, A>
+{
readonly [TypeId]: TypeId
readonly command: Command.Command
readonly handler: (_: A) => Effect.Effect
+ readonly tag: Context.Tag, A>
}
const Prototype = {
+ ...Effectable.CommitPrototype,
[TypeId]: TypeId,
+ commit(this: HandledCommand) {
+ return this.tag
+ },
pipe() {
return pipeArguments(this, arguments)
}
@@ -57,9 +66,28 @@ export const fromCommand = dual<
const self = Object.create(Prototype)
self.command = command
self.handler = handler
+ self.tag = Context.Tag()
return self
})
+/**
+ * @since 1.0.0
+ * @category combinators
+ */
+export const modify = dual<
+ (f: (_: HandledCommand) => HandledCommand) => (
+ self: HandledCommand
+ ) => HandledCommand,
+ (
+ self: HandledCommand,
+ f: (_: HandledCommand) => HandledCommand
+ ) => HandledCommand
+>(2, (self, f) => {
+ const command = f(self)
+ ;(command as any).tag = self.tag
+ return command
+})
+
/**
* @since 1.0.0
* @category constructors
@@ -72,10 +100,10 @@ export const fromCommandUnit = (
* @since 1.0.0
* @category constructors
*/
-export const fromCommandOrDie = (
- command: Command.Command,
- orDie: () => unknown
-): HandledCommand => fromCommand(command, (_) => Effect.dieSync(orDie))
+export const fromCommandRequestHelp = (
+ command: Command.Command
+): HandledCommand =>
+ fromCommand(command, (_) => Effect.fail(ValidationError.helpRequested(command)))
/**
* @since 1.0.0
@@ -110,15 +138,14 @@ export const makeUnit = (
+export const makeRequestHelp = (
name: Name,
- config: Command.Command.ConstructorConfig,
- orDie: () => unknown
+ config?: Command.Command.ConstructorConfig
): HandledCommand<
{ readonly name: Name; readonly options: OptionsType; readonly args: ArgsType },
never,
- never
-> => fromCommandOrDie(Command.make(name, config), orDie)
+ ValidationError.ValidationError
+> => fromCommandRequestHelp(Command.make(name, config))
/**
* @since 1.0.0
@@ -134,7 +161,8 @@ export const withSubcommands = dual<
{ subcommand: Option.Option> }
>
>,
- R | Effect.Effect.Context>,
+ | R
+ | Exclude>, Command.Command>,
E | Effect.Effect.Error>
>,
<
@@ -152,37 +180,43 @@ export const withSubcommands = dual<
{ subcommand: Option.Option> }
>
>,
- R | Effect.Effect.Context>,
+ | R
+ | Exclude>, Command.Command>,
E | Effect.Effect.Error>
>
->(2, (self, subcommands) => {
- const command = Command.withSubcommands(
- self.command,
- ReadonlyArray.map(subcommands, (_) => _.command)
- )
- const handlers = ReadonlyArray.reduce(
- subcommands,
- {} as Record Effect.Effect>,
- (handlers, subcommand) => {
- for (const name of Command.getNames(subcommand.command)) {
- handlers[name] = subcommand.handler
+>(2, (self, subcommands) =>
+ modify(self, () => {
+ const command = Command.withSubcommands(
+ self.command,
+ ReadonlyArray.map(subcommands, (_) => _.command)
+ )
+ const handlers = ReadonlyArray.reduce(
+ subcommands,
+ {} as Record Effect.Effect>,
+ (handlers, subcommand) => {
+ for (const name of Command.getNames(subcommand.command)) {
+ handlers[name] = subcommand.handler
+ }
+ return handlers
}
- return handlers
- }
- )
- const handler = (
- args: {
- readonly name: string
- readonly subcommand: Option.Option<{ readonly name: string }>
- }
- ) => {
- if (args.subcommand._tag === "Some") {
- return handlers[args.subcommand.value.name](args.subcommand.value)
+ )
+ function handler(
+ args: {
+ readonly name: string
+ readonly subcommand: Option.Option<{ readonly name: string }>
+ }
+ ) {
+ if (args.subcommand._tag === "Some") {
+ return Effect.provideService(
+ handlers[args.subcommand.value.name](args.subcommand.value),
+ (self as any).tag,
+ args as any
+ )
+ }
+ return self.handler(args as any)
}
- return self.handler(args as any)
- }
- return fromCommand(command as any, handler) as any
-})
+ return fromCommand(command as any, handler) as any
+ }))
/**
* @since 1.0.0
@@ -198,7 +232,7 @@ export const toAppAndRun = dual<
self: HandledCommand
) => (
args: ReadonlyArray
- ) => Effect.Effect,
+ ) => Effect.Effect,
(self: HandledCommand, config: {
readonly name: string
readonly version: string
@@ -206,7 +240,7 @@ export const toAppAndRun = dual<
readonly footer?: HelpDoc | undefined
}) => (
args: ReadonlyArray
- ) => Effect.Effect
+ ) => Effect.Effect
>(2, (self, config) => {
const app = CliApp.make({
...config,