diff --git a/NodeApp/src/commander/CommanderApp.ts b/NodeApp/src/commander/CommanderApp.ts index e70ce933f692f7cf8b4cac50fdd0b571cb5e4d11..b729c95d25223b799140f889bd489fb1c719e078 100644 --- a/NodeApp/src/commander/CommanderApp.ts +++ b/NodeApp/src/commander/CommanderApp.ts @@ -9,7 +9,7 @@ import { stateConfigFile } from '../config/ConfigFiles'; import semver from 'semver/preload'; import { version } from '../config/Version'; import Config from '../config/Config'; -import { writeBashCompletion, writeFishCompletion } from '../helpers/AutoCompletionHelper'; +import CompletionCommand from './completion/CompletionCommand'; class CommanderApp { @@ -44,8 +44,7 @@ class CommanderApp { this.registerCommands(); - writeBashCompletion(this.program, "bash_completion.sh") - writeFishCompletion(this.program, "dojo.fish") + this.program.on this.program.parse(); } @@ -92,6 +91,7 @@ https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, { SessionCommand.registerOnCommand(this.program); AssignmentCommand.registerOnCommand(this.program); ExerciseCommand.registerOnCommand(this.program); + CompletionCommand.registerOnCommand(this.program); } } diff --git a/NodeApp/src/commander/completion/CompletionCommand.ts b/NodeApp/src/commander/completion/CompletionCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..11df30fce08acce6a70ecd5d43946ac385b1aceb --- /dev/null +++ b/NodeApp/src/commander/completion/CompletionCommand.ts @@ -0,0 +1,25 @@ +import CommanderCommand from '../CommanderCommand'; +import CompletionBashCommand from './subcommands/CompletionBashCommand'; +import CompletionFishCommand from './subcommands/CompletionFishCommand'; +import CompletionZshCommand from './subcommands/CompletionZshCommand'; + + +class CompletionCommand extends CommanderCommand { + protected commandName: string = 'completion'; + + protected defineCommand() { + this.command + .description('Generate completions for bash, fish, or zsh'); + } + + protected defineSubCommands() { + CompletionBashCommand.registerOnCommand(this.command); + CompletionFishCommand.registerOnCommand(this.command); + CompletionZshCommand.registerOnCommand(this.command); + } + + protected async commandAction(): Promise<void> { } +} + + +export default new CompletionCommand(); \ No newline at end of file diff --git a/NodeApp/src/commander/completion/subcommands/CompletionBashCommand.ts b/NodeApp/src/commander/completion/subcommands/CompletionBashCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..5dfde487fa71a56ad02ab12a5886f93c006ae3ae --- /dev/null +++ b/NodeApp/src/commander/completion/subcommands/CompletionBashCommand.ts @@ -0,0 +1,61 @@ +import { existsSync, writeFileSync } from 'fs'; +import inquirer from 'inquirer'; +import CommanderCommand from '../../CommanderCommand'; +import { generateBashCompletion, getRoot } from '../../../helpers/AutoCompletionHelper'; + +class CompletionBashCommand extends CommanderCommand { + protected commandName: string = 'bash'; + + protected default_filename: string = './dojo_bash_completion.sh' + + writeFile(filename: string) { + const installInstructions: string = ` +The easiest way to install the completion is to append the content of the +generated file to the end of the '~/.bash_completion' file or to +overwrite it, if it only contains the 'dojo' completion. + +This can be performed by either + +cat ${filename} > ~/.bash_completion # overwrites .bash_completion +cat ${filename} >> ~/.bash_completion # appends to .bash_completion + +For more details: <https://github.com/scop/bash-completion/blob/master/README.md> +` + try { + writeFileSync(filename, generateBashCompletion(getRoot(this.command))) + console.log(`Bash completion successfully written.`) + console.log(`${installInstructions}`) + } catch (error) { + console.log(`Error: ${error}, failed to write ${filename}`) + } + } + + protected defineCommand() { + this.command + .description('generate bash completion') + .option(`-f, --file <filename>', 'complete path of the filename where the bash completion will be stored (default to ${this.default_filename}).`) + .action(this.commandAction.bind(this)); + } + + protected async commandAction(options: { file?: string }): Promise<void> { + const filename = options.file ?? this.default_filename + if (existsSync(filename)) { + // File exists in path + const confirm: boolean = (await inquirer.prompt({ + name: 'confirm', + message: `${options.file} is going to be overwritten. Are you sure?`, + type: 'confirm', + default: false + })).confirm; + + if (confirm) { + this.writeFile(filename) + } + } else { + this.writeFile(filename) + } + } +} + + +export default new CompletionBashCommand(); \ No newline at end of file diff --git a/NodeApp/src/commander/completion/subcommands/CompletionFishCommand.ts b/NodeApp/src/commander/completion/subcommands/CompletionFishCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..b6ad117aa4c0272cffd0e57810d1bf3591bf2a3e --- /dev/null +++ b/NodeApp/src/commander/completion/subcommands/CompletionFishCommand.ts @@ -0,0 +1,56 @@ +import { existsSync, writeFileSync } from 'fs'; +import { generateFishCompletion, getRoot } from '../../../helpers/AutoCompletionHelper'; +import CommanderCommand from '../../CommanderCommand'; +import inquirer from 'inquirer'; + + +class CompletionFishCommand extends CommanderCommand { + protected commandName: string = 'fish'; + + protected default_filename: string = './dojo.fish' + + writeFile(filename: string) { + const installInstructions: string = ` +The easiest way to install the completion is to copy the ${filename} into the +~/.config/fish/completions directory + +cp -i ${filename} ~/.config/fish/completions # interactive cp to avoid accidents +` + try { + writeFileSync(filename, generateFishCompletion(getRoot(this.command))) + console.log(`Bash completion successfully written.`) + console.log(`${installInstructions}`) + } catch (error) { + console.log(`Error: ${error}, failed to write ${filename}`) + } + } + + protected defineCommand() { + this.command + .description('generate fish completion') + .option('-f, --file <filename>', 'filename where the bash completion will be stored (default to ./dojo.fish).') + .action(this.commandAction.bind(this)); + } + + protected async commandAction(options: { file?: string }): Promise<void> { + const filename = options.file ?? this.default_filename + if (existsSync(filename)) { + // File exists in path + const confirm: boolean = (await inquirer.prompt({ + name: 'confirm', + message: `${options.file} is going to be overwritten. Are you sure?`, + type: 'confirm', + default: false + })).confirm; + + if (confirm) { + this.writeFile(filename) + } + } else { + this.writeFile(filename) + } + } +} + + +export default new CompletionFishCommand(); \ No newline at end of file diff --git a/NodeApp/src/commander/completion/subcommands/CompletionZshCommand.ts b/NodeApp/src/commander/completion/subcommands/CompletionZshCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..d162dc98393d28fbb2d3c946010b06b58133be86 --- /dev/null +++ b/NodeApp/src/commander/completion/subcommands/CompletionZshCommand.ts @@ -0,0 +1,69 @@ +import { existsSync, writeFileSync } from 'fs'; +import CommanderCommand from '../../CommanderCommand'; +import { generateBashCompletion, getRoot } from '../../../helpers/AutoCompletionHelper'; +import inquirer from 'inquirer'; + + + +class CompletionZshCommand extends CommanderCommand { + protected commandName: string = 'zsh'; + + protected default_filename: string = './dojo_bash_completion.sh' + + writeFile(filename: string) { + const installInstructions: string = ` +The easiest way to install the completion is to append the content of the +generated file to the end of the '~/.bash_completion' file or to +overwrite it, if it only contains the 'dojo' completion. + +This can be performed by either + +cat ${filename} > ~/.bash_completion # overwrites .bash_completion +cat ${filename} >> ~/.bash_completion # appends to .bash_completion + +For more details: <https://github.com/scop/bash-completion/blob/master/README.md> + +Next add the following lines to your ~/.zprofile file: + +autoload -U +X compinit && compinit +autoload -U +X bashcompinit && bashcompinit +source .bash_completion +` + try { + writeFileSync(filename, generateBashCompletion(getRoot(this.command))) + console.log(`Bash completion successfully written.`) + console.log(`${installInstructions}`) + } catch (error) { + console.log(`Error: ${error}, failed to write ${filename}`) + } + } + + protected async commandAction(options: { file?: string }): Promise<void> { + const filename = options.file ?? this.default_filename + if (existsSync(filename)) { + // File exists in path + const confirm: boolean = (await inquirer.prompt({ + name: 'confirm', + message: `${options.file} is going to be overwritten. Are you sure?`, + type: 'confirm', + default: false + })).confirm; + + if (confirm) { + this.writeFile(filename) + } + } else { + this.writeFile(filename) + } + } + + protected defineCommand() { + this.command + .description('generate zsh completion, which is derived from the bash completion') + .option('-f, --file <filename>', 'bash completion filename (defaults to ./dojo_bash_completion.sh).') + .action(this.commandAction.bind(this)); + } +} + + +export default new CompletionZshCommand(); \ No newline at end of file diff --git a/NodeApp/src/helpers/AutoCompletionHelper.ts b/NodeApp/src/helpers/AutoCompletionHelper.ts index 2e1f42ec093ee7f0c55b29a7cb861d59b6cfafad..3af7148ae53fbe5e61b7cf887ffcac87e96ceb2b 100644 --- a/NodeApp/src/helpers/AutoCompletionHelper.ts +++ b/NodeApp/src/helpers/AutoCompletionHelper.ts @@ -53,6 +53,15 @@ function computeHeight(cmd: Command | null): number { return height } +// Computes the maximum number of commands until the root is reached +export function getRoot(cmd: Command): Command { + if (cmd.parent == null) { + return cmd + } else { + return getRoot(cmd.parent) + } +} + function getOptions(cmd: Command): string { // we remove <args>, [command], and , from option lines return cmd.options.filter(opt => !opt.hidden).map(opt => @@ -93,7 +102,7 @@ function generateBashSubCommands(cmd: Command, current: number, maxDepth: number } } -export function writeBashCompletion(root: Command, filename: string) { +export function generateBashCompletion(root: Command): string { const depth = computeDepth(root) let data = addLine(0, '#/usr/bin/env bash\nfunction _dojo_completions()') @@ -110,7 +119,7 @@ export function writeBashCompletion(root: Command, filename: string) { + addLine(0, '}') + addLine(0, 'complete -F _dojo_completions dojo') - writeFileSync(filename, data); + return data } const prefix = 'complete -f -c dojo -n \'__fish_dojo_using_commands' @@ -134,7 +143,7 @@ function optionsToString(cmd: Command): string { }).join('\n').concat('\n') } -export function writeFishCompletion(root: Command, filename: string) { +export function generateFishCompletion(root: Command): string { const commands = flatten(root) const data = fishFunction.concat( @@ -149,7 +158,7 @@ export function writeFishCompletion(root: Command, filename: string) { }).join('\n').concat('\n')).join('') ) - writeFileSync(filename, data); + return data }