EdgeAIG's picture
download
raw
7.04 kB
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
const escapeForBash = s => s.replace(/'/g, "'\\''");
const sanitizeFunctionName = s => s.replace(/[^a-zA-Z0-9_]/g, "_");
const flagNamesForWordlist = flag => {
const names = [`--${flag.name}`];
for (const alias of flag.aliases) {
names.push(alias.length === 1 ? `-${alias}` : `--${alias}`);
}
if (flag.type._tag === "Boolean") {
names.push(`--no-${flag.name}`);
}
return names;
};
/**
* Build an associative array mapping each flag form to a group index.
* At completion time, if any form in a group appears in COMP_WORDS,
* all forms in that group are removed from the candidate list.
*/
const buildFlagGroupDeclarations = (flags, lines) => {
if (flags.length === 0) return;
lines.push(` # Build used-flag filter`);
lines.push(` local -A _flag_groups`);
let groupIdx = 0;
for (const flag of flags) {
const forms = flagNamesForWordlist(flag);
for (const form of forms) {
lines.push(` _flag_groups[${form}]=${groupIdx}`);
}
groupIdx++;
}
lines.push(` local -A _used_groups`);
lines.push(` for ((i = 1; i < cword; i++)); do`);
lines.push(` local _g="\${_flag_groups[\${words[i]}]:-}"`);
lines.push(` [[ -n "$_g" ]] && _used_groups[$_g]=1`);
lines.push(` done`);
lines.push(` local _filtered_flags=""`);
lines.push(` for _f in ${flags.flatMap(flagNamesForWordlist).join(" ")}; do`);
lines.push(` local _g="\${_flag_groups[$_f]:-}"`);
lines.push(` [[ -z "$_g" || -z "\${_used_groups[$_g]:-}" ]] && _filtered_flags+=" $_f"`);
lines.push(` done`);
lines.push(``);
};
const flagValueCompletion = type => {
switch (type._tag) {
case "Boolean":
return undefined;
case "Choice":
return `COMPREPLY=( $(compgen -W '${type.values.join(" ")}' -- "$cur") )`;
case "Path":
if (type.pathType === "directory") return `COMPREPLY=( $(compgen -d -- "$cur") )`;
return `COMPREPLY=( $(compgen -f -- "$cur") )`;
default:
return undefined;
}
};
const argCompletion = type => {
switch (type._tag) {
case "Choice":
return `COMPREPLY=( $(compgen -W '${type.values.join(" ")}' -- "$cur") )`;
case "Path":
if (type.pathType === "directory") return `COMPREPLY=( $(compgen -d -- "$cur") )`;
return `COMPREPLY=( $(compgen -f -- "$cur") )`;
default:
return undefined;
}
};
// ---------------------------------------------------------------------------
// Generator
// ---------------------------------------------------------------------------
const generateFunction = (descriptor, parentPath, lines) => {
const currentPath = [...parentPath, descriptor.name];
const funcName = `_${currentPath.map(sanitizeFunctionName).join("_")}`;
lines.push(`${funcName}()`);
lines.push(`{`);
lines.push(` local cur prev words cword`);
lines.push(` _init_completion || return`);
lines.push(``);
// Build flag-value dispatch
const flagsWithValues = descriptor.flags.filter(f => f.type._tag !== "Boolean");
if (flagsWithValues.length > 0) {
lines.push(` # Flag value completions`);
lines.push(` case "$prev" in`);
for (const flag of flagsWithValues) {
const longNames = [`--${flag.name}`];
for (const alias of flag.aliases) {
longNames.push(alias.length === 1 ? `-${alias}` : `--${alias}`);
}
const completion = flagValueCompletion(flag.type);
if (completion) {
lines.push(` ${longNames.join("|")})`);
lines.push(` ${completion}`);
lines.push(` return`);
lines.push(` ;;`);
}
}
lines.push(` esac`);
lines.push(``);
}
// Subcommand dispatch
if (descriptor.subcommands.length > 0) {
lines.push(` # Subcommand dispatch`);
lines.push(` local i cmd`);
lines.push(` for ((i = 1; i < cword; i++)); do`);
lines.push(` case "\${words[i]}" in`);
for (const sub of descriptor.subcommands) {
const subFuncName = `_${[...currentPath, sub.name].map(sanitizeFunctionName).join("_")}`;
lines.push(` ${sub.name})`);
lines.push(` ${subFuncName}`);
lines.push(` return`);
lines.push(` ;;`);
}
lines.push(` esac`);
lines.push(` done`);
lines.push(``);
}
// Filter already-used flags (entire alias group removed when any form is used)
buildFlagGroupDeclarations(descriptor.flags, lines);
if (descriptor.flags.length > 0 || descriptor.subcommands.length > 0) {
lines.push(` # Complete flags (filtered) and subcommands`);
lines.push(` if [[ "$cur" == -* ]]; then`);
if (descriptor.flags.length > 0) {
lines.push(` COMPREPLY=( $(compgen -W "$_filtered_flags" -- "$cur") )`);
}
lines.push(` return`);
lines.push(` fi`);
lines.push(``);
}
// Positional argument completion
const argsWithCompletions = descriptor.arguments.filter(a => argCompletion(a.type) !== undefined);
if (argsWithCompletions.length > 0) {
lines.push(` # Positional argument completions`);
for (const arg of argsWithCompletions) {
const comp = argCompletion(arg.type);
lines.push(` ${comp}`);
lines.push(` return`);
}
} else if (descriptor.subcommands.length > 0) {
const subNames = descriptor.subcommands.map(s => s.name);
lines.push(` COMPREPLY=( $(compgen -W '${subNames.join(" ")}' -- "$cur") )`);
}
lines.push(`}`);
lines.push(``);
// Recurse into subcommands
for (const sub of descriptor.subcommands) {
generateFunction(sub, currentPath, lines);
}
};
/** @internal */
export const generate = (executableName, descriptor) => {
const lines = [];
const safeName = sanitizeFunctionName(executableName);
lines.push(`###-begin-${escapeForBash(executableName)}-completions-###`);
lines.push(`#`);
lines.push(`# Static completion script for Bash`);
lines.push(`#`);
lines.push(`# Installation:`);
lines.push(`# ${escapeForBash(executableName)} --completions bash >> ~/.bashrc`);
lines.push(`#`);
lines.push(``);
// Inline minimal _init_completion fallback for environments without
// bash-completion installed. The real _init_completion handles edge cases
// (= in options, redirections, etc.) but this covers the common path.
lines.push(`if ! type _init_completion &>/dev/null; then`);
lines.push(` _init_completion()`);
lines.push(` {`);
lines.push(` COMPREPLY=()`);
lines.push(` cur="\${COMP_WORDS[COMP_CWORD]}"`);
lines.push(` prev="\${COMP_WORDS[COMP_CWORD-1]}"`);
lines.push(` words=("\${COMP_WORDS[@]}")`);
lines.push(` cword=$COMP_CWORD`);
lines.push(` }`);
lines.push(`fi`);
lines.push(``);
generateFunction(descriptor, [], lines);
lines.push(`complete -F _${safeName} ${escapeForBash(executableName)}`);
lines.push(`###-end-${escapeForBash(executableName)}-completions-###`);
return lines.join("\n");
};
//# sourceMappingURL=bash.js.map

Xet Storage Details

Size:
7.04 kB
·
Xet hash:
e7c02255f3a00f4965610654fc9f7fdf3cebf87a3d3a2f19707cc886b6f34064

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