Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/many-badgers-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

Allow schemas provided to CLI flags / arguments to utilize the environment required by the CLI
17 changes: 9 additions & 8 deletions packages/effect/src/unstable/cli/Argument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type * as Redacted from "../../Redacted.ts"
import type * as Result from "../../Result.ts"
import type * as Schema from "../../Schema.ts"
import type * as CliError from "./CliError.ts"
import type { Environment } from "./Command.ts"
import * as Param from "./Param.ts"
import type * as Primitive from "./Primitive.ts"

Expand Down Expand Up @@ -261,7 +262,7 @@ export const fileParse = (
*/
export const fileSchema = <A>(
name: string,
schema: Schema.Decoder<A>,
schema: Schema.Decoder<A, Environment>,
options?: Primitive.FileSchemaOptions | undefined
): Argument<A> => Param.fileSchema(Param.argumentKind, name, schema, options)

Expand Down Expand Up @@ -339,11 +340,11 @@ export const withDescription: {
*/
export const withDefault: {
<const B>(
defaultValue: B | Effect.Effect<B, CliError.CliError, Param.Environment>
defaultValue: B | Effect.Effect<B, CliError.CliError, Environment>
): <A>(self: Argument<A>) => Argument<A | B>
<A, const B>(
self: Argument<A>,
defaultValue: B | Effect.Effect<B, CliError.CliError, Param.Environment>
defaultValue: B | Effect.Effect<B, CliError.CliError, Environment>
): Argument<A | B>
} = Param.withDefault

Expand Down Expand Up @@ -471,15 +472,15 @@ export const map: {
*/
export const mapEffect: {
<A, B>(
f: (a: A) => Effect.Effect<B, CliError.CliError, Param.Environment>
f: (a: A) => Effect.Effect<B, CliError.CliError, Environment>
): (self: Argument<A>) => Argument<B>
<A, B>(
self: Argument<A>,
f: (a: A) => Effect.Effect<B, CliError.CliError, Param.Environment>
f: (a: A) => Effect.Effect<B, CliError.CliError, Environment>
): Argument<B>
} = dual(2, <A, B>(
self: Argument<A>,
f: (a: A) => Effect.Effect<B, CliError.CliError, Param.Environment>
f: (a: A) => Effect.Effect<B, CliError.CliError, Environment>
) => Param.mapEffect(self, f))

/**
Expand Down Expand Up @@ -589,8 +590,8 @@ export const between: {
* @since 4.0.0
*/
export const withSchema: {
<A, B>(schema: Schema.Codec<B, A>): (self: Argument<A>) => Argument<B>
<A, B>(self: Argument<A>, schema: Schema.Codec<B, A>): Argument<B>
<A, B>(schema: Schema.Codec<B, A, Environment, Environment>): (self: Argument<A>) => Argument<B>
<A, B>(self: Argument<A>, schema: Schema.Codec<B, A, Environment, Environment>): Argument<B>
} = dual(2, <A, B>(self: Argument<A>, schema: Schema.Codec<B, A>) => Param.withSchema(self, schema))

/**
Expand Down
17 changes: 9 additions & 8 deletions packages/effect/src/unstable/cli/Flag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type * as Redacted from "../../Redacted.ts"
import type * as Result from "../../Result.ts"
import type * as Schema from "../../Schema.ts"
import type * as CliError from "./CliError.ts"
import type { Environment } from "./Command.ts"
import * as Param from "./Param.ts"
import type * as Primitive from "./Primitive.ts"

Expand Down Expand Up @@ -355,7 +356,7 @@ export const fileParse = (
*/
export const fileSchema = <A>(
name: string,
schema: Schema.Decoder<A>,
schema: Schema.Decoder<A, Environment>,
options?: Primitive.FileSchemaOptions | undefined
): Flag<A> => Param.fileSchema(Param.flagKind, name, schema, options)

Expand Down Expand Up @@ -579,8 +580,8 @@ export const optional = <A>(param: Flag<A>): Flag<Option.Option<A>> => Param.opt
* @since 4.0.0
*/
export const withDefault: {
<const B>(defaultValue: B | Effect.Effect<B, CliError.CliError, Param.Environment>): <A>(self: Flag<A>) => Flag<A | B>
<A, const B>(self: Flag<A>, defaultValue: B | Effect.Effect<B, CliError.CliError, Param.Environment>): Flag<A | B>
<const B>(defaultValue: B | Effect.Effect<B, CliError.CliError, Environment>): <A>(self: Flag<A>) => Flag<A | B>
<A, const B>(self: Flag<A>, defaultValue: B | Effect.Effect<B, CliError.CliError, Environment>): Flag<A | B>
} = Param.withDefault

/**
Expand Down Expand Up @@ -677,15 +678,15 @@ export const map: {
*/
export const mapEffect: {
<A, B>(
f: (a: A) => Effect.Effect<B, CliError.CliError, Param.Environment>
f: (a: A) => Effect.Effect<B, CliError.CliError, Environment>
): (self: Flag<A>) => Flag<B>
<A, B>(
self: Flag<A>,
f: (a: A) => Effect.Effect<B, CliError.CliError, Param.Environment>
f: (a: A) => Effect.Effect<B, CliError.CliError, Environment>
): Flag<B>
} = dual(2, <A, B>(
self: Flag<A>,
f: (a: A) => Effect.Effect<B, CliError.CliError, Param.Environment>
f: (a: A) => Effect.Effect<B, CliError.CliError, Environment>
) => Param.mapEffect(self, f))

/**
Expand Down Expand Up @@ -981,6 +982,6 @@ export const orElseResult: {
* @since 4.0.0
*/
export const withSchema: {
<A, B>(schema: Schema.Codec<B, A>): (self: Flag<A>) => Flag<B>
<A, B>(self: Flag<A>, schema: Schema.Codec<B, A>): Flag<B>
<A, B>(schema: Schema.Codec<B, A, Environment, Environment>): (self: Flag<A>) => Flag<B>
<A, B>(self: Flag<A>, schema: Schema.Codec<B, A, Environment, Environment>): Flag<B>
} = dual(2, <A, B>(self: Flag<A>, schema: Schema.Codec<B, A>) => Param.withSchema(self, schema))
27 changes: 11 additions & 16 deletions packages/effect/src/unstable/cli/Param.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,16 @@
*/
import * as Config from "../../Config.ts"
import * as Effect from "../../Effect.ts"
import type * as FileSystem from "../../FileSystem.ts"
import { dual, identity } from "../../Function.ts"
import * as Option from "../../Option.ts"
import type * as Path from "../../Path.ts"
import { type Pipeable, pipeArguments } from "../../Pipeable.ts"
import * as Predicate from "../../Predicate.ts"
import type * as Redacted from "../../Redacted.ts"
import * as Result from "../../Result.ts"
import * as Schema from "../../Schema.ts"
import type * as Terminal from "../../Terminal.ts"
import type { Covariant } from "../../Types.ts"
import type { ChildProcessSpawner } from "../process/ChildProcessSpawner.ts"
import * as CliError from "./CliError.ts"
import type { Environment } from "./Command.ts"
import * as Primitive from "./Primitive.ts"
import * as Prompt from "./Prompt.ts"

Expand Down Expand Up @@ -56,15 +53,6 @@ export interface Param<Kind extends ParamKind, out A> extends Param.Variance<A>
*/
export type ParamKind = "argument" | "flag"

/**
* Services that parameter parsing can require, such as filesystem, path,
* terminal, and child-process support.
*
* @category models
* @since 4.0.0
*/
export type Environment = FileSystem.FileSystem | Path.Path | Terminal.Terminal | ChildProcessSpawner

/**
* Defines the kind discriminator for positional argument parameters.
*
Expand Down Expand Up @@ -859,7 +847,7 @@ export const fileParse = <Kind extends ParamKind>(
export const fileSchema = <Kind extends ParamKind, A>(
kind: Kind,
name: string,
schema: Schema.Decoder<A>,
schema: Schema.Decoder<A, Environment>,
options?: Primitive.FileSchemaOptions | undefined
): Param<Kind, A> =>
makeSingle({
Expand Down Expand Up @@ -1799,8 +1787,15 @@ export const withMetavar: {
* @since 4.0.0
*/
export const withSchema: {
<A, B>(schema: Schema.Codec<B, A>): <Kind extends ParamKind>(self: Param<Kind, A>) => Param<Kind, B>
<Kind extends ParamKind, A, B>(self: Param<Kind, A>, schema: Schema.Codec<B, A>): Param<Kind, B>
<A, B>(
schema: Schema.Codec<B, A, Environment, Environment>
): <Kind extends ParamKind>(
self: Param<Kind, A>
) => Param<Kind, B>
<Kind extends ParamKind, A, B>(
self: Param<Kind, A>,
schema: Schema.Codec<B, A, Environment, Environment>
): Param<Kind, B>
} = dual(2, <Kind extends ParamKind, A, B>(
self: Param<Kind, A>,
schema: Schema.Codec<B, A>
Expand Down
11 changes: 5 additions & 6 deletions packages/effect/src/unstable/cli/Primitive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import * as Schema from "../../Schema.ts"
import type { Formatter } from "../../SchemaIssue.ts"
import type * as Struct from "../../Struct.ts"
import type { Covariant } from "../../Types.ts"
import type { Environment } from "./Command.ts"

const TypeId = "~effect/cli/Primitive"

Expand Down Expand Up @@ -58,7 +59,7 @@ const TypeId = "~effect/cli/Primitive"
*/
export interface Primitive<out A> extends Primitive.Variance<A> {
readonly _tag: string
readonly parse: (value: string) => Effect.Effect<A, string, FileSystem.FileSystem | Path.Path>
readonly parse: (value: string) => Effect.Effect<A, string, Environment>
}

/**
Expand Down Expand Up @@ -97,9 +98,7 @@ export const isBoolean = (p: Primitive<unknown>): p is Primitive<boolean> => p._

const makePrimitive = <A>(
tag: string,
parse: (
value: string
) => Effect.Effect<A, string, FileSystem.FileSystem | Path.Path>
parse: (value: string) => Effect.Effect<A, string, Environment>
): Primitive<A> =>
Object.assign(Object.create(Proto), {
_tag: tag,
Expand All @@ -108,7 +107,7 @@ const makePrimitive = <A>(

const makeSchemaPrimitive = <T, E>(
tag: string,
schema: Schema.Codec<T, E>
schema: Schema.Codec<T, E, Environment, Environment>
): Primitive<T> => {
const toCodecStringTree = Schema.toCodecStringTree(schema)
const decode = Schema.decodeUnknownEffect(toCodecStringTree)
Expand Down Expand Up @@ -614,7 +613,7 @@ export type FileSchemaOptions = Struct.Simplify<
* @since 4.0.0
*/
export const fileSchema = <A>(
schema: Schema.Decoder<A>,
schema: Schema.Decoder<A, Environment>,
options?: FileSchemaOptions | undefined
): Primitive<A> => {
const decode = Schema.decodeUnknownEffect(schema)
Expand Down
4 changes: 2 additions & 2 deletions packages/effect/src/unstable/cli/internal/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import * as Effect from "../../../Effect.ts"
import * as Option from "../../../Option.ts"
import * as CliError from "../CliError.ts"
import type { Command, ParsedTokens } from "../Command.ts"
import type { Command, Environment, ParsedTokens } from "../Command.ts"
import * as Param from "../Param.ts"
import * as Primitive from "../Primitive.ts"
import { suggest } from "./auto-suggest.ts"
Expand All @@ -50,7 +50,7 @@ export const parseArgs = (
lexResult: LexResult,
command: Command.Any,
commandPath: ReadonlyArray<string> = []
): Effect.Effect<ParsedTokens, never, Param.Environment> =>
): Effect.Effect<ParsedTokens, never, Environment> =>
Effect.gen(function*() {
const { tokens, trailingOperands: afterEndOfOptions } = lexResult
const newCommandPath = [...commandPath, command.name]
Expand Down
10 changes: 8 additions & 2 deletions packages/effect/test/unstable/cli/Arguments.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { assert, describe, expect, it } from "@effect/vitest"
import { Effect, FileSystem, Layer, Option, Path, PlatformError, Ref, Result } from "effect"
import { Effect, FileSystem, Layer, Option, Path, PlatformError, Ref, Result, Stdio } from "effect"
import { TestConsole } from "effect/testing"
import { Argument, CliOutput, Command, Flag } from "effect/unstable/cli"
import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"
Expand Down Expand Up @@ -32,14 +32,20 @@ const CliOutputLayer = CliOutput.layer(
colors: false
})
)
const StdioLayer = Stdio.layerTest({})
const ChildProcessSpawnerLayer = Layer.succeed(
ChildProcessSpawner.ChildProcessSpawner,
ChildProcessSpawner.make(() => Effect.die("Not implemented"))
)

const TestLayer = Layer.mergeAll(
ConsoleLayer,
FileSystemLayer,
PathLayer,
TerminalLayer,
CliOutputLayer,
Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make(() => Effect.die("Not implemented")))
StdioLayer,
ChildProcessSpawnerLayer
)

describe("Command arguments", () => {
Expand Down
10 changes: 8 additions & 2 deletions packages/effect/test/unstable/cli/Param.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { assert, describe, it } from "@effect/vitest"
import { Config, ConfigProvider, Effect, FileSystem, Layer, Option, Path, Ref } from "effect"
import { Config, ConfigProvider, Effect, FileSystem, Layer, Option, Path, Ref, Stdio } from "effect"
import { TestConsole } from "effect/testing"
import { Argument, CliError, Flag, Prompt } from "effect/unstable/cli"
import { ChildProcessSpawner } from "effect/unstable/process"
Expand All @@ -9,13 +9,19 @@ const ConsoleLayer = TestConsole.layer
const FileSystemLayer = FileSystem.layerNoop({})
const PathLayer = Path.layer
const TerminalLayer = MockTerminal.layer
const StdioLayer = Stdio.layerTest({})
const ChildProcessSpawnerLayer = Layer.succeed(
ChildProcessSpawner.ChildProcessSpawner,
ChildProcessSpawner.make(() => Effect.die("Not implemented"))
)

const TestLayer = Layer.mergeAll(
ConsoleLayer,
FileSystemLayer,
PathLayer,
TerminalLayer,
Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make(() => Effect.die("Not implemented")))
StdioLayer,
ChildProcessSpawnerLayer
)

describe("Param", () => {
Expand Down
18 changes: 16 additions & 2 deletions packages/effect/test/unstable/cli/Primitive.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
import { assert, describe, it } from "@effect/vitest"
import { Effect, FileSystem, Layer, Path, PlatformError, Redacted } from "effect"
import { Effect, FileSystem, Layer, Path, PlatformError, Redacted, Stdio } from "effect"
import { TestConsole } from "effect/testing/index"
import { Primitive } from "effect/unstable/cli"
import { ChildProcessSpawner } from "effect/unstable/process"
import * as MockTerminal from "./services/MockTerminal.ts"

const ConsoleLayer = TestConsole.layer
const FileSystemLayer = FileSystem.layerNoop({})
const PathLayer = Path.layer
const TerminalLayer = MockTerminal.layer
const StdioLayer = Stdio.layerTest({})
const ChildProcessSpawnerLayer = Layer.succeed(
ChildProcessSpawner.ChildProcessSpawner,
ChildProcessSpawner.make(() => Effect.die("Not implemented"))
)

const TestLayer = Layer.mergeAll(
ConsoleLayer,
FileSystemLayer,
PathLayer
PathLayer,
TerminalLayer,
StdioLayer,
ChildProcessSpawnerLayer
)

// Helper functions to reduce repetition
Expand Down
Loading