mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-23 11:56:00 +01:00
add completion generation for zsh (#137)
This commit is contained in:
parent
3d11b635d8
commit
723ff201f6
135
src/commands/completion.command.ts
Normal file
135
src/commands/completion.command.ts
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user