EdgeAIG's picture
download
raw
17.7 kB
/**
* Parses raw command-line strings into typed values.
*
* A `Primitive<A>` receives one string and returns an `Effect` that either
* produces an `A` or fails with a parser message. `Argument` and `Flag` build
* on these primitives to add names, aliases, defaults, prompts, configuration
* fallbacks, repetition, and help metadata. Primitive parsers cover common
* scalar values, paths, files, structured config files, schema-decoded input,
* redacted values, and key-value pairs.
*
* @since 4.0.0
*/
import * as Ini from "ini";
import * as Toml from "toml";
import * as Yaml from "yaml";
import * as Config from "../../Config.js";
import * as Effect from "../../Effect.js";
import * as FileSystem from "../../FileSystem.js";
import { format } from "../../Formatter.js";
import { identity } from "../../Function.js";
import * as Path from "../../Path.js";
import * as Redacted from "../../Redacted.js";
import * as Schema from "../../Schema.js";
const TypeId = "~effect/cli/Primitive";
const Proto = {
[TypeId]: {
_A: identity
}
};
/** @internal */
export const isTrueValue = /*#__PURE__*/Schema.is(Config.TrueValues);
/** @internal */
export const isFalseValue = /*#__PURE__*/Schema.is(Config.FalseValues);
/** @internal */
export const isBoolean = p => p._tag === "Boolean";
const makePrimitive = (tag, parse) => Object.assign(Object.create(Proto), {
_tag: tag,
parse
});
const makeSchemaPrimitive = (tag, schema) => {
const toCodecStringTree = Schema.toCodecStringTree(schema);
const decode = Schema.decodeUnknownEffect(toCodecStringTree);
return makePrimitive(tag, value => Effect.mapError(decode(value), error => error.message));
};
/**
* Creates a primitive that parses boolean values from string input.
*
* **Details**
*
* Recognizes various forms of true/false values:
* - True values: "true", "1", "y", "yes", "on"
* - False values: "false", "0", "n", "no", "off"
*
* **Example** (Parsing boolean values)
*
* ```ts
* import { Effect } from "effect"
* import { Primitive } from "effect/unstable/cli"
*
* const parseBoolean = Effect.gen(function*() {
* const result1 = yield* Primitive.boolean.parse("true")
* console.log(result1) // true
*
* const result2 = yield* Primitive.boolean.parse("yes")
* console.log(result2) // true
*
* const result3 = yield* Primitive.boolean.parse("false")
* console.log(result3) // false
*
* const result4 = yield* Primitive.boolean.parse("0")
* console.log(result4) // false
* })
* ```
*
* @category constructors
* @since 4.0.0
*/
export const boolean = /*#__PURE__*/makeSchemaPrimitive("Boolean", Config.Boolean);
/**
* Creates a primitive that parses floating-point numbers from string input.
*
* **Example** (Parsing floating-point numbers)
*
* ```ts
* import { Effect } from "effect"
* import { Primitive } from "effect/unstable/cli"
*
* const parseFloat = Effect.gen(function*() {
* const result1 = yield* Primitive.float.parse("3.14")
* console.log(result1) // 3.14
*
* const result2 = yield* Primitive.float.parse("-42.5")
* console.log(result2) // -42.5
*
* const result3 = yield* Primitive.float.parse("0")
* console.log(result3) // 0
* })
* ```
*
* @category constructors
* @since 4.0.0
*/
export const float = /*#__PURE__*/makeSchemaPrimitive("Float", Schema.Finite);
/**
* Creates a primitive that parses integer numbers from string input.
*
* **Example** (Parsing integer values)
*
* ```ts
* import { Effect } from "effect"
* import { Primitive } from "effect/unstable/cli"
*
* const parseInteger = Effect.gen(function*() {
* const result1 = yield* Primitive.integer.parse("42")
* console.log(result1) // 42
*
* const result2 = yield* Primitive.integer.parse("-123")
* console.log(result2) // -123
*
* const result3 = yield* Primitive.integer.parse("0")
* console.log(result3) // 0
* })
* ```
*
* @category constructors
* @since 4.0.0
*/
export const integer = /*#__PURE__*/makeSchemaPrimitive("Integer", Schema.Int);
/**
* Creates a primitive that parses Date objects from string input.
*
* **Example** (Parsing date values)
*
* ```ts
* import { Effect } from "effect"
* import { Primitive } from "effect/unstable/cli"
*
* const parseDate = Effect.gen(function*() {
* const result1 = yield* Primitive.date.parse("2023-12-25")
* console.log(result1) // Date object for December 25, 2023
*
* const result2 = yield* Primitive.date.parse("2023-12-25T10:30:00Z")
* console.log(result2) // Date object with time
*
* const result3 = yield* Primitive.date.parse("Dec 25, 2023")
* console.log(result3) // Date object parsed from natural format
* })
* ```
*
* @category constructors
* @since 4.0.0
*/
export const date = /*#__PURE__*/makeSchemaPrimitive("Date", Schema.DateValid);
/**
* Creates a primitive that accepts any string value without validation.
*
* **Example** (Parsing string values)
*
* ```ts
* import { Effect } from "effect"
* import { Primitive } from "effect/unstable/cli"
*
* const parseString = Effect.gen(function*() {
* const result1 = yield* Primitive.string.parse("hello world")
* console.log(result1) // "hello world"
*
* const result2 = yield* Primitive.string.parse("")
* console.log(result2) // ""
*
* const result3 = yield* Primitive.string.parse("123")
* console.log(result3) // "123"
* })
* ```
*
* @category constructors
* @since 4.0.0
*/
export const string = /*#__PURE__*/makePrimitive("String", value => Effect.succeed(value));
/**
* Creates a primitive that accepts only specific choice values mapped to custom types.
*
* **Example** (Parsing choices)
*
* ```ts
* import { Effect } from "effect"
* import { Primitive } from "effect/unstable/cli"
*
* type LogLevel = "debug" | "info" | "warn" | "error"
*
* const logLevelPrimitive = Primitive.choice<LogLevel>([
* ["debug", "debug"],
* ["info", "info"],
* ["warn", "warn"],
* ["error", "error"]
* ])
*
* const parseLogLevel = Effect.gen(function*() {
* const result1 = yield* logLevelPrimitive.parse("info")
* console.log(result1) // "info"
*
* const result2 = yield* logLevelPrimitive.parse("debug")
* console.log(result2) // "debug"
* })
* ```
*
* @category constructors
* @since 4.0.0
*/
export const choice = choices => {
const choiceMap = new Map(choices);
const validChoices = choices.map(([key]) => format(key)).join(" | ");
const primitive = makePrimitive("Choice", value => {
if (choiceMap.has(value)) {
return Effect.succeed(choiceMap.get(value));
}
return Effect.fail(`Expected ${validChoices}, got ${format(value)}`);
});
return Object.assign(primitive, {
choiceKeys: choices.map(([key]) => key)
});
};
/**
* Creates a primitive that validates and resolves file system paths.
*
* **Example** (Parsing file system paths)
*
* ```ts
* import { Effect } from "effect"
* import { Primitive } from "effect/unstable/cli"
*
* const program = Effect.gen(function*() {
* // Parse a file path that must exist
* const filePrimitive = Primitive.path("file", true)
* const filePath = yield* filePrimitive.parse("./package.json")
* console.log(filePath) // Absolute path to package.json
*
* // Parse a directory path
* const dirPrimitive = Primitive.path("directory", false)
* const dirPath = yield* dirPrimitive.parse("./src")
* console.log(dirPath) // Absolute path to src directory
*
* // Parse any path type
* const anyPrimitive = Primitive.path("either", false)
* const anyPath = yield* anyPrimitive.parse("./some/path")
* console.log(anyPath) // Absolute path
* })
* ```
*
* @category constructors
* @since 4.0.0
*/
export const path = (pathType, mustExist) => makePrimitive("Path", Effect.fnUntraced(function* (value) {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
// Resolve the path to absolute
const absolutePath = path.isAbsolute(value) ? value : path.resolve(value);
// Check if path exists
const exists = yield* Effect.mapError(fs.exists(absolutePath), error => `Failed to check path existence: ${error.message}`);
// Validate existence requirements
if (mustExist === true && !exists) {
return yield* Effect.fail(`Path does not exist: ${absolutePath}`);
}
// Validate path type if it exists
if (exists && pathType !== "either") {
const stat = yield* Effect.mapError(fs.stat(absolutePath), error => `Failed to stat path: ${error.message}`);
if (pathType === "file" && stat.type !== "File") {
return yield* Effect.fail(`Path is not a file: ${absolutePath}`);
}
if (pathType === "directory" && stat.type !== "Directory") {
return yield* Effect.fail(`Path is not a directory: ${absolutePath}`);
}
}
return absolutePath;
}));
/**
* Creates a primitive that wraps string input in `Redacted`.
*
* **Details**
*
* The wrapped value is hidden when formatted or inspected, while the original
* string remains available through the `Redacted` API when explicitly needed.
*
* **Example** (Parsing redacted values)
*
* ```ts
* import { Effect, Redacted } from "effect"
* import { Primitive } from "effect/unstable/cli"
*
* const parseRedacted = Effect.gen(function*() {
* const result = yield* Primitive.redacted.parse("secret-password")
* console.log(Redacted.value(result)) // "secret-password"
* console.log(String(result)) // "<redacted>"
* })
* ```
*
* @category constructors
* @since 4.0.0
*/
export const redacted = /*#__PURE__*/makePrimitive("Redacted", value => Effect.succeed(Redacted.make(value)));
/**
* Creates a primitive that reads and returns the contents of a file as a string.
*
* **Example** (Reading file text)
*
* ```ts
* import { Effect, Schema } from "effect"
* import { Primitive } from "effect/unstable/cli"
*
* const ConfigSchema = Schema.Struct({
* name: Schema.String,
* version: Schema.String,
* port: Schema.Number
* })
* const decodeConfig = Schema.decodeUnknownEffect(
* Schema.fromJsonString(ConfigSchema)
* )
*
* const readConfigFile = Effect.gen(function*() {
* const content = yield* Primitive.fileText.parse("./config.json")
* console.log(content) // {"name":"my-app","version":"1.0.0","port":3000}
*
* const config = yield* decodeConfig(content)
* console.log(config) // { name: "my-app", version: "1.0.0", port: 3000 }
* return config
* })
* ```
*
* @category constructors
* @since 4.0.0
*/
export const fileText = /*#__PURE__*/makePrimitive("FileText", /*#__PURE__*/Effect.fnUntraced(function* (filePath) {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
// Resolve to absolute path
const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(filePath);
// Check if file exists
const exists = yield* Effect.mapError(fs.exists(absolutePath), error => `Failed to check file existence: ${error.message}`);
if (!exists) {
return yield* Effect.fail(`File does not exist: ${absolutePath}`);
}
// Check if it's actually a file
const stat = yield* Effect.mapError(fs.stat(absolutePath), error => `Failed to stat file: ${error.message}`);
if (stat.type !== "File") {
return yield* Effect.fail(`Path is not a file: ${absolutePath}`);
}
// Read file content
const content = yield* Effect.mapError(fs.readFileString(absolutePath), error => `Failed to read file: ${error.message}`);
return content;
}));
const fileParsers = {
ini: content => Ini.parse(content),
json: content => JSON.parse(content),
toml: content => Toml.parse(content),
yml: content => Yaml.parse(content),
yaml: content => Yaml.parse(content)
};
/**
* Creates a primitive that reads a file and parses its content as structured
* data.
*
* **Details**
*
* The parser is selected from `options.format` when provided, otherwise from
* the file extension. Supported formats include INI, JSON, TOML, YAML, and YML.
*
* **Example** (Parsing file content)
*
* ```ts
* import { Effect } from "effect"
* import { Primitive } from "effect/unstable/cli"
*
* const tomlFilePrimitive = Primitive.fileParse({ format: "toml" })
*
* const loadConfig = Effect.gen(function*() {
* const config = yield* tomlFilePrimitive.parse("./config.toml")
* console.log(config) // { name: "my-app", version: "1.0.0", port: 3000 }
* return config
* })
* ```
*
* @category constructors
* @since 4.0.0
*/
export const fileParse = options => {
return makePrimitive("FileParse", Effect.fnUntraced(function* (filePath) {
const fileFormat = options?.format ?? filePath.split(".").pop();
const parser = fileParsers[fileFormat];
if (parser === undefined) {
return yield* Effect.fail(`Unsupported file format: ${fileFormat}`);
}
const content = yield* fileText.parse(filePath);
return yield* Effect.try({
try: () => parser(content),
catch: error => `Failed to parse '.${fileFormat}' file content: ${error}`
});
}));
};
/**
* Reads and parses file content using the specified schema.
*
* **Example** (Parsing file content with a schema)
*
* ```ts
* import { Effect, Schema } from "effect"
* import { Primitive } from "effect/unstable/cli"
*
* const ConfigSchema = Schema.Struct({
* name: Schema.String,
* version: Schema.String,
* port: Schema.Number
* })
*
* const jsonConfigPrimitive = Primitive.fileSchema(ConfigSchema, {
* format: "json"
* })
*
* const loadConfig = Effect.gen(function*() {
* const config = yield* jsonConfigPrimitive.parse("./config.json")
* console.log(config) // { name: "my-app", version: "1.0.0", port: 3000 }
* return config
* })
* ```
*
* @category constructors
* @since 4.0.0
*/
export const fileSchema = (schema, options) => {
const decode = Schema.decodeUnknownEffect(schema);
return makePrimitive("FileSchema", Effect.fnUntraced(function* (filePath) {
const content = yield* fileParse(options).parse(filePath);
return yield* Effect.mapError(decode(content), error => options?.errorFormatter?.(error.issue) ?? error.toString());
}));
};
/**
* Parses a single `key=value` pair into a record object.
*
* **Example** (Parsing key-value pairs)
*
* ```ts
* import { Effect } from "effect"
* import { Primitive } from "effect/unstable/cli"
*
* const parseKeyValue = Effect.gen(function*() {
* const result1 = yield* Primitive.keyValuePair.parse("name=john")
* console.log(result1) // { name: "john" }
*
* const result2 = yield* Primitive.keyValuePair.parse("port=3000")
* console.log(result2) // { port: "3000" }
*
* const result3 = yield* Primitive.keyValuePair.parse("debug=true")
* console.log(result3) // { debug: "true" }
* })
* ```
*
* @category constructors
* @since 4.0.0
*/
export const keyValuePair = /*#__PURE__*/makePrimitive("KeyValuePair", /*#__PURE__*/Effect.fnUntraced(function* (value) {
const parts = value.split("=");
if (parts.length !== 2) {
return yield* Effect.fail(`Invalid key=value format. Expected format: key=value, got: ${value}`);
}
const [key, val] = parts;
if (!key || !val) {
return yield* Effect.fail(`Invalid key=value format. Both key and value must be non-empty. Got: ${value}`);
}
return {
[key]: val
};
}));
/**
* Creates a sentinel primitive that always fails to parse a value.
*
* **When to use**
*
* Use when you need a CLI primitive for flags that do not accept values.
*
* **Example** (Rejecting option values)
*
* ```ts
* import { Effect } from "effect"
* import { Primitive } from "effect/unstable/cli"
*
* const program = Effect.gen(function*() {
* // This will always fail - useful for boolean flags
* return yield* Primitive.none.parse("any-value")
* })
*
* // The above effect will fail with "This option does not accept values"
* ```
*
* @category constructors
* @since 4.0.0
*/
export const none = /*#__PURE__*/makePrimitive("None", () => Effect.fail("This option does not accept values"));
/**
* Gets a human-readable type name for a primitive.
*
* **When to use**
*
* Use when you need the display type name for a `Primitive`, such as when
* generating CLI help documentation.
*
* **Example** (Getting primitive type names)
*
* ```ts
* import { Primitive } from "effect/unstable/cli"
*
* console.log(Primitive.getTypeName(Primitive.string)) // "string"
* console.log(Primitive.getTypeName(Primitive.integer)) // "integer"
* console.log(Primitive.getTypeName(Primitive.boolean)) // "boolean"
* console.log(Primitive.getTypeName(Primitive.date)) // "date"
* console.log(Primitive.getTypeName(Primitive.keyValuePair)) // "key=value"
*
* const logLevelChoice = Primitive.choice([
* ["debug", "debug"],
* ["info", "info"]
* ])
* console.log(Primitive.getTypeName(logLevelChoice)) // "choice"
* ```
*
* @category getters
* @since 4.0.0
*/
export const getTypeName = primitive => {
switch (primitive._tag) {
case "Boolean":
return "boolean";
case "String":
return "string";
case "Integer":
return "integer";
case "Float":
return "number";
case "Date":
return "date";
case "Path":
return "path";
case "Choice":
return "choice";
case "Redacted":
return "string";
case "FileText":
return "file";
case "FileParse":
return "file";
case "FileSchema":
return "file";
case "KeyValuePair":
return "key=value";
case "None":
return "none";
default:
return "value";
}
};
/** @internal */
export const getChoiceKeys = primitive => primitive._tag === "Choice" ? primitive.choiceKeys : undefined;
//# sourceMappingURL=Primitive.js.map

Xet Storage Details

Size:
17.7 kB
·
Xet hash:
48fcdd5cc92cd0cc079703ab3da1b1e6cd99c6ccbd28af5e1df3313b70d99313

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