diff --git a/.changeset/beige-mice-hide.md b/.changeset/beige-mice-hide.md new file mode 100644 index 0000000..1da06be --- /dev/null +++ b/.changeset/beige-mice-hide.md @@ -0,0 +1,5 @@ +--- +"@effect/cli": patch +--- + +Made `Command`, `Option`, `Args` and `Primitive` pipeable diff --git a/src/Args.ts b/src/Args.ts index 60ce109..37ca9d5 100644 --- a/src/Args.ts +++ b/src/Args.ts @@ -8,6 +8,7 @@ import type { ValidationError } from "@effect/cli/ValidationError" import type { Chunk, NonEmptyChunk } from "@effect/data/Chunk" import type { Either } from "@effect/data/Either" import type { Option } from "@effect/data/Option" +import type { Pipeable } from "@effect/data/Pipeable" import type { NonEmptyReadonlyArray } from "@effect/data/ReadonlyArray" import type { Effect } from "@effect/io/Effect" @@ -29,7 +30,7 @@ export type ArgsTypeId = typeof ArgsTypeId * @since 1.0.0 * @category models */ -export interface Args extends Args.Variance {} +export interface Args extends Args.Variance, Pipeable {} /** * @since 1.0.0 diff --git a/src/Command.ts b/src/Command.ts index bf245de..dc6e43a 100644 --- a/src/Command.ts +++ b/src/Command.ts @@ -13,6 +13,7 @@ import type { Either } from "@effect/data/Either" import type { HashMap } from "@effect/data/HashMap" import type { HashSet } from "@effect/data/HashSet" import type { Option } from "@effect/data/Option" +import type { Pipeable } from "@effect/data/Pipeable" import type { NonEmptyReadonlyArray } from "@effect/data/ReadonlyArray" import type { Effect } from "@effect/io/Effect" @@ -38,7 +39,7 @@ export type CommandTypeId = typeof CommandTypeId * @since 1.0.0 * @category models */ -export interface Command extends Command.Variance {} +export interface Command extends Command.Variance, Pipeable {} /** * @since 1.0.0 diff --git a/src/Options.ts b/src/Options.ts index baa9c4d..dca4fb2 100644 --- a/src/Options.ts +++ b/src/Options.ts @@ -10,6 +10,7 @@ import type { Chunk, NonEmptyChunk } from "@effect/data/Chunk" import type { Either } from "@effect/data/Either" import type { HashMap } from "@effect/data/HashMap" import type { Option } from "@effect/data/Option" +import type { Pipeable } from "@effect/data/Pipeable" import type { NonEmptyReadonlyArray } from "@effect/data/ReadonlyArray" import type { Effect } from "@effect/io/Effect" @@ -29,7 +30,7 @@ export type OptionsTypeId = typeof OptionsTypeId * @since 1.0.0 * @category models */ -export interface Options extends Options.Variance {} +export interface Options extends Options.Variance, Pipeable {} /** * @since 1.0.0 diff --git a/src/Primitive.ts b/src/Primitive.ts index eafa0a5..c8e2f4f 100644 --- a/src/Primitive.ts +++ b/src/Primitive.ts @@ -4,6 +4,7 @@ import type { Span } from "@effect/cli/HelpDoc/Span" import * as internal from "@effect/cli/internal/primitive" import type { Option } from "@effect/data/Option" +import type { Pipeable } from "@effect/data/Pipeable" import type { NonEmptyReadonlyArray } from "@effect/data/ReadonlyArray" import type { Effect } from "@effect/io/Effect" @@ -37,7 +38,7 @@ export declare namespace Primitive { * @since 1.0.0 * @category models */ - export interface Variance { + export interface Variance extends Pipeable { readonly [PrimitiveTypeId]: { readonly _A: (_: never) => A } diff --git a/src/internal/args.ts b/src/internal/args.ts index ae90f4b..56e2bd5 100644 --- a/src/internal/args.ts +++ b/src/internal/args.ts @@ -12,6 +12,7 @@ import * as Chunk from "@effect/data/Chunk" import * as Either from "@effect/data/Either" import { dual } from "@effect/data/Function" import * as Option from "@effect/data/Option" +import { pipeArguments } from "@effect/data/Pipeable" import * as ReadonlyArray from "@effect/data/ReadonlyArray" import type { NonEmptyReadonlyArray } from "@effect/data/ReadonlyArray" import * as Effect from "@effect/io/Effect" @@ -31,6 +32,9 @@ export type Op = Args.Args & Body & { const proto = { [ArgsTypeId]: { _A: (_: never) => _ + }, + pipe() { + return pipeArguments(this, arguments) } } diff --git a/src/internal/command.ts b/src/internal/command.ts index 72e6adb..985e46f 100644 --- a/src/internal/command.ts +++ b/src/internal/command.ts @@ -21,6 +21,7 @@ import { dual, pipe } from "@effect/data/Function" import * as HashMap from "@effect/data/HashMap" import * as HashSet from "@effect/data/HashSet" import * as Option from "@effect/data/Option" +import { pipeArguments } from "@effect/data/Pipeable" import * as ReadonlyArray from "@effect/data/ReadonlyArray" import type { NonEmptyReadonlyArray } from "@effect/data/ReadonlyArray" import * as Effect from "@effect/io/Effect" @@ -41,6 +42,9 @@ const proto = { [CommandTypeId]: { _ArgsType: (_: never) => _, _OptionsType: (_: never) => _ + }, + pipe() { + return pipeArguments(this, arguments) } } diff --git a/src/internal/options.ts b/src/internal/options.ts index 691fddd..96f4bf9 100644 --- a/src/internal/options.ts +++ b/src/internal/options.ts @@ -16,6 +16,7 @@ import { dual, pipe } from "@effect/data/Function" import * as HashMap from "@effect/data/HashMap" import * as Option from "@effect/data/Option" import * as Order from "@effect/data/Order" +import { pipeArguments } from "@effect/data/Pipeable" import type { Predicate } from "@effect/data/Predicate" import * as RA from "@effect/data/ReadonlyArray" import * as Effect from "@effect/io/Effect" @@ -35,6 +36,9 @@ export type Op = Options.Options & Body & const proto = { [OptionsTypeId]: { _A: (_: never) => _ + }, + pipe() { + return pipeArguments(this, arguments) } } diff --git a/src/internal/primitive.ts b/src/internal/primitive.ts index d698d70..03cee9e 100644 --- a/src/internal/primitive.ts +++ b/src/internal/primitive.ts @@ -3,6 +3,7 @@ import * as span from "@effect/cli/internal/helpDoc/span" import type * as Primitive from "@effect/cli/Primitive" import { dual, pipe } from "@effect/data/Function" import * as Option from "@effect/data/Option" +import { pipeArguments } from "@effect/data/Pipeable" import type { NonEmptyReadonlyArray } from "@effect/data/ReadonlyArray" import * as Effect from "@effect/io/Effect" @@ -16,6 +17,9 @@ export const PrimitiveTypeId: Primitive.PrimitiveTypeId = Symbol.for( const proto = { [PrimitiveTypeId]: { _A: (_: never) => _ + }, + pipe() { + return pipeArguments(this, arguments) } } diff --git a/test/Command.ts b/test/Command.ts index 213cfbc..4f966f2 100644 --- a/test/Command.ts +++ b/test/Command.ts @@ -64,7 +64,7 @@ describe.concurrent("Command", () => { Effect.gen(function*($) { const config = CliConfig.defaultConfig const args = ["log"] - const command = pipe(Command.make("remote"), Command.orElse(Command.make("log"))) + const command = Command.make("remote").pipe(Command.orElse(Command.make("log"))) const result = yield* $(Command.parse(command, args, config)) const expected = { name: "log", options: void 0, args: void 0 } expect(result).toEqual(CommandDirective.userDefined([], expected)) @@ -97,8 +97,7 @@ describe.concurrent("Command", () => { })) describe.concurrent("Subcommands - no options or arguments", () => { - const git = pipe( - Command.make("git", { options: Options.alias(Options.boolean("verbose"), "v") }), + const git = Command.make("git", { options: Options.boolean("verbose").pipe(Options.alias("v")) }).pipe( Command.subcommands([Command.make("remote"), Command.make("log")]) ) @@ -155,13 +154,11 @@ describe.concurrent("Command", () => { }) describe.concurrent("Subcommands - with options and arguments", () => { - const rebaseOptions = pipe( - Options.boolean("i"), - Options.zip(Options.withDefault(Options.text("empty"), "drop")) + const rebaseOptions = Options.boolean("i").pipe( + Options.zip(Options.text("empty").pipe(Options.withDefault("drop"))) ) const rebaseArgs = Args.zip(Args.text(), Args.text()) - const git = pipe( - Command.make("git"), + const git = Command.make("git").pipe( Command.subcommands([ Command.make("rebase", { options: rebaseOptions, args: rebaseArgs }) ]) @@ -212,11 +209,9 @@ describe.concurrent("Command", () => { }) describe.concurrent("Subcommands - nested", () => { - const command = pipe( - Command.make("command"), + const command = Command.make("command").pipe( Command.subcommands([ - pipe( - Command.make("sub"), + Command.make("sub").pipe( Command.subcommands([Command.make("subsub", { options: Options.boolean("i"), args: Args.text() })]) ) ]) diff --git a/test/Options.ts b/test/Options.ts index 64d55a4..bb27227 100644 --- a/test/Options.ts +++ b/test/Options.ts @@ -68,7 +68,7 @@ describe.concurrent("Options", () => { it.effect("validates a text option", () => Effect.gen(function*($) { const config = CliConfig.defaultConfig - const option = Options.alias(Options.text("firstName"), "f") + const option = Options.text("firstName").pipe(Options.alias("f")) const result = yield* $(Options.validate(option, ["--firstName", "John"], config)) expect(result).toEqual([[], "John"]) })) @@ -76,7 +76,7 @@ describe.concurrent("Options", () => { it.effect("validates a text option with an alternative format", () => Effect.gen(function*($) { const config = CliConfig.defaultConfig - const option = Options.alias(Options.text("firstName"), "f") + const option = Options.text("firstName").pipe(Options.alias("f")) const result = yield* $(Options.validate(option, ["--firstName=John"], config)) expect(result).toEqual([[], "John"]) })) @@ -84,7 +84,7 @@ describe.concurrent("Options", () => { it.effect("validates a text option with an alias", () => Effect.gen(function*($) { const config = CliConfig.defaultConfig - const option = Options.alias(Options.text("firstName"), "f") + const option = Options.text("firstName").pipe(Options.alias("f")) const result = yield* $(Options.validate(option, ["-f", "John"], config)) expect(result).toEqual([[], "John"]) })) @@ -100,7 +100,7 @@ describe.concurrent("Options", () => { it.effect("validates an option and returns the remainder", () => Effect.gen(function*($) { const config = CliConfig.defaultConfig - const option = Options.alias(Options.text("firstName"), "f") + const option = Options.text("firstName").pipe(Options.alias("f")) const args = ["--firstName", "John", "--lastName", "Doe"] const result = yield* $(Options.validate(option, args, config)) expect(result).toEqual([["--lastName", "Doe"], "John"]) @@ -109,7 +109,7 @@ describe.concurrent("Options", () => { it.effect("validates an option and returns the remainder with different ordering", () => Effect.gen(function*($) { const config = CliConfig.defaultConfig - const option = Options.alias(Options.text("firstName"), "f") + const option = Options.text("firstName").pipe(Options.alias("f")) const args = ["--bar", "baz", "--firstName", "John", "--lastName", "Doe"] const result = yield* $(Options.validate(option, args, config)) expect(result).toEqual([["--bar", "baz", "--lastName", "Doe"], "John"]) @@ -118,7 +118,7 @@ describe.concurrent("Options", () => { it.effect("does not validate when no valid values are passed", () => Effect.gen(function*($) { const config = CliConfig.defaultConfig - const option = Options.alias(Options.text("firstName"), "f") + const option = Options.text("firstName").pipe(Options.alias("f")) const args = ["--lastName", "Doe"] const result = yield* $(Effect.either(Options.validate(option, args, config))) expect(result).toEqual(Either.left(ValidationError.missingValue(HelpDoc.p(Span.error( @@ -129,7 +129,7 @@ describe.concurrent("Options", () => { it.effect("does not validate when an option is passed without a corresponding value", () => Effect.gen(function*($) { const config = CliConfig.defaultConfig - const option = Options.alias(Options.text("firstName"), "f") + const option = Options.text("firstName").pipe(Options.alias("f")) const args = ["--firstName"] const result = yield* $(Effect.either(Options.validate(option, args, config))) expect(result).toEqual(Either.left(ValidationError.invalidValue(HelpDoc.p( @@ -184,7 +184,7 @@ describe.concurrent("Options", () => { it.effect("validates an unsupplied optional option", () => Effect.gen(function*($) { const config = CliConfig.defaultConfig - const option = Options.optional(Options.integer("age")) + const option = Options.integer("age").pipe(Options.optional) const result = yield* $(Options.validate(option, [], config)) expect(result).toEqual([[], Option.none()]) })) @@ -192,7 +192,7 @@ describe.concurrent("Options", () => { it.effect("validates an unsupplied optional option with remainder", () => Effect.gen(function*($) { const config = CliConfig.defaultConfig - const option = Options.optional(Options.integer("age")) + const option = Options.integer("age").pipe(Options.optional) const args = ["--bar", "baz"] const result = yield* $(Options.validate(option, args, config)) expect(result).toEqual([args, Option.none()]) @@ -201,7 +201,7 @@ describe.concurrent("Options", () => { it.effect("validates a supplied optional option", () => Effect.gen(function*($) { const config = CliConfig.defaultConfig - const option = Options.optional(Options.integer("age")) + const option = Options.integer("age").pipe(Options.optional) const args = ["--age", "20"] const result = yield* $(Options.validate(option, args, config)) expect(result).toEqual([[], Option.some(20)]) @@ -210,7 +210,7 @@ describe.concurrent("Options", () => { it.effect("validates a supplied optional option with remainder", () => Effect.gen(function*($) { const config = CliConfig.defaultConfig - const option = Options.optional(Options.integer("age")) + const option = Options.integer("age").pipe(Options.optional) const args = ["--firstName", "John", "--age", "20", "--lastName", "Doe"] const result = yield* $(Options.validate(option, args, config)) expect(result).toEqual([["--firstName", "John", "--lastName", "Doe"], Option.some(20)]) @@ -245,7 +245,7 @@ describe.concurrent("Options", () => { it.effect("validate provides a suggestion if a provided option with a default is close to a specified option", () => Effect.gen(function*($) { const config = CliConfig.defaultConfig - const option = Options.withDefault(Options.text("firstName"), "Jack") + const option = Options.text("firstName").pipe(Options.withDefault("Jack")) const args = ["--firstme", "Alice"] const result = yield* $(Effect.flip(Options.validate(option, args, config))) expect(result).toEqual(ValidationError.invalidValue(HelpDoc.p(Span.error( @@ -300,8 +300,7 @@ describe.concurrent("Options", () => { it.effect("orElse - invalid option provided with a default", () => Effect.gen(function*($) { const config = CliConfig.defaultConfig - const option = pipe( - Options.integer("min"), + const option = Options.integer("min").pipe( Options.orElse(Options.integer("max")), Options.withDefault(0) )