swc/crates/swc_bundler/tests/.cache/deno/0cdc36c0fbd42779e71f6f7280e4c0fdfe8276cb.ts
2021-11-09 20:42:49 +09:00

326 lines
8.9 KiB
TypeScript

// Loaded from https://deno.land/x/cliffy@v0.18.0/command/completions/_zsh_completions_generator.ts
import type { Command } from "../command.ts";
import type { IArgument, IOption } from "../types.ts";
interface ICompletionAction {
arg: IArgument;
label: string;
name: string;
cmd: string;
}
/** Generates zsh completions script. */
export class ZshCompletionsGenerator {
private actions: Map<string, ICompletionAction> = new Map();
/** Generates zsh completions script for given command. */
public static generate(cmd: Command) {
return new ZshCompletionsGenerator(cmd).generate();
}
private constructor(protected cmd: Command) {}
/** Generates zsh completions code. */
private generate(): string {
const path = this.cmd.getPath();
const name = this.cmd.getName();
const version: string | undefined = this.cmd.getVersion()
? ` v${this.cmd.getVersion()}`
: "";
return `#!/usr/bin/env zsh
# zsh completion support for ${path}${version}
autoload -U is-at-least
# shellcheck disable=SC2154
(( $+functions[__${replaceSpecialChars(name)}_complete] )) ||
function __${replaceSpecialChars(name)}_complete {
local name="$1"; shift
local action="$1"; shift
integer ret=1
local -a values
local expl lines
_tags "$name"
while _tags; do
if _requested "$name"; then
# shellcheck disable=SC2034
lines="$(${name} completions complete "\${action}" "\${@}")"
values=("\${(ps:\\n:)lines}")
if (( \${#values[@]} )); then
while _next_label "$name" expl "$action"; do
compadd -S '' "\${expl[@]}" "\${values[@]}"
done
fi
fi
done
}
${this.generateCompletions(this.cmd).trim()}
# _${replaceSpecialChars(path)} "\${@}"
compdef _${replaceSpecialChars(path)} ${path}
`;
}
/** Generates zsh completions method for given command and child commands. */
private generateCompletions(command: Command, path = ""): string {
if (
!command.hasCommands(false) && !command.hasOptions(false) &&
!command.hasArguments()
) {
return "";
}
path = (path ? path + " " : "") + command.getName();
return `# shellcheck disable=SC2154
(( $+functions[_${replaceSpecialChars(path)}] )) ||
function _${replaceSpecialChars(path)}() {` +
(!command.getParent()
? `
local state`
: "") +
this.generateCommandCompletions(command, path) +
this.generateSubCommandCompletions(command, path) +
this.generateArgumentCompletions(command, path) +
this.generateActions(command) +
`\n}\n\n` +
command.getCommands(false)
.filter((subCommand: Command) => subCommand !== command)
.map((subCommand: Command) =>
this.generateCompletions(subCommand, path)
)
.join("");
}
private generateCommandCompletions(command: Command, path: string): string {
const commands = command.getCommands(false);
let completions: string = commands
.map((subCommand: Command) =>
`'${subCommand.getName()}:${subCommand.getShortDescription()}'`
)
.join("\n ");
if (completions) {
completions = `
local -a commands
# shellcheck disable=SC2034
commands=(
${completions}
)
_describe 'command' commands`;
}
if (command.hasArguments()) {
const completionsPath: string = path.split(" ").slice(1).join(" ");
// @TODO: support multiple arguments zsh completions
const arg: IArgument = command.getArguments()[0];
const action = this.addAction(arg, completionsPath);
if (action && command.getCompletion(arg.action)) {
completions += `\n __${
replaceSpecialChars(this.cmd.getName())
}_complete ${action.arg.name} ${action.arg.action} ${action.cmd}`;
}
}
if (completions) {
completions = `\n\n function _commands() {${completions}\n }`;
}
return completions;
}
private generateSubCommandCompletions(
command: Command,
path: string,
): string {
if (command.hasCommands(false)) {
const actions: string = command
.getCommands(false)
.map((command: Command) =>
`${command.getName()}) _${
replaceSpecialChars(path + " " + command.getName())
} ;;`
)
.join("\n ");
return `\n
function _command_args() {
case "\${words[1]}" in\n ${actions}\n esac
}`;
}
return "";
}
private generateArgumentCompletions(command: Command, path: string): string {
/* clear actions from previously parsed command. */
this.actions.clear();
const options: string[] = this.generateOptions(command, path);
let argIndex = 0;
// @TODO: add stop early option: -A "-*"
// http://zsh.sourceforge.net/Doc/Release/Completion-System.html
let argsCommand = "\n\n _arguments -w -s -S -C";
if (command.hasOptions()) {
argsCommand += ` \\\n ${options.join(" \\\n ")}`;
}
if (
command.hasCommands(false) || (
command.getArguments()
.filter((arg) => command.getCompletion(arg.action)).length
)
) {
argsCommand += ` \\\n '${++argIndex}: :_commands'`;
}
if (command.hasArguments() || command.hasCommands(false)) {
const args: string[] = [];
for (const arg of command.getArguments().slice(1)) {
const completionsPath: string = path.split(" ").slice(1).join(" ");
const action = this.addAction(arg, completionsPath);
args.push(
`${++argIndex}${arg.optionalValue ? "::" : ":"}${action.name}`,
);
}
argsCommand += args.map((arg: string) => `\\\n '${arg}'`).join("");
if (command.hasCommands(false)) {
argsCommand += ` \\\n '*:: :->command_args'`;
}
}
return argsCommand;
}
private generateOptions(command: Command, path: string) {
const options: string[] = [];
const cmdArgs: string[] = path.split(" ");
const _baseName: string = cmdArgs.shift() as string;
const completionsPath: string = cmdArgs.join(" ");
const excludedFlags: string[] = command.getOptions(false)
.map((option) => option.standalone ? option.flags : false)
.flat()
.filter((flag) => typeof flag === "string") as string[];
for (const option of command.getOptions(false)) {
options.push(this.generateOption(option, completionsPath, excludedFlags));
}
return options;
}
private generateOption(
option: IOption,
completionsPath: string,
excludedOptions: string[],
): string {
const flags = option.flags;
let excludedFlags = option.conflicts?.length
? [
...excludedOptions,
...option.conflicts.map((opt) => "--" + opt.replace(/^--/, "")),
]
: excludedOptions;
excludedFlags = option.collect ? excludedFlags : [
...excludedFlags,
...flags,
];
let args = "";
for (const arg of option.args) {
const action = this.addAction(arg, completionsPath);
if (arg.variadic) {
args += `${arg.optionalValue ? "::" : ":"}${arg.name}:->${action.name}`;
} else {
args += `${arg.optionalValue ? "::" : ":"}${arg.name}:->${action.name}`;
}
}
let description: string = option.description
.trim()
.split("\n")
.shift() as string;
// escape brackets and quotes
description = description
.replace(/\[/g, "\\[")
.replace(/]/g, "\\]")
.replace(/"/g, '\\"')
.replace(/'/g, "'\"'\"'");
const collect: string = option.collect ? "*" : "";
if (option.standalone) {
return `'(- *)'{${collect}${flags}}'[${description}]${args}'`;
} else {
const excluded: string = excludedFlags.length
? `'(${excludedFlags.join(" ")})'`
: "";
if (collect || flags.length > 1) {
return `${excluded}{${collect}${flags}}'[${description}]${args}'`;
} else {
return `${excluded}${flags}'[${description}]${args}'`;
}
}
}
private addAction(arg: IArgument, cmd: string): ICompletionAction {
const action = `${arg.name}-${arg.action}`;
if (!this.actions.has(action)) {
this.actions.set(action, {
arg: arg,
label: `${arg.name}: ${arg.action}`,
name: action,
cmd,
});
}
return this.actions.get(action) as ICompletionAction;
}
private generateActions(command: Command): string {
let actions: string[] = [];
if (this.actions.size) {
actions = Array
.from(this.actions)
.map(([name, action]) =>
`${name}) __${
replaceSpecialChars(this.cmd.getName())
}_complete ${action.arg.name} ${action.arg.action} ${action.cmd} ;;`
);
}
if (command.hasCommands(false)) {
actions.unshift(`command_args) _command_args ;;`);
}
if (actions.length) {
return `\n\n case "$state" in\n ${actions.join("\n ")}\n esac`;
}
return "";
}
}
function replaceSpecialChars(str: string): string {
return str.replace(/[^a-zA-Z0-9]/g, "_");
}