EdgeAIG's picture
download
raw
43.1 kB
/**
* Defines the shared parameter model for CLI arguments and flags.
*
* A `Param<Kind, A>` describes how to consume parsed command-line input and
* return a typed value. The `Kind` decides whether the parameter reads
* positional arguments or named flags. `Argument` and `Flag` build on this
* module to share parsing structure, primitive constructors, help metadata,
* aliases, defaults, prompts, configuration fallbacks, validation, schema
* decoding, fallback parameters, and traversal helpers.
*
* @since 4.0.0
*/
import * as Config from "../../Config.js";
import * as Effect from "../../Effect.js";
import { dual, identity } from "../../Function.js";
import * as Option from "../../Option.js";
import { pipeArguments } from "../../Pipeable.js";
import * as Predicate from "../../Predicate.js";
import * as Result from "../../Result.js";
import * as Schema from "../../Schema.js";
import * as CliError from "./CliError.js";
import * as Primitive from "./Primitive.js";
import * as Prompt from "./Prompt.js";
const TypeId = "~effect/cli/Param";
/**
* Defines the kind discriminator for positional argument parameters.
*
* **When to use**
*
* Use to build low-level `Param` constructors or type positions for positional
* argument parameters.
*
* @see {@link flagKind} for the named flag parameter discriminator
* @see {@link ParamKind} for the full parameter kind union
*
* @category constants
* @since 4.0.0
*/
export const argumentKind = "argument";
/**
* Defines the kind discriminator for flag parameters.
*
* **When to use**
*
* Use to build low-level `Param` constructors or type positions for named flag
* parameters.
*
* @see {@link argumentKind} for the positional argument parameter discriminator
*
* @category constants
* @since 4.0.0
*/
export const flagKind = "flag";
const Proto = {
[TypeId]: {
_A: identity
},
pipe() {
return pipeArguments(this, arguments);
}
};
/**
* Type guard to check if a value is a Param.
*
* **Example** (Checking for params)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* const maybeParam = Param.string(Param.flagKind, "name")
*
* if (Param.isParam(maybeParam)) {
* console.log("This is a Param")
* }
* ```
*
* @category refinements
* @since 4.0.0
*/
export const isParam = u => Predicate.hasProperty(u, TypeId);
/**
* Type guard to check if a param is a Single param (not composed).
*
* **Example** (Checking for single params)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* const nameParam = Param.string(Param.flagKind, "name")
* const optionalParam = Param.optional(nameParam)
*
* console.log(Param.isSingle(nameParam)) // true
* console.log(Param.isSingle(optionalParam)) // false
* ```
*
* @category refinements
* @since 4.0.0
*/
export const isSingle = param => Predicate.isTagged(param, "Single");
/**
* Type guard to check if a Single param is a flag (not an argument).
*
* @internal
*/
export const isFlagParam = single => single.kind === "flag";
/**
* Constructs a leaf `Single` parameter from its kind, name, primitive parser,
* and optional help metadata.
*
* **Details**
*
* The returned parser reads either one positional argument or the named flag,
* depending on `kind`.
*
* @category constructors
* @since 4.0.0
*/
export const makeSingle = params => {
const parse = args => params.kind === argumentKind ? parsePositional(params.name, params.primitiveType, args) : parseFlag(params.name, params.primitiveType, args);
return Object.assign(Object.create(Proto), {
_tag: "Single",
...params,
description: params.description ?? Option.none(),
aliases: params.aliases ?? [],
hidden: params.hidden ?? false,
parse
});
};
/**
* Creates a string parameter.
*
* **Example** (Creating string parameters)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* // Create a string flag
* const nameFlag = Param.string(Param.flagKind, "name")
*
* // Create a string argument
* const fileArg = Param.string(Param.argumentKind, "file")
*
* // Usage in CLI: --name "John Doe" or as positional argument
* ```
*
* @category constructors
* @since 4.0.0
*/
export const string = (kind, name) => makeSingle({
name,
primitiveType: Primitive.string,
kind
});
/**
* Creates a boolean parameter.
*
* **Example** (Creating boolean parameters)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* // Create a boolean flag
* const verboseFlag = Param.boolean(Param.flagKind, "verbose")
*
* // Create a boolean argument
* const enableArg = Param.boolean(Param.argumentKind, "enable")
*
* // Usage in CLI: --verbose (defaults to true when present, false when absent)
* // or as positional: true/false
* ```
*
* @category constructors
* @since 4.0.0
*/
export const boolean = (kind, name) => makeSingle({
name,
primitiveType: Primitive.boolean,
kind
});
/**
* Creates an integer parameter.
*
* **Example** (Creating integer parameters)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* // Create an integer flag
* const portFlag = Param.integer(Param.flagKind, "port")
*
* // Create an integer argument
* const countArg = Param.integer(Param.argumentKind, "count")
*
* // Usage in CLI: --port 8080 or as positional argument: 42
* ```
*
* @category constructors
* @since 4.0.0
*/
export const integer = (kind, name) => makeSingle({
name,
primitiveType: Primitive.integer,
kind
});
/**
* Creates a floating-point number parameter.
*
* **Example** (Creating float parameters)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* // Create a float flag
* const rateFlag = Param.float(Param.flagKind, "rate")
*
* // Create a float argument
* const thresholdArg = Param.float(Param.argumentKind, "threshold")
*
* // Usage in CLI: --rate 0.95 or as positional argument: 3.14159
* ```
*
* @category constructors
* @since 4.0.0
*/
export const float = (kind, name) => makeSingle({
name,
primitiveType: Primitive.float,
kind
});
/**
* Creates a date parameter that parses ISO date strings.
*
* **Example** (Creating date parameters)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* // Create a date flag
* const startFlag = Param.date(Param.flagKind, "start-date")
*
* // Create a date argument
* const dueDateArg = Param.date(Param.argumentKind, "due-date")
*
* // Usage in CLI: --start-date "2023-12-25" or as positional: "2023-01-01"
* // Parses to JavaScript Date object
* ```
*
* @category constructors
* @since 4.0.0
*/
export const date = (kind, name) => makeSingle({
name,
primitiveType: Primitive.date,
kind
});
/**
* Constructs command-line params that represent a choice between several
* inputs. The input will be mapped to it's associated value during parsing.
*
* **Example** (Creating valued choices)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* type Animal = Dog | Cat
*
* interface Dog {
* readonly _tag: "Dog"
* }
*
* interface Cat {
* readonly _tag: "Cat"
* }
*
* const animal = Param.choiceWithValue(Param.flagKind, "animal", [
* ["dog", { _tag: "Dog" }],
* ["cat", { _tag: "Cat" }]
* ])
* ```
*
* @category constructors
* @since 4.0.0
*/
export const choiceWithValue = (kind, name, choices) => makeSingle({
name,
primitiveType: Primitive.choice(choices),
kind
});
/**
* Constructs command-line params that represent a choice between several
* string inputs.
*
* **Example** (Creating string choices)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* const logLevel = Param.choice(Param.flagKind, "log-level", [
* "debug",
* "info",
* "warn",
* "error"
* ])
* ```
*
* @category constructors
* @since 4.0.0
*/
export const choice = (kind, name, choices) => {
const mappedChoices = choices.map(value => [value, value]);
return choiceWithValue(kind, name, mappedChoices);
};
/**
* Creates a path parameter that accepts file or directory paths.
*
* **Example** (Creating path parameters)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* // Basic path parameter
* const outputPath = Param.path(Param.flagKind, "output")
*
* // Path that must exist
* const inputPath = Param.path(Param.flagKind, "input", { mustExist: true })
*
* // File-only path
* const configFile = Param.path(Param.flagKind, "config", {
* pathType: "file",
* mustExist: true,
* typeName: "config-file"
* })
* ```
*
* @category constructors
* @since 4.0.0
*/
export const path = (kind, name, options) => makeSingle({
name,
kind,
primitiveType: Primitive.path(options?.pathType ?? "either", options?.mustExist),
typeName: options?.typeName
});
/**
* Creates a directory path parameter.
*
* **Details**
*
* This is a convenience function that creates a path parameter with the
* `pathType` set to `"directory"` and a default type name of `"directory"`.
*
* **Example** (Creating directory parameters)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* // Basic directory parameter
* const outputDir = Param.directory(Param.flagKind, "output-dir")
*
* // Directory that must exist
* const sourceDir = Param.directory(Param.flagKind, "source", { mustExist: true })
*
* // Usage: --output-dir /path/to/dir --source /existing/dir
* ```
*
* @category constructors
* @since 4.0.0
*/
export const directory = (kind, name, options) => path(kind, name, {
pathType: "directory",
typeName: "directory",
mustExist: options?.mustExist
});
/**
* Creates a file path parameter.
*
* **Details**
*
* This is a convenience function that creates a path parameter with a
* `pathType` set to `"file"` and a default type name of `"file"`.
*
* **Example** (Creating file parameters)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* // Basic file parameter
* const outputFile = Param.file(Param.flagKind, "output")
*
* // File that must exist
* const inputFile = Param.file(Param.flagKind, "input", { mustExist: true })
*
* // Usage: --output result.txt --input existing-file.txt
* ```
*
* @category constructors
* @since 4.0.0
*/
export const file = (kind, name, options) => path(kind, name, {
pathType: "file",
typeName: "file",
mustExist: options?.mustExist
});
/**
* Creates a redacted parameter for sensitive data like passwords.
* The value is masked in help output and logging.
*
* **Example** (Creating redacted parameters)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* // Create a password parameter
* const password = Param.redacted(Param.flagKind, "password")
*
* // Create an API key argument
* const apiKey = Param.redacted(Param.argumentKind, "api-key")
*
* // Usage: --password (value will be hidden in help/logs)
* ```
*
* @category constructors
* @since 4.0.0
*/
export const redacted = (kind, name) => makeSingle({
name,
primitiveType: Primitive.redacted,
kind
});
/**
* Creates a parameter that reads and returns file content as a string.
*
* **Example** (Reading file text)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* // Read a config file as string
* const configContent = Param.fileText(Param.flagKind, "config")
*
* // Read a template file as argument
* const templateContent = Param.fileText(Param.argumentKind, "template")
*
* // Usage: --config config.txt (reads file content into string)
* ```
*
* @category constructors
* @since 4.0.0
*/
export const fileText = (kind, name) => makeSingle({
name,
primitiveType: Primitive.fileText,
kind
});
/**
* Creates a param that reads and parses the content of the specified file.
*
* **Details**
*
* The parser that is utilized will depend on the specified `format`, or the
* extension of the file passed on the command-line if no `format` is specified.
*
* **Example** (Parsing file contents)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* // Will use the extension of the file passed on the command line to determine
* // the parser to use
* const config = Param.fileParse(Param.flagKind, "config")
*
* // Will use the JSON parser
* const jsonConfig = Param.fileParse(Param.flagKind, "json-config", {
* format: "json"
* })
* ```
*
* @category constructors
* @since 4.0.0
*/
export const fileParse = (kind, name, options) => makeSingle({
name,
primitiveType: Primitive.fileParse(options),
kind
});
/**
* Creates a parameter that reads and validates file content using a schema.
*
* **Example** (Validating file contents)
*
* ```ts
* import { Schema } from "effect"
* import { Param } from "effect/unstable/cli"
* // @internal - this module is not exported publicly
*
* // Parse JSON config file
* const configSchema = Schema.Struct({
* port: Schema.Number,
* host: Schema.String
* })
*
* const config = Param.fileSchema(Param.flagKind, "config", configSchema, {
* format: "json"
* })
*
* // Parse YAML file
* const yamlConfig = Param.fileSchema(Param.flagKind, "config", configSchema, {
* format: "yaml"
* })
*
* // Usage: --config config.json (reads and validates file content)
* ```
*
* @category constructors
* @since 4.0.0
*/
export const fileSchema = (kind, name, schema, options) => makeSingle({
name,
primitiveType: Primitive.fileSchema(schema, options),
kind
});
/**
* Creates a param that parses key=value pairs.
*
* **When to use**
*
* Use when you need command-line options or arguments that collect `key=value`
* configuration entries.
*
* **Details**
*
* Requires at least one key=value pair. The parsed pairs are merged into a
* single record object.
*
* **Example** (Parsing key-value pairs)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* const env = Param.keyValuePair(Param.flagKind, "env")
* // --env FOO=bar --env BAZ=qux will parse to { FOO: "bar", BAZ: "qux" }
*
* const props = Param.keyValuePair(Param.flagKind, "property")
* // --property name=value --property debug=true
* ```
*
* @category constructors
* @since 4.0.0
*/
export const keyValuePair = (kind, name) => map(variadic(makeSingle({
name,
primitiveType: Primitive.keyValuePair,
kind
}), {
min: 1
}), objects => Object.assign({}, ...objects));
/**
* Creates an empty sentinel parameter that always fails to parse.
*
* **When to use**
*
* Use when you need an empty CLI parameter sentinel for optional parameter
* construction or internal combinators.
*
* **Example** (Creating sentinel parameters)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* const disabledDebugParam = Param.none(Param.flagKind)
*
* const makeDebugParam = (enableDebug: boolean) =>
* enableDebug ? Param.string(Param.flagKind, "debug") : disabledDebugParam
*
* console.log(makeDebugParam(true) === disabledDebugParam) // false
* console.log(makeDebugParam(false) === disabledDebugParam) // true
* ```
*
* @category constructors
* @since 4.0.0
*/
export const none = kind => makeSingle({
name: "__none__",
primitiveType: Primitive.none,
kind
});
const FLAG_DASH_REGEXP = /^-+/;
/**
* Adds an alias to an option.
*
* **When to use**
*
* Use when you need a CLI parameter to accept an alternate name, such as "-f"
* for "--force".
*
* **Details**
*
* This works on any param structure by recursively finding the underlying
* `Single` node and applying the alias there.
*
* **Example** (Adding parameter aliases)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* const force = Param.boolean(Param.flagKind, "force").pipe(
* Param.withAlias("-f"),
* Param.withAlias("-F")
* )
*
* // Also works on composed params:
* const count = Param.integer(Param.flagKind, "count").pipe(
* Param.optional,
* Param.withAlias("-c") // finds the underlying Single and adds alias
* )
* ```
*
* @category combinators
* @since 4.0.0
*/
export const withAlias = /*#__PURE__*/dual(2, (self, alias) => {
return transformSingle(self, single => makeSingle({
...single,
aliases: [...single.aliases, alias.replace(FLAG_DASH_REGEXP, "")]
}));
});
/**
* Adds a description to an option for help text.
*
* **Details**
*
* Descriptions provide users with information about what the option does
* when they view help documentation.
*
* **Example** (Adding help descriptions)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* const verbose = Param.boolean(Param.flagKind, "verbose").pipe(
* Param.withAlias("-v"),
* Param.withDescription("Enable verbose output")
* )
* ```
*
* @category combinators
* @since 4.0.0
*/
export const withDescription = /*#__PURE__*/dual(2, (self, description) => {
return transformSingle(self, single => makeSingle({
...single,
description: Option.some(description)
}));
});
/**
* Hides a parameter from generated help output and completions while keeping
* it parseable on the command line.
*
* **When to use**
*
* Use when experimental, internal, or deprecated flags should be accepted but
* not advertised.
*
* **Example** (Hiding a flag from help)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* const experimental = Param.boolean(Param.flagKind, "experimental-foo").pipe(
* Param.withHidden
* )
* ```
*
* @category metadata
* @since 4.0.0
*/
export const withHidden = self => transformSingle(self, single => makeSingle({
...single,
hidden: true
}));
/**
* Transforms the parsed value of an option using a mapping function.
*
* **Example** (Mapping parsed values)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* const port = Param.integer(Param.flagKind, "port").pipe(
* Param.map((n) => ({ port: n, url: `http://localhost:${n}` }))
* )
* ```
*
* @category combinators
* @since 4.0.0
*/
export const map = /*#__PURE__*/dual(2, (self, f) => {
const parse = args => Effect.map(self.parse(args), ([operands, value]) => [operands, f(value)]);
return Object.assign(Object.create(Proto), {
_tag: "Map",
kind: self.kind,
param: self,
f,
parse
});
});
const transform = (self, f) => Object.assign(Object.create(Proto), {
_tag: "Transform",
kind: self.kind,
param: self,
f,
parse: f(self.parse)
});
/**
* Transforms the parsed value of an option using an effectful mapping function.
*
* **Example** (Mapping parsed values effectfully)
*
* ```ts
* import { Effect } from "effect"
* import { CliError, Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* const validatedEmail = Param.string(Param.flagKind, "email").pipe(
* Param.mapEffect((email) =>
* email.includes("@")
* ? Effect.succeed(email)
* : Effect.fail(
* new CliError.InvalidValue({
* option: "email",
* value: email,
* expected: "valid email format",
* kind: "flag"
* })
* )
* )
* )
* ```
*
* @category combinators
* @since 4.0.0
*/
export const mapEffect = /*#__PURE__*/dual(2, (self, f) => transform(self, parse => args => Effect.flatMap(parse(args), ([leftover, a]) => f(a).pipe(Effect.map(b => [leftover, b])))));
/**
* Transforms the parsed value of an option using a function that may throw,
* converting any thrown errors into failure messages.
*
* **Example** (Mapping thrown errors)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* const parsedJson = Param.string(Param.flagKind, "config").pipe(
* Param.mapTryCatch(
* (str) => JSON.parse(str),
* (error) =>
* `Invalid JSON: ${error instanceof Error ? error.message : String(error)}`
* )
* )
* ```
*
* @category combinators
* @since 4.0.0
*/
export const mapTryCatch = /*#__PURE__*/dual(3, (self, f, onError) => {
const single = getUnderlyingSingleOrThrow(self);
return transform(self, parse => args => Effect.flatMap(parse(args), ([leftover, a]) => Effect.try({
try: () => f(a),
catch: error => onError(error)
}).pipe(Effect.mapError(error => new CliError.InvalidValue({
option: single.name,
value: String(a),
expected: error,
kind: single.kind
})), Effect.map(b => [leftover, b]))));
});
/**
* Makes a flag or positional argument optional.
*
* **Details**
*
* When the parameter is absent, parsing succeeds with `Option.none()` instead
* of failing with a missing option or missing argument error. When present, the
* parsed value is wrapped in `Option.some()`.
*
* **Example** (Making parameters optional)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // Create an optional port option
* // - When not provided: returns Option.none()
* // - When provided: returns Option.some(parsedValue)
* const port = Param.optional(Param.integer(Param.flagKind, "port"))
* ```
*
* @category combinators
* @since 4.0.0
*/
export const optional = param => {
const parse = Effect.fnUntraced(function* (args) {
const single = getUnderlyingSingleOrThrow(param);
// Handle boolean params that are explicitly marked as optional (i.e. the
// end user wants to return `Option.none()` instead of `false` when the
// flag (or its negated variant) are not present on the command line
if (isFlagParam(single) && Primitive.isBoolean(single.primitiveType) && ![single.name, ...single.aliases].some(name => (args.flags[name] ?? []).length > 0)) {
return [args.arguments, Option.none()];
}
return yield* param.parse(args).pipe(Effect.map(([leftover, value]) => [leftover, Option.some(value)]),
// Catch both MissingOption (for flags) and MissingArgument (for positional arguments)
Effect.catchTags({
MissingOption: () => Effect.succeed([args.arguments, Option.none()]),
MissingArgument: () => Effect.succeed([args.arguments, Option.none()])
}));
});
return Object.assign(Object.create(Proto), {
_tag: "Optional",
kind: param.kind,
param,
parse
});
};
/**
* Makes a flag or positional argument optional by supplying a fallback value.
*
* **Details**
*
* The fallback may be a pure value or an effect. It is used only when the
* parameter is absent; provided values are parsed normally.
*
* **Example** (Providing default values)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* // Using the pipe operator to make an option optional
* const port = Param.integer(Param.flagKind, "port").pipe(
* Param.withDefault(8080)
* )
*
* // Can also be used with other combinators
* const verbose = Param.boolean(Param.flagKind, "verbose").pipe(
* Param.withAlias("-v"),
* Param.withDescription("Enable verbose output"),
* Param.withDefault(false)
* )
* ```
*
* @category combinators
* @since 4.0.0
*/
export const withDefault = /*#__PURE__*/dual(2, (self, defaultValue) => {
if (!Effect.isEffect(defaultValue)) {
return map(optional(self), Option.getOrElse(() => defaultValue));
}
return mapEffect(optional(self), Option.match({
onNone: () => defaultValue,
onSome: Effect.succeed
}));
});
/**
* Adds a fallback config that is loaded when a required parameter is missing.
*
* **When to use**
*
* Use when you need config to provide a fallback source for required flags or
* arguments that are absent from CLI input.
*
* **Details**
*
* Provided CLI values win. Config is loaded only after a missing option or
* missing argument error.
*
* **Gotchas**
*
* Missing config preserves the original missing-parameter error. Config parse
* failure becomes `CliError.InvalidValue`.
*
* @see {@link withDefault} for a pure default value
* @see {@link withFallbackPrompt} for prompting interactively when input is missing
*
* @category combinators
* @since 4.0.0
*/
export const withFallbackConfig = /*#__PURE__*/dual(2, (self, config) => {
const toInvalidValue = (error, configError) => new CliError.InvalidValue({
option: error._tag === "MissingOption" ? error.option : error.argument,
value: "config",
expected: configError.message,
kind: error._tag === "MissingOption" ? "flag" : "argument"
});
const runConfig = (error, args) => Config.option(config).pipe(Effect.mapError(configError => toInvalidValue(error, configError)), Effect.flatMap(Option.match({
onNone: () => Effect.fail(error),
onSome: value => Effect.succeed([args.arguments, value])
})));
return transform(self, parse => args => parse(args).pipe(Effect.catchTag(["MissingOption", "MissingArgument"], error => runConfig(error, args))));
});
/**
* Adds a fallback prompt that is shown when a required parameter is missing.
*
* **When to use**
*
* Use when a CLI should ask interactively for a missing required flag or
* argument.
*
* **Details**
*
* `FallbackPrompt` accepts either a `Prompt` or an effect that builds one.
* Effectful prompt creation is lazy and runs only when the fallback is needed.
*
* **Gotchas**
*
* This only handles missing options and missing arguments. Invalid values do not
* prompt, and prompt cancellation re-fails with the original missing error.
*
* @see {@link FallbackPrompt} for accepted fallback prompt forms
* @see {@link withFallbackConfig} for loading a fallback from config
* @see {@link withDefault} for a pure default value
*
* @category combinators
* @since 4.0.0
*/
export const withFallbackPrompt = /*#__PURE__*/dual(2, (self, prompt) => {
const runPrompt = (error, args) => Effect.flatMap(Prompt.isPrompt(prompt) ? Effect.succeed(prompt) : prompt, Prompt.run).pipe(Effect.map(value => [args.arguments, value]), Effect.catchTag("QuitError", () => Effect.fail(error)));
return transform(self, parse => args => parse(args).pipe(Effect.catchTag(["MissingOption", "MissingArgument"], error => runPrompt(error, args))));
});
/**
* Creates a variadic parameter that can be specified multiple times.
*
* **Details**
*
* This is the base combinator for creating parameters that accept multiple values.
* The `min` and `max` parameters are optional. When they are not provided, the
* parameter can be specified any number of times, from 0 to infinity.
*
* **Example** (Accepting multiple values)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* // Basic variadic parameter (0 to infinity)
* const tags = Param.variadic(Param.string(Param.flagKind, "tag"))
*
* // Variadic with minimum count
* const inputs = Param.variadic(
* Param.string(Param.flagKind, "input"),
* { min: 1 } // at least 1 required
* )
*
* // Variadic with both min and max
* const limited = Param.variadic(Param.string(Param.flagKind, "item"), {
* min: 2, // at least 2 times
* max: 2 // at most 2 times
* })
* ```
*
* @category combinators
* @since 4.0.0
*/
export const variadic = (self, options) => {
const single = getUnderlyingSingleOrThrow(self);
const parse = args => {
if (single.kind === "argument") {
return parsePositionalVariadic(self, single, args, options);
} else {
return parseOptionVariadic(self, single, args, options);
}
};
return Object.assign(Object.create(Proto), {
_tag: "Variadic",
kind: self.kind,
param: self,
min: Option.fromUndefinedOr(options?.min),
max: Option.fromUndefinedOr(options?.max),
parse
});
};
/**
* Wraps an option to allow it to be specified multiple times within a range.
*
* **Details**
*
* This combinator transforms an option to accept between `min` and `max`
* occurrences on the command line, returning an array of all provided values.
*
* **Example** (Bounding repeated values)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* // Allow 1-3 file inputs
* const files = Param.string(Param.flagKind, "file").pipe(
* Param.between(1, 3),
* Param.withAlias("-f")
* )
*
* // Parse: --file a.txt --file b.txt
* // Result: ["a.txt", "b.txt"]
*
* // Allow 0 or more tags
* const tags = Param.string(Param.flagKind, "tag").pipe(
* Param.between(0, Number.MAX_SAFE_INTEGER)
* )
*
* // Parse: --tag dev --tag staging --tag v1.0
* // Result: ["dev", "staging", "v1.0"]
* ```
*
* @category combinators
* @since 4.0.0
*/
export const between = /*#__PURE__*/dual(3, (self, min, max) => {
if (min < 0) {
throw new Error("between: min must be non-negative");
}
if (max < min) {
throw new Error("between: max must be greater than or equal to min");
}
return variadic(self, {
min,
max
});
});
/**
* Wraps an option to allow it to be specified at most `max` times.
*
* **Details**
*
* This combinator transforms an option to accept between 0 and `max`
* occurrences on the command line, returning an array of all provided values.
*
* **Example** (Limiting repeated values)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* // Allow at most 3 warning suppressions
* const suppressions = Param.string(Param.flagKind, "suppress").pipe(
* Param.atMost(3)
* )
*
* // Parse: --suppress warning1 --suppress warning2
* // Result: ["warning1", "warning2"]
* ```
*
* @category combinators
* @since 4.0.0
*/
export const atMost = /*#__PURE__*/dual(2, (self, max) => {
if (max < 0) {
throw new Error("atMost: max must be non-negative");
}
return variadic(self, {
max
});
});
/**
* Wraps an option to require it to be specified at least `min` times.
*
* **Details**
*
* This combinator transforms an option to accept at least `min`
* occurrences on the command line, returning an array of all provided values.
*
* **Example** (Requiring repeated values)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* // Require at least 2 input files
* const inputs = Param.string(Param.flagKind, "input").pipe(
* Param.atLeast(2),
* Param.withAlias("-i")
* )
*
* // Parse: --input file1.txt --input file2.txt --input file3.txt
* // Result: ["file1.txt", "file2.txt", "file3.txt"]
* ```
*
* @category combinators
* @since 4.0.0
*/
export const atLeast = /*#__PURE__*/dual(2, (self, min) => {
if (min < 0) {
throw new Error("atLeast: min must be non-negative");
}
return variadic(self, {
min
});
});
/**
* Filters and transforms parsed values, failing with a custom error message
* if the filter function returns `Option.none()`.
*
* **When to use**
*
* Use when you need validation and transformation in a single parameter
* combinator.
*
* **Example** (Filtering and transforming values)
*
* ```ts
* import { Option } from "effect"
* import { Param } from "effect/unstable/cli"
* // @internal - this module is not exported publicly
*
* const positiveInt = Param.integer(Param.flagKind, "count").pipe(
* Param.filterMap(
* (n) => n > 0 ? Option.some(n) : Option.none(),
* (n) => `Expected positive integer, got ${n}`
* )
* )
* ```
*
* @category combinators
* @since 4.0.0
*/
export const filterMap = /*#__PURE__*/dual(3, (self, filter, onNone) => mapEffect(self, Effect.fnUntraced(function* (a) {
const result = filter(a);
if (Option.isSome(result)) {
return result.value;
}
const single = getUnderlyingSingleOrThrow(self);
return yield* new CliError.InvalidValue({
option: single.name,
value: String(a),
expected: onNone(a),
kind: single.kind
});
})));
/**
* Filters parsed values, failing with a custom error message if the predicate returns false.
*
* **Example** (Filtering parsed values)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* const evenNumber = Param.integer(Param.flagKind, "num").pipe(
* Param.filter(
* (n) => n % 2 === 0,
* (n) => `Expected even number, got ${n}`
* )
* )
* ```
*
* @category combinators
* @since 4.0.0
*/
export const filter = /*#__PURE__*/dual(3, (self, predicate, onFalse) => filterMap(self, Option.liftPredicate(predicate), onFalse));
/**
* Sets a custom metavar (placeholder name) for the param in help documentation.
*
* **Details**
*
* The metavar is displayed in usage text to indicate what value the user should provide.
* For example, `--output FILE` shows `FILE` as the metavar.
*
* **Example** (Setting metavars)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* const port = Param.integer(Param.flagKind, "port").pipe(
* Param.withMetavar("PORT"),
* Param.filter(
* (p) => p >= 1 && p <= 65535,
* () => "Port must be between 1 and 65535"
* )
* )
* ```
*
* @category metadata
* @since 4.0.0
*/
export const withMetavar = /*#__PURE__*/dual(2, (self, metavar) => transformSingle(self, single => makeSingle({
...single,
typeName: metavar
})));
/**
* Validates parsed values against a Schema, providing detailed error messages.
*
* **Example** (Validating with schemas)
*
* ```ts
* import { Schema } from "effect"
* import { Param } from "effect/unstable/cli"
* // @internal - this module is not exported publicly
*
* const isEmail = Schema.isPattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)
*
* const Email = Schema.String.pipe(
* Schema.check(isEmail)
* )
*
* const email = Param.string(Param.flagKind, "email").pipe(
* Param.withSchema(Email)
* )
* ```
*
* @category combinators
* @since 4.0.0
*/
export const withSchema = /*#__PURE__*/dual(2, (self, schema) => {
const decodeParam = Schema.decodeUnknownEffect(schema);
return mapEffect(self, value => Effect.mapError(decodeParam(value), error => {
const single = getUnderlyingSingleOrThrow(self);
return new CliError.InvalidValue({
option: single.name,
value: String(value),
expected: `Schema validation failed: ${error.message}`,
kind: single.kind
});
}));
});
/**
* Provides a fallback param to use if this param fails to parse.
*
* **Example** (Falling back to another parameter)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* const config = Param.file(Param.flagKind, "config").pipe(
* Param.orElse(() => Param.string(Param.flagKind, "config-url"))
* )
* ```
*
* @category combinators
* @since 4.0.0
*/
export const orElse = /*#__PURE__*/dual(2, (self, orElse) => transform(self, parse => args => Effect.catch(parse(args), err => orElse(err).parse(args))));
/**
* Provides a fallback param and returns a `Result` indicating which param
* succeeded.
*
* **Details**
*
* The original param's value is returned as `Result.succeed`, while the
* fallback param's value is returned as `Result.fail`.
*
* **Example** (Returning fallback results)
*
* ```ts
* import { Param } from "effect/unstable/cli"
*
* // @internal - this module is not exported publicly
*
* const configSource = Param.file(Param.flagKind, "config").pipe(
* Param.orElseResult(() => Param.string(Param.flagKind, "config-url"))
* )
* // Returns Result<string, string>
* ```
*
* @category combinators
* @since 4.0.0
*/
export const orElseResult = /*#__PURE__*/dual(2, (self, orElse) => {
return transform(self, parse => args => Effect.catch(Effect.map(parse(args), ([leftover, value]) => [leftover, Result.succeed(value)]), err => Effect.map(orElse(err).parse(args), ([leftover, value]) => [leftover, Result.fail(value)])));
});
// =============================================================================
// Parsing Utilities
// =============================================================================
const parsePositional = /*#__PURE__*/Effect.fnUntraced(function* (name, primitiveType, args) {
if (args.arguments.length === 0) {
return yield* new CliError.MissingArgument({
argument: name
});
}
const arg = args.arguments[0];
const value = yield* Effect.mapError(primitiveType.parse(arg), error => new CliError.InvalidValue({
option: name,
value: arg,
expected: error,
kind: "argument"
}));
return [args.arguments.slice(1), value];
});
const parseFlag = /*#__PURE__*/Effect.fnUntraced(function* (name, primitiveType, args) {
const providedValues = args.flags[name];
if (providedValues === undefined || providedValues.length === 0) {
// Option not provided (empty array due to initialization)
if (Primitive.isBoolean(primitiveType)) {
// Boolean params default to false when not present
return [args.arguments, false];
} else {
return yield* new CliError.MissingOption({
option: name
});
}
}
// Parse the first value (later we can handle multiple)
const arg = providedValues[0];
const value = yield* Effect.mapError(primitiveType.parse(arg), error => new CliError.InvalidValue({
option: name,
value: arg,
expected: error,
kind: "flag"
}));
return [args.arguments, value];
});
const parsePositionalVariadic = /*#__PURE__*/Effect.fnUntraced(function* (self, single, args, options) {
const results = [];
const minValue = options?.min ?? 0;
const maxValue = options?.max ?? Number.POSITIVE_INFINITY;
let count = 0;
let currentArgs = args.arguments;
while (currentArgs.length > 0 && count < maxValue) {
const [remainingArgs, value] = yield* self.parse({
flags: args.flags,
arguments: currentArgs
});
results.push(value);
currentArgs = remainingArgs;
count++;
}
if (count < minValue) {
return yield* new CliError.InvalidValue({
option: single.name,
value: `${count} values`,
expected: `at least ${minValue} value${minValue === 1 ? "" : "s"}`,
kind: single.kind
});
}
return [currentArgs, results];
});
const parseOptionVariadic = /*#__PURE__*/Effect.fnUntraced(function* (self, single, args, options) {
const results = [];
const names = [single.name, ...single.aliases];
const values = names.flatMap(name => args.flags[name] ?? []);
const count = values.length;
// Validate count constraints
if (Predicate.isNotUndefined(options?.min) && count < options.min) {
return yield* count === 0 ? new CliError.MissingOption({
option: single.name
}) : new CliError.InvalidValue({
option: single.name,
value: `${count} occurrences`,
expected: `at least ${options.min} value${options.min === 1 ? "" : "s"}`,
kind: single.kind
});
}
if (Predicate.isNotUndefined(options?.max) && count > options.max) {
return yield* new CliError.InvalidValue({
option: single.name,
value: `${count} occurrences`,
expected: `at most ${options.max} value${options.max === 1 ? "" : "s"}`,
kind: single.kind
});
}
// Parse each value individually
for (const value of values) {
const [, parsedValue] = yield* self.parse({
flags: {
[single.name]: [value]
},
arguments: []
});
results.push(parsedValue);
}
return [args.arguments, results];
});
/**
* Type-safe param matcher that handles the unsafe casting internally.
* This provides a clean API for pattern matching on param types while
* maintaining type safety at the call site.
*/
const matchParam = (param, patterns) => {
const p = param;
switch (p._tag) {
case "Single":
return patterns.Single(p);
case "Map":
return patterns.Map(p);
case "Transform":
return patterns.Transform(p);
case "Optional":
return patterns.Optional(p);
case "Variadic":
return patterns.Variadic(p);
}
};
/**
* Recursively transforms a param by applying a function to any `Single` nodes.
* This is used internally by combinators like `withAlias` to traverse the param tree.
*/
const transformSingle = (param, f) => {
return matchParam(param, {
Single: single => f(single),
Map: mapped => map(transformSingle(mapped.param, f), mapped.f),
Transform: mapped => transform(transformSingle(mapped.param, f), mapped.f),
Optional: p => optional(transformSingle(p.param, f)),
Variadic: p => variadic(transformSingle(p.param, f), {
min: Option.getOrUndefined(p.min),
max: Option.getOrUndefined(p.max)
})
});
};
/**
* Extracts all Single params from a potentially nested param structure.
* This handles all param combinators including Map, Transform, Optional, and Variadic.
*
* @internal
*/
export const extractSingleParams = param => {
return matchParam(param, {
Single: single => [single],
Map: mapped => extractSingleParams(mapped.param),
Transform: mapped => extractSingleParams(mapped.param),
Optional: optional => extractSingleParams(optional.param),
Variadic: variadic => extractSingleParams(variadic.param)
});
};
/**
* Gets the underlying Single param from a potentially nested param structure.
* Throws an error if there are no singles or multiple singles found.
*
* @internal
*/
export const getUnderlyingSingleOrThrow = param => {
const singles = extractSingleParams(param);
if (singles.length === 0) {
throw new Error("No Single param found in param structure");
}
if (singles.length > 1) {
throw new Error(`Multiple Single params found: ${singles.map(s => s.name).join(", ")}`);
}
return singles[0];
};
/**
* Gets param metadata by traversing the structure.
*
* @internal
*/
export const getParamMetadata = param => {
return matchParam(param, {
Single: () => ({
isOptional: false,
isVariadic: false
}),
Map: mapped => getParamMetadata(mapped.param),
Transform: mapped => getParamMetadata(mapped.param),
Optional: optional => ({
...getParamMetadata(optional.param),
isOptional: true
}),
Variadic: variadic => ({
...getParamMetadata(variadic.param),
isVariadic: true
})
});
};
//# sourceMappingURL=Param.js.map

Xet Storage Details

Size:
43.1 kB
·
Xet hash:
f151c75829c6dcb8021ff63a000d7f5e7a9488c77376127b9b96aae70b0b43bf

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.