| /** | |
| * Command Implementation | |
| * ====================== | |
| * | |
| * Internal implementation details for CLI commands. | |
| * Public API is in ../Command.ts | |
| */ | |
| import * as Arr from "../../../Array.js"; | |
| import * as Context from "../../../Context.js"; | |
| import * as Effect from "../../../Effect.js"; | |
| import * as Effectable from "../../../Effectable.js"; | |
| import * as Option from "../../../Option.js"; | |
| import * as Predicate from "../../../Predicate.js"; | |
| import * as CliError from "../CliError.js"; | |
| import * as Param from "../Param.js"; | |
| import * as Primitive from "../Primitive.js"; | |
| import { emptyConfig, reconstructTree } from "./config.js"; | |
| /* ========================================================================== */ | |
| /* Type ID */ | |
| /* ========================================================================== */ | |
| export const TypeId = "~effect/cli/Command"; | |
| /* ========================================================================== */ | |
| /* Casting */ | |
| /* ========================================================================== */ | |
| /** | |
| * Casts a Command to its internal implementation. | |
| * For use by internal modules that need access to config, parse, handle, etc. | |
| */ | |
| export const toImpl = self => self; | |
| /* ========================================================================== */ | |
| /* Proto */ | |
| /* ========================================================================== */ | |
| export const Proto = { | |
| ... /*#__PURE__*/Effectable.Prototype({ | |
| label: "Command", | |
| evaluate() { | |
| return toImpl(this).service; | |
| } | |
| }) | |
| }; | |
| /* ========================================================================== */ | |
| /* Constructor */ | |
| /* ========================================================================== */ | |
| /** | |
| * Internal command constructor. Only accepts already-parsed ConfigInternal. | |
| */ | |
| export const makeCommand = options => { | |
| const config = options.config; | |
| const contextConfig = options.contextConfig ?? emptyConfig; | |
| const service = options.service ?? Context.Service(`${TypeId}/${options.name}`); | |
| const annotations = options.annotations ?? Context.empty(); | |
| const globalFlags = options.globalFlags ?? []; | |
| const subcommands = options.subcommands ?? []; | |
| const handle = (input, commandPath) => Predicate.isNotUndefined(options.handle) ? options.handle(input, commandPath) : Effect.fail(new CliError.ShowHelp({ | |
| commandPath, | |
| errors: [] | |
| })); | |
| const parse = options.parse ?? makeParser(config); | |
| const parseContext = options.parseContext ?? makeParser(contextConfig); | |
| const buildHelpDoc = commandPath => { | |
| const args = []; | |
| const flags = []; | |
| for (const arg of config.arguments) { | |
| const singles = Param.extractSingleParams(arg); | |
| const metadata = Param.getParamMetadata(arg); | |
| for (const single of singles) { | |
| args.push({ | |
| name: single.name, | |
| type: single.typeName ?? Primitive.getTypeName(single.primitiveType), | |
| description: single.description, | |
| required: !metadata.isOptional, | |
| variadic: metadata.isVariadic | |
| }); | |
| } | |
| } | |
| let usage = commandPath.length > 0 ? commandPath.join(" ") : options.name; | |
| // Only render `<subcommand>` in usage when at least one visible subcommand | |
| // exists; an all-hidden subcommand tree should look like a leaf command. | |
| if (subcommands.some(group => group.commands.some(c => !c.hidden))) { | |
| usage += " <subcommand>"; | |
| } | |
| usage += " [flags]"; | |
| for (const arg of args) { | |
| const argName = arg.variadic ? `<${arg.name}...>` : `<${arg.name}>`; | |
| usage += ` ${arg.required ? argName : `[${argName}]`}`; | |
| } | |
| for (const option of config.flags) { | |
| const singles = Param.extractSingleParams(option); | |
| for (const single of singles) { | |
| // Hidden flags still parse on the command line but are omitted from | |
| // generated --help output. | |
| if (single.hidden) continue; | |
| flags.push(toFlagDoc(single)); | |
| } | |
| } | |
| const subcommandDocs = []; | |
| for (const group of subcommands) { | |
| // Hidden subcommands still parse on the command line but are omitted | |
| // from --help. Drop the whole group when nothing visible remains so we | |
| // don't render an empty heading. | |
| const visible = group.commands.filter(c => !c.hidden); | |
| if (visible.length === 0) continue; | |
| subcommandDocs.push({ | |
| group: group.group, | |
| commands: Arr.map(visible, subcommand => ({ | |
| name: subcommand.name, | |
| alias: subcommand.alias, | |
| shortDescription: subcommand.shortDescription, | |
| description: subcommand.description ?? "" | |
| })) | |
| }); | |
| } | |
| const examples = options.examples ?? []; | |
| return { | |
| description: options.description ?? "", | |
| usage, | |
| flags, | |
| annotations, | |
| ...(args.length > 0 && { | |
| args | |
| }), | |
| ...(subcommandDocs.length > 0 && { | |
| subcommands: subcommandDocs | |
| }), | |
| ...(examples.length > 0 && { | |
| examples | |
| }) | |
| }; | |
| }; | |
| return Object.assign(Object.create(Proto), { | |
| [TypeId]: TypeId, | |
| name: options.name, | |
| examples: options.examples ?? [], | |
| annotations, | |
| globalFlags, | |
| subcommands, | |
| hidden: options.hidden ?? false, | |
| config, | |
| contextConfig, | |
| service, | |
| parse, | |
| parseContext, | |
| handle, | |
| buildHelpDoc, | |
| ...(Predicate.isNotUndefined(options.description) ? { | |
| description: options.description | |
| } : {}), | |
| ...(Predicate.isNotUndefined(options.shortDescription) ? { | |
| shortDescription: options.shortDescription | |
| } : {}), | |
| ...(Predicate.isNotUndefined(options.alias) ? { | |
| alias: options.alias | |
| } : {}) | |
| }); | |
| }; | |
| /* ========================================================================== */ | |
| /* Helpers */ | |
| /* ========================================================================== */ | |
| /** | |
| * Converts a single flag param into a FlagDoc for help display. | |
| */ | |
| export const toFlagDoc = single => { | |
| const formattedAliases = single.aliases.map(alias => alias.length === 1 ? `-${alias}` : `--${alias}`); | |
| return { | |
| name: single.name, | |
| aliases: formattedAliases, | |
| type: single.typeName ?? Primitive.getTypeName(single.primitiveType), | |
| description: appendChoiceKeys(single.description, Primitive.getChoiceKeys(single.primitiveType)), | |
| required: single.primitiveType._tag !== "Boolean" | |
| }; | |
| }; | |
| const appendChoiceKeys = (description, choiceKeys) => { | |
| if (choiceKeys === undefined || choiceKeys.length === 0) { | |
| return description; | |
| } | |
| const choiceSuffix = `(choices: ${choiceKeys.join(", ")})`; | |
| return Option.match(description, { | |
| onNone: () => Option.some(choiceSuffix), | |
| onSome: value => Option.some(`${value} ${choiceSuffix}`) | |
| }); | |
| }; | |
| /** | |
| * Creates a parser for a given config. Used as the default for both `parse` | |
| * and `parseContext`, and also by `withSharedFlags` to avoid constructing a | |
| * full throwaway command. | |
| */ | |
| export const makeParser = cfg => Effect.fnUntraced(function* (input) { | |
| const parsedArgs = { | |
| flags: input.flags, | |
| arguments: input.arguments | |
| }; | |
| const values = yield* parseParams(parsedArgs, cfg.orderedParams); | |
| return reconstructTree(cfg.tree, values); | |
| }); | |
| /** | |
| * Parses param values from parsed command arguments into their typed | |
| * representations. | |
| */ | |
| const parseParams = /*#__PURE__*/Effect.fnUntraced(function* (parsedArgs, params) { | |
| const results = []; | |
| let currentArguments = parsedArgs.arguments; | |
| for (const option of params) { | |
| const [remainingArguments, parsed] = yield* option.parse({ | |
| flags: parsedArgs.flags, | |
| arguments: currentArguments | |
| }); | |
| results.push(parsed); | |
| currentArguments = remainingArguments; | |
| } | |
| return results; | |
| }); | |
| /** | |
| * Checks that inherited parent context flags do not reuse names declared by | |
| * child command flags. | |
| * | |
| * When `contextConfig` is supplied, it is used as the inherited flag set; | |
| * otherwise the parent's current context config is checked. | |
| */ | |
| export const checkForDuplicateFlags = (parent, subcommands, options) => { | |
| const parentImpl = toImpl(parent); | |
| const parentOptionNames = new Set(); | |
| const extractNames = flags => { | |
| for (const option of flags) { | |
| const singles = Param.extractSingleParams(option); | |
| for (const single of singles) { | |
| parentOptionNames.add(single.name); | |
| } | |
| } | |
| }; | |
| extractNames((options?.contextConfig ?? parentImpl.contextConfig).flags); | |
| for (const subcommand of subcommands) { | |
| const subImpl = toImpl(subcommand); | |
| for (const option of subImpl.config.flags) { | |
| const singles = Param.extractSingleParams(option); | |
| for (const single of singles) { | |
| if (parentOptionNames.has(single.name)) { | |
| throw new CliError.DuplicateOption({ | |
| option: single.name, | |
| parentCommand: parent.name, | |
| childCommand: subcommand.name | |
| }); | |
| } | |
| } | |
| } | |
| } | |
| }; | |
| //# sourceMappingURL=command.js.map |
Xet Storage Details
- Size:
- 9.2 kB
- Xet hash:
- 0adc1d780bcd1e19c5a1aff90bca0576a48d6031b830bf8a507f355bbdbdb3c2
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.