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

Commit

Permalink
make Command, Option, Args and Primitive pipeable (#316)
Browse files Browse the repository at this point in the history
  • Loading branch information
fubhy authored Aug 3, 2023
1 parent 886f1fe commit 3f4e8c3
Show file tree
Hide file tree
Showing 11 changed files with 49 additions and 30 deletions.
5 changes: 5 additions & 0 deletions .changeset/beige-mice-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/cli": patch
---

Made `Command`, `Option`, `Args` and `Primitive` pipeable
3 changes: 2 additions & 1 deletion src/Args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -29,7 +30,7 @@ export type ArgsTypeId = typeof ArgsTypeId
* @since 1.0.0
* @category models
*/
export interface Args<A> extends Args.Variance<A> {}
export interface Args<A> extends Args.Variance<A>, Pipeable {}

/**
* @since 1.0.0
Expand Down
3 changes: 2 additions & 1 deletion src/Command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -38,7 +39,7 @@ export type CommandTypeId = typeof CommandTypeId
* @since 1.0.0
* @category models
*/
export interface Command<A> extends Command.Variance<A> {}
export interface Command<A> extends Command.Variance<A>, Pipeable {}

/**
* @since 1.0.0
Expand Down
3 changes: 2 additions & 1 deletion src/Options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -29,7 +30,7 @@ export type OptionsTypeId = typeof OptionsTypeId
* @since 1.0.0
* @category models
*/
export interface Options<A> extends Options.Variance<A> {}
export interface Options<A> extends Options.Variance<A>, Pipeable {}

/**
* @since 1.0.0
Expand Down
3 changes: 2 additions & 1 deletion src/Primitive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -37,7 +38,7 @@ export declare namespace Primitive {
* @since 1.0.0
* @category models
*/
export interface Variance<A> {
export interface Variance<A> extends Pipeable {
readonly [PrimitiveTypeId]: {
readonly _A: (_: never) => A
}
Expand Down
4 changes: 4 additions & 0 deletions src/internal/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -31,6 +32,9 @@ export type Op<Tag extends string, Body = {}> = Args.Args<never> & Body & {
const proto = {
[ArgsTypeId]: {
_A: (_: never) => _
},
pipe() {
return pipeArguments(this, arguments)
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/internal/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -41,6 +42,9 @@ const proto = {
[CommandTypeId]: {
_ArgsType: (_: never) => _,
_OptionsType: (_: never) => _
},
pipe() {
return pipeArguments(this, arguments)
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/internal/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -35,6 +36,9 @@ export type Op<Tag extends string, Body = {}> = Options.Options<never> & Body &
const proto = {
[OptionsTypeId]: {
_A: (_: never) => _
},
pipe() {
return pipeArguments(this, arguments)
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/internal/primitive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -16,6 +17,9 @@ export const PrimitiveTypeId: Primitive.PrimitiveTypeId = Symbol.for(
const proto = {
[PrimitiveTypeId]: {
_A: (_: never) => _
},
pipe() {
return pipeArguments(this, arguments)
}
}

Expand Down
19 changes: 7 additions & 12 deletions test/Command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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")])
)

Expand Down Expand Up @@ -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 })
])
Expand Down Expand Up @@ -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() })])
)
])
Expand Down
27 changes: 13 additions & 14 deletions test/Options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,23 +68,23 @@ 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"])
}))

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"])
}))

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"])
}))
Expand All @@ -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"])
Expand All @@ -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"])
Expand All @@ -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(
Expand All @@ -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(
Expand Down Expand Up @@ -184,15 +184,15 @@ 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()])
}))

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()])
Expand All @@ -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)])
Expand All @@ -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)])
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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)
)
Expand Down

0 comments on commit 3f4e8c3

Please sign in to comment.