EdgeAIG's picture
download
raw
6.15 kB
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
const escapeZsh = s => s.replace(/\\/g, "\\\\").replace(/'/g, "'\\''").replace(/:/g, "\\:");
const sanitize = s => s.replace(/[^a-zA-Z0-9_]/g, "_");
/**
* All forms of a flag (--name, -alias, --no-name) for the exclusion group.
*/
const allForms = flag => {
const forms = [`--${flag.name}`];
for (const alias of flag.aliases) {
forms.push(alias.length === 1 ? `-${alias}` : `--${alias}`);
}
if (flag.type._tag === "Boolean") {
forms.push(`--no-${flag.name}`);
}
return forms;
};
const valueAction = type => {
switch (type._tag) {
case "Boolean":
return "";
case "Choice":
return `:value:(${type.values.join(" ")})`;
case "Path":
return type.pathType === "directory" ? `:directory:_directories` : `:file:_files`;
case "Integer":
return `:integer:`;
case "Float":
return `:float:`;
case "Date":
return `:date:`;
default:
return `:value:`;
}
};
const argAction = type => {
switch (type._tag) {
case "Choice":
return `(${type.values.join(" ")})`;
case "Path":
return type.pathType === "directory" ? `_directories` : `_files`;
default:
return ``;
}
};
/**
* Produce _arguments optspecs for a single flag. Each spec is a complete,
* single-quoted string — no brace expansion, no line continuations.
*/
const flagSpecs = flag => {
const specs = [];
const desc = flag.description ? `[${escapeZsh(flag.description)}]` : "";
const action = valueAction(flag.type);
const excl = `(${allForms(flag).join(" ")})`;
// Primary long name
specs.push(`'${excl}--${flag.name}${desc}${action}'`);
// Aliases
for (const alias of flag.aliases) {
const prefix = alias.length === 1 ? "-" : "--";
specs.push(`'${excl}${prefix}${alias}${desc}${action}'`);
}
// Boolean negation
if (flag.type._tag === "Boolean") {
const negDesc = flag.description ? `[${escapeZsh(`Disable ${flag.name}`)}]` : "";
specs.push(`'${excl}--no-${flag.name}${negDesc}'`);
}
return specs;
};
const argSpec = arg => {
const desc = arg.description ? escapeZsh(arg.description) : arg.name;
const action = argAction(arg.type);
const prefix = arg.variadic ? "*" : "";
return `'${prefix}:${desc}:${action}'`;
};
// ---------------------------------------------------------------------------
// Generator
// ---------------------------------------------------------------------------
const generateFunction = (descriptor, parentPath, lines) => {
const currentPath = [...parentPath, descriptor.name];
const funcName = `_${currentPath.map(sanitize).join("_")}`;
lines.push(`${funcName}() {`);
if (descriptor.subcommands.length > 0) {
// _arguments -C requires these locals for the state machine
lines.push(` local context state state_descr line`);
lines.push(` typeset -A opt_args`);
lines.push(``);
// Subcommand list
lines.push(` local -a commands`);
lines.push(` commands=(`);
for (const sub of descriptor.subcommands) {
const desc = sub.description ? escapeZsh(sub.description) : "";
lines.push(` '${sub.name}:${desc}'`);
}
lines.push(` )`);
lines.push(``);
// Build specs array to avoid brace expansion / line continuation issues
lines.push(` local -a specs`);
lines.push(` specs=(`);
for (const flag of descriptor.flags) {
for (const spec of flagSpecs(flag)) {
lines.push(` ${spec}`);
}
}
for (const arg of descriptor.arguments) {
lines.push(` ${argSpec(arg)}`);
}
lines.push(` '1:command:->command'`);
lines.push(` '*::arg:->args'`);
lines.push(` )`);
lines.push(``);
lines.push(` _arguments -C "\${specs[@]}"`);
lines.push(``);
// State dispatch
lines.push(` case "$state" in`);
lines.push(` command)`);
lines.push(` _describe -t commands 'commands' commands`);
lines.push(` ;;`);
lines.push(` args)`);
lines.push(` case "$words[1]" in`);
for (const sub of descriptor.subcommands) {
const subFunc = `_${[...currentPath, sub.name].map(sanitize).join("_")}`;
lines.push(` ${sub.name})`);
lines.push(` ${subFunc}`);
lines.push(` ;;`);
}
lines.push(` esac`);
lines.push(` ;;`);
lines.push(` esac`);
} else {
// Leaf command — no subcommands
const allSpecs = [];
for (const flag of descriptor.flags) {
allSpecs.push(...flagSpecs(flag));
}
for (const arg of descriptor.arguments) {
allSpecs.push(argSpec(arg));
}
if (allSpecs.length > 0) {
lines.push(` local -a specs`);
lines.push(` specs=(`);
for (const spec of allSpecs) {
lines.push(` ${spec}`);
}
lines.push(` )`);
lines.push(` _arguments "\${specs[@]}"`);
}
}
lines.push(`}`);
lines.push(``);
for (const sub of descriptor.subcommands) {
generateFunction(sub, currentPath, lines);
}
};
/** @internal */
export const generate = (executableName, descriptor) => {
const lines = [];
const safeName = sanitize(executableName);
lines.push(`#compdef ${executableName}`);
lines.push(`###-begin-${executableName}-completions-###`);
lines.push(`#`);
lines.push(`# Static completion script for Zsh`);
lines.push(`#`);
lines.push(`# Installation:`);
lines.push(`# ${executableName} --completions zsh > ~/.zsh/completions/_${executableName}`);
lines.push(`# then add ~/.zsh/completions to your fpath`);
lines.push(`#`);
lines.push(``);
generateFunction(descriptor, [], lines);
lines.push(`# Handle both direct invocation and autoload`);
lines.push(`if [[ "\${zsh_eval_context[-1]}" == "loadautofunc" ]]; then`);
lines.push(` _${safeName} "$@"`);
lines.push(`else`);
lines.push(` compdef _${safeName} ${executableName}`);
lines.push(`fi`);
lines.push(`###-end-${executableName}-completions-###`);
return lines.join("\n");
};
//# sourceMappingURL=zsh.js.map

Xet Storage Details

Size:
6.15 kB
·
Xet hash:
67997f03dbd845d8242070bca3c68a24a19ea5285899d837b81d88a55dbad680

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