Skip to content

Commit

Permalink
feat(command): add --name option to completion commands (#587)
Browse files Browse the repository at this point in the history
  • Loading branch information
c4spar authored Apr 25, 2023
1 parent 7f0ec49 commit f9368eb
Show file tree
Hide file tree
Showing 11 changed files with 1,177 additions and 230 deletions.
12 changes: 8 additions & 4 deletions command/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2150,11 +2150,15 @@ export class Command<
return this.aliases;
}

/** Get full command path. */
public getPath(): string {
/**
* Get full command path.
*
* @param name Override the main command name.
*/
public getPath(name?: string): string {
return this._parent
? this._parent.getPath() + " " + this._name
: this._name;
? this._parent.getPath(name) + " " + this._name
: name || this._name;
}

/** Get arguments definition. E.g: <input-file:string> <output-file:string> */
Expand Down
47 changes: 30 additions & 17 deletions command/completions/_bash_completions_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@ import { FileType } from "../types/file.ts";
/** Generates bash completions script. */
export class BashCompletionsGenerator {
/** Generates bash completions script for given command. */
public static generate(cmd: Command) {
return new BashCompletionsGenerator(cmd).generate();
public static generate(name: string, cmd: Command) {
return new BashCompletionsGenerator(name, cmd).generate();
}

private constructor(protected cmd: Command) {}
private constructor(
protected name: string,
protected cmd: Command,
) {}

/** Generates bash completions code. */
private generate(): string {
const path = this.cmd.getPath();
const path = this.cmd.getPath(this.name);
const version: string | undefined = this.cmd.getVersion()
? ` v${this.cmd.getVersion()}`
: "";
Expand All @@ -31,15 +34,15 @@ _${replaceSpecialChars(path)}() {
opts=()
listFiles=0
_${replaceSpecialChars(this.cmd.getName())}_complete() {
_${replaceSpecialChars(this.name)}_complete() {
local action="$1"; shift
mapfile -t values < <( ${this.cmd.getName()} completions complete "\${action}" "\${@}" )
mapfile -t values < <( ${this.name} completions complete "\${action}" "\${@}" )
for i in "\${values[@]}"; do
opts+=("$i")
done
}
_${replaceSpecialChars(this.cmd.getName())}_expand() {
_${replaceSpecialChars(this.name)}_expand() {
[ "$cur" != "\${cur%\\\\}" ] && cur="$cur\\\\"
# expand ~username type directory specifications
Expand All @@ -56,10 +59,10 @@ _${replaceSpecialChars(path)}() {
}
# shellcheck disable=SC2120
_${replaceSpecialChars(this.cmd.getName())}_file_dir() {
_${replaceSpecialChars(this.name)}_file_dir() {
listFiles=1
local IFS=$'\\t\\n' xspec #glob
_${replaceSpecialChars(this.cmd.getName())}_expand || return 0
_${replaceSpecialChars(this.name)}_expand || return 0
if [ "\${1:-}" = -d ]; then
# shellcheck disable=SC2206,SC2207,SC2086
Expand All @@ -74,7 +77,7 @@ _${replaceSpecialChars(path)}() {
$( compgen -d -- "$cur" ) )
}
${this.generateCompletions(this.cmd).trim()}
${this.generateCompletions(this.name, this.cmd).trim()}
for word in "\${COMP_WORDS[@]}"; do
case "\${word}" in
Expand Down Expand Up @@ -119,8 +122,13 @@ complete -F _${replaceSpecialChars(path)} -o bashdefault -o default ${path}`;
}

/** Generates bash completions method for given command and child commands. */
private generateCompletions(command: Command, path = "", index = 1): string {
path = (path ? path + " " : "") + command.getName();
private generateCompletions(
name: string,
command: Command,
path = "",
index = 1,
): string {
path = (path ? path + " " : "") + name;
const commandCompletions = this.generateCommandCompletions(
command,
path,
Expand All @@ -129,7 +137,12 @@ complete -F _${replaceSpecialChars(path)} -o bashdefault -o default ${path}`;
const childCommandCompletions: string = command.getCommands(false)
.filter((subCommand: Command) => subCommand !== command)
.map((subCommand: Command) =>
this.generateCompletions(subCommand, path, index + 1)
this.generateCompletions(
subCommand.getName(),
subCommand,
path,
index + 1,
)
)
.join("");

Expand Down Expand Up @@ -214,10 +227,10 @@ ${childCommandCompletions}`;
if (args.length) {
const type = command.getType(args[0].type);
if (type && type.handler instanceof FileType) {
return `_${replaceSpecialChars(this.cmd.getName())}_file_dir`;
return `_${replaceSpecialChars(this.name)}_file_dir`;
}
// @TODO: add support for multiple arguments
return `_${replaceSpecialChars(this.cmd.getName())}_complete ${
return `_${replaceSpecialChars(this.name)}_complete ${
args[0].action
}${path}`;
}
Expand All @@ -234,10 +247,10 @@ ${childCommandCompletions}`;
if (args.length) {
const type = command.getType(args[0].type);
if (type && type.handler instanceof FileType) {
return `opts=(); _${replaceSpecialChars(this.cmd.getName())}_file_dir`;
return `opts=(); _${replaceSpecialChars(this.name)}_file_dir`;
}
// @TODO: add support for multiple arguments
return `opts=(); _${replaceSpecialChars(this.cmd.getName())}_complete ${
return `opts=(); _${replaceSpecialChars(this.name)}_complete ${
args[0].action
}${path}`;
}
Expand Down
36 changes: 20 additions & 16 deletions command/completions/_fish_completions_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,27 @@ interface CompleteOptions {
/** Fish completions generator. */
export class FishCompletionsGenerator {
/** Generates fish completions script for given command. */
public static generate(cmd: Command) {
return new FishCompletionsGenerator(cmd).generate();
public static generate(name: string, cmd: Command) {
return new FishCompletionsGenerator(name, cmd).generate();
}

private constructor(protected cmd: Command) {}
private constructor(
protected name: string,
protected cmd: Command,
) {}

/** Generates fish completions script. */
private generate(): string {
const path = this.cmd.getPath();
const path = this.cmd.getPath(this.name);
const version: string | undefined = this.cmd.getVersion()
? ` v${this.cmd.getVersion()}`
: "";

return `#!/usr/bin/env fish
# fish completion support for ${path}${version}
function __fish_${replaceSpecialChars(this.cmd.getName())}_using_command
set -l cmds ${getCommandFnNames(this.cmd).join(" ")}
function __fish_${replaceSpecialChars(this.name)}_using_command
set -l cmds ${getCommandFnNames(this.name, this.cmd).join(" ")}
set -l words (commandline -opc)
set -l cmd "_"
for word in $words
Expand All @@ -54,18 +57,18 @@ function __fish_${replaceSpecialChars(this.cmd.getName())}_using_command
return 1
end
${this.generateCompletions(this.cmd).trim()}`;
${this.generateCompletions(this.name, this.cmd).trim()}`;
}

private generateCompletions(command: Command): string {
private generateCompletions(name: string, command: Command): string {
const parent: Command | undefined = command.getParent();
let result = ``;

if (parent) {
// command
result += "\n" + this.complete(parent, {
description: command.getShortDescription(),
arguments: command.getName(),
arguments: name,
});
}

Expand All @@ -85,7 +88,7 @@ ${this.generateCompletions(this.cmd).trim()}`;
}

for (const subCommand of command.getCommands(false)) {
result += this.generateCompletions(subCommand);
result += this.generateCompletions(subCommand.getName(), subCommand);
}

return result;
Expand Down Expand Up @@ -114,11 +117,11 @@ ${this.generateCompletions(this.cmd).trim()}`;

private complete(command: Command, options: CompleteOptions) {
const cmd = ["complete"];
cmd.push("-c", this.cmd.getName());
cmd.push("-c", this.name);
cmd.push(
"-n",
`'__fish_${replaceSpecialChars(this.cmd.getName())}_using_command __${
replaceSpecialChars(command.getPath())
`'__fish_${replaceSpecialChars(this.name)}_using_command __${
replaceSpecialChars(command.getPath(this.name))
}'`,
);
options.shortOption && cmd.push("-s", options.shortOption);
Expand Down Expand Up @@ -148,19 +151,20 @@ ${this.generateCompletions(this.cmd).trim()}`;
if (type && type.handler instanceof FileType) {
return `'(__fish_complete_path)'`;
}
return `'(${this.cmd.getName()} completions complete ${
return `'(${this.name} completions complete ${
arg.action + " " + getCompletionsPath(cmd)
})'`;
}
}

function getCommandFnNames(
name: string,
cmd: Command,
cmds: Array<string> = [],
): Array<string> {
cmds.push(`__${replaceSpecialChars(cmd.getPath())}`);
cmds.push(`__${replaceSpecialChars(cmd.getPath(name))}`);
cmd.getCommands(false).forEach((command) => {
getCommandFnNames(command, cmds);
getCommandFnNames(name, command, cmds);
});
return cmds;
}
Expand Down
34 changes: 20 additions & 14 deletions command/completions/_zsh_completions_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,18 @@ 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();
public static generate(name: string, cmd: Command) {
return new ZshCompletionsGenerator(name, cmd).generate();
}

private constructor(protected cmd: Command) {}
private constructor(
protected name: string,
protected cmd: Command,
) {}

/** Generates zsh completions code. */
private generate(): string {
const path = this.cmd.getPath();
const name = this.cmd.getName();
const path = this.cmd.getPath(this.name);
const version: string | undefined = this.cmd.getVersion()
? ` v${this.cmd.getVersion()}`
: "";
Expand All @@ -35,8 +37,8 @@ export class ZshCompletionsGenerator {
autoload -U is-at-least
# shellcheck disable=SC2154
(( $+functions[__${replaceSpecialChars(name)}_complete] )) ||
function __${replaceSpecialChars(name)}_complete {
(( $+functions[__${replaceSpecialChars(this.name)}_complete] )) ||
function __${replaceSpecialChars(this.name)}_complete {
local name="$1"; shift
local action="$1"; shift
integer ret=1
Expand All @@ -46,7 +48,7 @@ function __${replaceSpecialChars(name)}_complete {
while _tags; do
if _requested "$name"; then
# shellcheck disable=SC2034
lines="$(${name} completions complete "\${action}" "\${@}")"
lines="$(${this.name} completions complete "\${action}" "\${@}")"
values=("\${(ps:\\n:)lines}")
if (( \${#values[@]} )); then
while _next_label "$name" expl "$action"; do
Expand All @@ -57,23 +59,27 @@ function __${replaceSpecialChars(name)}_complete {
done
}
${this.generateCompletions(this.cmd).trim()}
${this.generateCompletions(this.name, 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 {
private generateCompletions(
name: string,
command: Command,
path = "",
): string {
if (
!command.hasCommands(false) && !command.hasOptions(false) &&
!command.hasArguments()
) {
return "";
}

path = (path ? path + " " : "") + command.getName();
path = (path ? path + " " : "") + name;

return `# shellcheck disable=SC2154
(( $+functions[_${replaceSpecialChars(path)}] )) ||
Expand All @@ -90,7 +96,7 @@ function _${replaceSpecialChars(path)}() {` +
command.getCommands(false)
.filter((subCommand: Command) => subCommand !== command)
.map((subCommand: Command) =>
this.generateCompletions(subCommand, path)
this.generateCompletions(subCommand.getName(), subCommand, path)
)
.join("");
}
Expand Down Expand Up @@ -125,7 +131,7 @@ function _${replaceSpecialChars(path)}() {` +
const action = this.addAction(arg, completionsPath);
if (action && command.getCompletion(arg.action)) {
completions += `\n __${
replaceSpecialChars(this.cmd.getName())
replaceSpecialChars(this.name)
}_complete ${action.arg.name} ${action.arg.action} ${action.cmd}`;
}
}
Expand Down Expand Up @@ -348,7 +354,7 @@ function _${replaceSpecialChars(path)}() {` +
.from(this.actions)
.map(([name, action]) =>
`${name}) __${
replaceSpecialChars(this.cmd.getName())
replaceSpecialChars(this.name)
}_complete ${action.arg.name} ${action.arg.action} ${action.cmd} ;;`
);
}
Expand Down
11 changes: 7 additions & 4 deletions command/completions/bash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { dim, italic } from "../deps.ts";
import { BashCompletionsGenerator } from "./_bash_completions_generator.ts";

/** Generates bash completions script. */
export class BashCompletionsCommand extends Command {
#cmd?: Command;
export class BashCompletionsCommand
extends Command<void, void, { name: string }> {
readonly #cmd?: Command;

public constructor(cmd?: Command) {
super();
this.#cmd = cmd;
Expand All @@ -20,9 +22,10 @@ To enable bash completions for this program add following line to your ${
${dim(italic(`source <(${baseCmd.getPath()} completions bash)`))}`;
})
.noGlobals()
.action(() => {
.option("-n, --name <command-name>", "The name of the main command.")
.action(({ name = this.getMainCommand().getName() }) => {
const baseCmd = this.#cmd || this.getMainCommand();
console.log(BashCompletionsGenerator.generate(baseCmd));
console.log(BashCompletionsGenerator.generate(name, baseCmd));
});
}
}
11 changes: 7 additions & 4 deletions command/completions/fish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { dim, italic } from "../deps.ts";
import { FishCompletionsGenerator } from "./_fish_completions_generator.ts";

/** Generates fish completions script. */
export class FishCompletionsCommand extends Command {
#cmd?: Command;
export class FishCompletionsCommand
extends Command<void, void, { name: string }> {
readonly #cmd?: Command;

public constructor(cmd?: Command) {
super();
this.#cmd = cmd;
Expand All @@ -20,9 +22,10 @@ To enable fish completions for this program add following line to your ${
${dim(italic(`source (${baseCmd.getPath()} completions fish | psub)`))}`;
})
.noGlobals()
.action(() => {
.option("-n, --name <command-name>", "The name of the main command.")
.action(({ name = this.getMainCommand().getName() }) => {
const baseCmd = this.#cmd || this.getMainCommand();
console.log(FishCompletionsGenerator.generate(baseCmd));
console.log(FishCompletionsGenerator.generate(name, baseCmd));
});
}
}
Loading

0 comments on commit f9368eb

Please sign in to comment.