EdgeAIG's picture
download
raw
9.2 kB
/**
* 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.