add completion generation for zsh (#137)

This commit is contained in:
Munif Tanjim 2020-05-26 19:17:41 +06:00 committed by GitHub
parent 3d11b635d8
commit 723ff201f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 157 additions and 0 deletions

View File

@ -0,0 +1,135 @@
import * as program from "commander";
import { Response } from "jslib/cli/models/response";
import { MessageResponse } from "jslib/cli/models/response/messageResponse";
type Option = {
long: string;
short: string;
description: string;
};
type Command = {
commands?: Command[];
options?: Option[];
_name: string;
_description: string;
};
const zshCompletion = (rootName: string, rootCommand: Command) => {
const renderCommandBlock = (name: string, command: Command): string => {
const { commands = [], options = [] } = command;
const hasOptions = options.length > 0;
const hasCommands = commands.length > 0;
const _arguments = options
.map(({ long, short, description }) => {
const aliases = [short, long].filter(Boolean);
const OPTS = aliases.join(",");
const DESCRIPTION = `[${description.replace("'", `'"'"'`)}]`;
return aliases.length > 1
? `'(${aliases.join(" ")})'{${OPTS}}'${DESCRIPTION}'`
: `'${OPTS}${DESCRIPTION}'`;
})
.concat(
`'(-h --help)'{-h,--help}'[output usage information]'`,
hasCommands ? '"1: :->cmnds"' : null,
'"*::arg:->args"'
)
.filter(Boolean);
const commandBlockFunctionParts = [];
if (hasCommands) {
commandBlockFunctionParts.push("local -a commands");
}
if (hasOptions) {
commandBlockFunctionParts.push(
`_arguments -C \\\n ${_arguments.join(` \\\n `)}`
);
}
if (hasCommands) {
commandBlockFunctionParts.push(
`case $state in
cmnds)
commands=(
${commands
.map(({ _name, _description }) => `"${_name}:${_description}"`)
.join("\n ")}
)
_describe "command" commands
;;
esac
case "$words[1]" in
${commands
.map(({ _name }) =>
[`${_name})`, `_${name}_${_name}`, ";;"].join("\n ")
)
.join("\n ")}
esac`
);
}
const commandBlocParts = [
`function _${name} {\n ${commandBlockFunctionParts.join(
"\n\n "
)}\n}`,
];
if (hasCommands) {
commandBlocParts.push(
commands
.map((command) =>
renderCommandBlock(`${name}_${command._name}`, command)
)
.join("\n\n")
);
}
return commandBlocParts.join("\n\n");
};
const render = () => {
return [
`#compdef _${rootName} ${rootName}`,
"",
renderCommandBlock(rootName, rootCommand),
].join("\n");
};
return {
render,
};
};
const validShells = ["zsh"];
export class CompletionCommand {
constructor() {}
async run(cmd: program.Command) {
const shell: typeof validShells[number] = cmd.shell;
if (!shell) {
return Response.badRequest("`shell` was not provided!");
}
if (!validShells.includes(shell)) {
return Response.badRequest(`Unsupported shell!`);
}
let content = "";
if (shell === "zsh") {
content = zshCompletion("bw", cmd.parent).render();
}
const res = new MessageResponse(content, null);
return Response.success(res);
}
}

View File

@ -21,6 +21,8 @@ import { ShareCommand } from './commands/share.command';
import { SyncCommand } from './commands/sync.command';
import { UnlockCommand } from './commands/unlock.command';
import { CompletionCommand } from './commands/completion.command';
import { LogoutCommand } from 'jslib/cli/commands/logout.command';
import { UpdateCommand } from 'jslib/cli/commands/update.command';
@ -660,6 +662,26 @@ export class Program extends BaseProgram {
this.processResponse(response);
});
program
.command('completion')
.description('Generate shell completions.')
.option('--shell <shell>', 'Shell to generate completions for.')
.on('--help', () => {
writeLn('\n Notes:');
writeLn('');
writeLn(' Valid shells are `zsh`.')
writeLn('');
writeLn(' Examples:');
writeLn('');
writeLn(' bw completion --shell zsh');
writeLn('', true);
})
.action(async (cmd: program.Command) => {
const command = new CompletionCommand();
const response = await command.run(cmd);
this.processResponse(response);
})
program
.parse(process.argv);