Skip to content
Snippets Groups Projects
Commit a4d97e4f authored by michael.minelli's avatar michael.minelli
Browse files

Completion => Adapt zsh and bash completion to updated procedure

parent 87f887d6
No related branches found
No related tags found
No related merge requests found
......@@ -2,6 +2,8 @@ import CommanderCommand from '../CommanderCommand';
import CompletionBashCommand from './subcommands/CompletionBashCommand';
import CompletionFishCommand from './subcommands/CompletionFishCommand';
import CompletionZshCommand from './subcommands/CompletionZshCommand';
import CompletionGetCommand from './subcommands/CompletionGetCommand';
import CompletionScriptCommand from './subcommands/CompletionScriptCommand';
class CompletionCommand extends CommanderCommand {
......@@ -16,6 +18,9 @@ class CompletionCommand extends CommanderCommand {
CompletionBashCommand.registerOnCommand(this.command);
CompletionFishCommand.registerOnCommand(this.command);
CompletionZshCommand.registerOnCommand(this.command);
CompletionGetCommand.registerOnCommand(this.command);
CompletionScriptCommand.registerOnCommand(this.command);
}
protected async commandAction(): Promise<void> {
......
import CommanderCommand from '../../CommanderCommand';
import { generateBashCompletion, getRoot, tryRenameFile } from '../../../helpers/AutoCompletionHelper';
import ora from 'ora';
import TextStyle from '../../../types/TextStyle';
import fs from 'fs-extra';
import path from 'path';
import os from 'os';
import GlobalHelper from '../../../helpers/GlobalHelper';
import { updateRcFile } from '../../../helpers/AutoCompletionHelper';
class CompletionBashCommand extends CommanderCommand {
protected commandName: string = 'bash';
private readonly installPath = path.join(os.homedir(), '.bash_completion');
private readonly bashrcPath = path.join(os.homedir(), '.bashrc');
private readonly completionCommand = `
# Added by DojoCLI
source <(dojo completion script bash)
`;
protected defineCommand() {
GlobalHelper.completionCommandDefinition(this.command)
.description('generate bash completion')
this.command.description('generate bash completion')
.action(this.commandAction.bind(this));
}
private writeFile(filename: string, showInstructions: boolean) {
const spinner: ora.Ora = ora(`Writing Bash completion in ${ TextStyle.CODE(filename) } ...`).start();
try {
fs.mkdirsSync(path.dirname(filename));
fs.writeFileSync(filename, generateBashCompletion(getRoot(this.command)));
spinner.succeed(`Bash completion successfully written in ${ TextStyle.CODE(filename) }`);
if ( showInstructions ) {
console.log(`
The easiest way to install the completion is to append the content of the generated file to the end of the ${ TextStyle.CODE('~/.bash_completion') } file or to overwrite it, if it only contains the 'dojo' completion.
This can be performed by either
${ TextStyle.CODE(`
cat ${ filename } > ~/.bash_completion # overwrites .bash_completion
cat ${ filename } >> ~/.bash_completion # appends to .bash_completion
`) }
For more details: ${ TextStyle.URL('https://github.com/scop/bash-completion/blob/master/README.md') }
`);
}
} catch ( error ) {
spinner.fail(`Bash completion error: ${ error }`);
protected async commandAction(): Promise<void> {
updateRcFile('bash', this.bashrcPath, this.completionCommand);
}
}
/* The completion command must do the following:
- if a file is provided:
- if force is not enabled:
- check if the bash completion file exists:
- if it exists, prompt the user that it will be overwritten
- if ok is given write the file and prompt that a backup has been created
- else create the file containing the completion
- else
- if force is not enabled:
- check if the default file exists:
- if it exists, prompt the user that it will be erased:
- if ok is given write the file and prompt that a backup has been created
- else
- create the file containing the completion
- create a .zprofile or append the appropriate commands into the .zprofile file
*/
protected async commandAction(options: { file: string, force: boolean }): Promise<void> {
const filePath = path.resolve(options.file ?? this.installPath); // change that if file is empty
const showInstructions = !!options.file;
if ( !(await tryRenameFile(filePath, options.force)) ) { // means renaming was interrupted
return;
}
this.writeFile(filePath, showInstructions);
}
}
export default new CompletionBashCommand();
\ No newline at end of file
import CommanderCommand from '../../CommanderCommand';
import { generateBashCompletion, getRoot, tryRenameFile } from '../../../helpers/AutoCompletionHelper';
import ora from 'ora';
import TextStyle from '../../../types/TextStyle';
import path from 'path';
import { homedir } from 'os';
import fs from 'fs-extra';
import GlobalHelper from '../../../helpers/GlobalHelper';
import { updateRcFile } from '../../../helpers/AutoCompletionHelper';
class CompletionZshCommand extends CommanderCommand {
protected commandName: string = 'zsh';
private readonly zprofile: string = path.join(homedir(), '.zprofile');
private readonly bashCompletion = path.join(homedir(), '.bash_completion');
private readonly loadBashCompletion = `
private readonly zshrcPath: string = path.join(homedir(), '.zshrc');
private readonly completionCommand = `
# Added by DojoCLI
autoload -U +X compinit && compinit
autoload -U +X bashcompinit && bashcompinit
source ${ this.bashCompletion }
source <(dojo completion script zsh)
`;
protected defineCommand() {
GlobalHelper.completionCommandDefinition(this.command)
.description('generate zsh completion, which is derived from the bash completion')
this.command.description('generate zsh completion')
.action(this.commandAction.bind(this));
}
private writeFile(filename: string, showInstructions: boolean) {
const spinner: ora.Ora = ora(`Writing Bash completion in ${ TextStyle.CODE(filename) } ...`).start();
try {
fs.mkdirsSync(path.dirname(filename));
fs.writeFileSync(filename, generateBashCompletion(getRoot(this.command)));
spinner.succeed(`Bash completion successfully written in ${ TextStyle.CODE(filename) }`);
if ( showInstructions ) {
const zprofileContent = TextStyle.CODE(`
autoload -U +X compinit && compinit
autoload -U +X bashcompinit && bashcompinit
source ${ filename }
`);
console.log(`
The easiest way to install the completion is to append the content of the generated file to the end of the ${ TextStyle.CODE(filename) } file or to overwrite it, if it only contains the 'dojo' completion.
This can be performed by either ${ TextStyle.CODE(`
cat ${ filename } > ~/.bash_completion # overwrites .bash_completion
cat ${ filename } >> ~/.bash_completion # appends to .bash_completion`) }
For more details: ${ TextStyle.URL('https://github.com/scop/bash-completion/blob/master/README.md') }
Next add the following lines to your ${ TextStyle.CODE('~/.zprofile') } file: ${ zprofileContent } `);
}
} catch ( error ) {
spinner.fail(`Bash completion writing error: ${ error }`);
}
}
protected addToZprofile(zprofilePath: string, bashPath: string) {
const spinner: ora.Ora = ora(`Modifying ${ zprofilePath } ...`).start();
if ( fs.existsSync(zprofilePath) ) {
const data = fs.readFileSync(zprofilePath);
let updated = false;
try {
if ( !data.includes('autoload -U +X compinit && compinit') ) {
fs.appendFileSync(zprofilePath, '\nautoload -U +X compinit && compinit');
updated = true;
}
if ( !data.includes('autoload -U +X bashcompinit && bashcompinit') ) {
fs.appendFileSync(zprofilePath, '\nautoload -U +X bashcompinit && bashcompinit');
updated = true;
}
if ( !data.includes(`source ${ bashPath }`) ) {
fs.appendFileSync(zprofilePath, `\nsource ${ bashPath }`);
updated = true;
}
} catch {
spinner.fail(`Error appending in ${ this.zprofile }`);
return;
}
spinner.succeed(updated ? `Zsh profile updated.` : `Zsh profile already up to date.`);
} else {
try {
fs.writeFileSync(zprofilePath, this.loadBashCompletion);
spinner.succeed(`Zsh profile written.`);
} catch ( error ) {
spinner.fail(`Error writing in ${ this.zprofile }`);
}
}
}
/* The completion command must do the following:
- if a file is provided:
- if force is not enabled:
- check if the bash completion file exists:
- if it exists, prompt the user that it will be overwritten
- if ok is given write the file and prompt that a backup has been created
- else create the file containing the completion
- else
- if force is not enabled:
- check if the default file exists:
- if it exists, prompt the user that it will be erased:
- if ok is given write the file and prompt that a backup has been created
- else
- create the file containing the completion
- create a .zprofile or append the appropriate commands into the .zprofile file
*/
protected async commandAction(options: { file: string, force: boolean }): Promise<void> {
const filePath = path.resolve(options.file ?? this.bashCompletion); // change that if file is empty
const showInstructions = !!options.file;
if ( !(await tryRenameFile(filePath, options.force)) ) { // means renaming was interrupted
return;
}
this.writeFile(filePath, showInstructions);
// Do not modify if custom file was provided
if ( !options.file ) {
this.addToZprofile(this.zprofile, filePath);
}
protected async commandAction(): Promise<void> {
updateRcFile('zsh', this.zshrcPath, this.completionCommand);
}
}
......
......@@ -3,6 +3,7 @@ import { existsSync, renameSync } from 'fs';
import ora from 'ora';
import TextStyle from '../types/TextStyle';
import inquirer from 'inquirer';
import fs from 'fs-extra';
function renameFile(filename: string, showWarning: boolean) {
......@@ -126,6 +127,19 @@ function addLine(identLevel: number, pattern: string): string {
return `${ ' '.repeat(identLevel) }${ pattern }\n`;
}
export function getCommandFromChain(currentCmd: Command, chain: Array<string>): Command | null {
if ( chain.length === 0 ) {
return currentCmd;
} else {
const subCmd = currentCmd.commands.find(c => c.name() === chain[0]);
if ( subCmd === undefined ) {
return currentCmd;
} else {
return getCommandFromChain(subCmd, chain.slice(1));
}
}
}
function generateBashSubCommands(cmd: Command, current: number, maxDepth: number, ident: number): string {
if ( current === maxDepth ) {
return addLine(ident, `case "\${COMP_WORDS[$COMP_CWORD - ${ maxDepth - current + 1 }]}" in`) + addLine(ident + 1, `${ cmd.name() })`) + addLine(ident + 2, `words="${ commandsAndOptionsToString(cmd) }"`) + addLine(ident + 1, ';;') + addLine(ident + 1, '*)') + addLine(ident + 1, ';;') + addLine(ident, 'esac');
......@@ -178,6 +192,32 @@ export function generateFishCompletion(root: Command): string {
commands.filter(c => !isHidden(c)).filter(cmd => !isLeaf(cmd)).map(cmd => cmd.commands.filter(c => !isHidden(c)).map(subCmd => `${ prefix } ${ computeHeight(cmd) } ${ generateCommandChain(cmd) }' -a ${ subCmd.name() } -d "${ subCmd.description() }"`).join('\n').concat('\n')).join(''));
}
export function updateRcFile(shellType: 'bash' | 'zsh', filePath: string, completionCommand: string) {
const spinner: ora.Ora = ora(`Modifying ${ filePath } ...`).start();
if ( fs.existsSync(filePath) ) {
const data = fs.readFileSync(filePath);
let updated = false;
try {
if ( !data.includes(completionCommand) ) {
fs.appendFileSync(filePath, completionCommand);
updated = true;
}
} catch {
spinner.fail(`Error appending in ${ filePath }`);
return;
}
spinner.succeed(updated ? `${ shellType } updated.` : `${ shellType } already up to date.`);
} else {
try {
fs.writeFileSync(filePath, completionCommand);
spinner.succeed(`${ shellType } written.`);
} catch ( error ) {
spinner.fail(`Error writing in ${ filePath }`);
}
}
}
// The following code should create a bash completion automatically from the Commander
// CLI library. The file should look something like that (it looks at the time
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment