diff --git a/NodeApp/.idea/.gitignore b/NodeApp/.idea/.gitignore index 13566b81b018ad684f3a35fee301741b2734c8f4..a9d7db9c0a81b2db47ca92e4e180b30090b27632 100644 --- a/NodeApp/.idea/.gitignore +++ b/NodeApp/.idea/.gitignore @@ -6,3 +6,5 @@ # Datasource local storage ignored files /dataSources/ /dataSources.local.xml +# GitHub Copilot persisted chat sessions +/copilot/chatSessions diff --git a/NodeApp/src/commander/CommanderApp.ts b/NodeApp/src/commander/CommanderApp.ts index d7ecd85f6471d907525a42449cd8b3e95602dae1..1f3619e55f76e4d40af303dc9fa0f39969521f03 100644 --- a/NodeApp/src/commander/CommanderApp.ts +++ b/NodeApp/src/commander/CommanderApp.ts @@ -14,7 +14,21 @@ import SessionCommand from './auth/SessionCommand'; class CommanderApp { - program: Command = new Command(); + public program: Command = new Command(); + + private readonly commandHookDisabled: Array<string> = [ 'completion' ]; + + private hasToExecuteHook(actionCommand: Command): boolean { + if ( actionCommand.parent == null ) { + return true; + } else { + if ( this.commandHookDisabled.includes(actionCommand.name()) ) { + return false; + } + + return this.hasToExecuteHook(actionCommand.parent); + } + } constructor() { this.program @@ -30,11 +44,16 @@ class CommanderApp { .option('-H, --host <string>', 'override the Dojo API endpoint', ClientsSharedConfig.apiURL) .option('-I, --interactive', 'show interactive interface when available', Config.interactiveMode) .addOption(new Option('--debug').hideHelp()) - .hook('preAction', () => { - this.warnDevelopmentVersion(); - }).hook('postAction', () => { - this.informNewVersion(); - }); + .hook('preAction', (_thisCommand: Command, actionCommand: Command) => { + if ( this.hasToExecuteHook(actionCommand) ) { + this.warnDevelopmentVersion(); + } + }) + .hook('postAction', (_thisCommand: Command, actionCommand: Command) => { + if ( this.hasToExecuteHook(actionCommand) ) { + this.informNewVersion(); + } + }); this.program.on('option:host', () => { ClientsSharedConfig.apiURL = this.program.opts().host; diff --git a/NodeApp/src/commander/completion/CompletionCommand.ts b/NodeApp/src/commander/completion/CompletionCommand.ts index 1ee0668b994796b04d303357de071c7a3b4f75ff..ba4f3cfe026a2da2f66992aa2ec535c35afcf5b5 100644 --- a/NodeApp/src/commander/completion/CompletionCommand.ts +++ b/NodeApp/src/commander/completion/CompletionCommand.ts @@ -1,7 +1,7 @@ -import CommanderCommand from '../CommanderCommand'; -import CompletionBashCommand from './subcommands/CompletionBashCommand'; -import CompletionFishCommand from './subcommands/CompletionFishCommand'; -import CompletionZshCommand from './subcommands/CompletionZshCommand'; +import CommanderCommand from '../CommanderCommand'; +import CompletionCreateUpdateCommand from './subcommands/CompletionCreateUpdateCommand'; +import CompletionGetCommand from './subcommands/CompletionGetCommand'; +import CompletionScriptCommand from './subcommands/CompletionScriptCommand'; class CompletionCommand extends CommanderCommand { @@ -13,9 +13,9 @@ class CompletionCommand extends CommanderCommand { } protected defineSubCommands() { - CompletionBashCommand.registerOnCommand(this.command); - CompletionFishCommand.registerOnCommand(this.command); - CompletionZshCommand.registerOnCommand(this.command); + CompletionCreateUpdateCommand.registerOnCommand(this.command); + CompletionGetCommand.registerOnCommand(this.command); + CompletionScriptCommand.registerOnCommand(this.command); } protected async commandAction(): Promise<void> { diff --git a/NodeApp/src/commander/completion/subcommands/CompletionBashCommand.ts b/NodeApp/src/commander/completion/subcommands/CompletionBashCommand.ts deleted file mode 100644 index d135440dccf7310afbb1304c15bbbd507f3a6eaa..0000000000000000000000000000000000000000 --- a/NodeApp/src/commander/completion/subcommands/CompletionBashCommand.ts +++ /dev/null @@ -1,76 +0,0 @@ -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'; - - -class CompletionBashCommand extends CommanderCommand { - protected commandName: string = 'bash'; - - private readonly installPath = path.join(os.homedir(), '.bash_completion'); - - protected defineCommand() { - GlobalHelper.completionCommandDefinition(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 }`); - } - } - - /* 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 diff --git a/NodeApp/src/commander/completion/subcommands/CompletionCreateUpdateCommand.ts b/NodeApp/src/commander/completion/subcommands/CompletionCreateUpdateCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..edc521942681dd07339e1607cc2423eeaa9586d7 --- /dev/null +++ b/NodeApp/src/commander/completion/subcommands/CompletionCreateUpdateCommand.ts @@ -0,0 +1,101 @@ +import CommanderCommand from '../../CommanderCommand'; +import { Option } from 'commander'; +import { generateFishCompletion, getRoot, tryRenameFile, updateRcFile } from '../../../helpers/AutoCompletionHelper'; +import os, { homedir } from 'os'; +import path from 'path'; +import ora from 'ora'; +import fs from 'fs-extra'; +import TextStyle from '../../../types/TextStyle'; + + +class CompletionCreateUpdateCommand extends CommanderCommand { + protected commandName: string = 'create'; + protected aliasNames: Array<string> = [ 'update' ]; + + + protected defineCommand() { + this.command.description('generate shell completion') + .addOption(new Option('-s, --shell <shell>', 'shell type').choices([ 'bash', 'zsh', 'fish' ]).makeOptionMandatory(true)) + .addOption(new Option('-f, --file <filename>', '(only for fish shell)').implies({ shell: 'fish' })) + .addOption(new Option('-y, --force', 'don\'t ask for file overwrite confirmation (only for fish shell)').implies({ shell: 'fish' })) + .action(this.commandAction.bind(this)); + } + + private bash() { + const completionCommand = ` +# Added by DojoCLI +eval "$(dojo completion script bash)" +`; + updateRcFile('bash', path.join(os.homedir(), '.bashrc'), completionCommand); + } + + private zsh() { + const completionCommand = ` +# Added by DojoCLI +source <(dojo completion script zsh) +`; + updateRcFile('zsh', path.join(homedir(), '.zshrc'), completionCommand); + } + + /* The completion command must do the following: + - if a file is provided: + - if force is not enabled: + - check if the 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 + - 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 + */ + private async fish(options: { file: string, force: boolean }) { + const filePath = path.resolve(options.file ?? path.join(os.homedir(), '.config/fish/completions/dojo.fish')); + const showInstructions = !!options.file; + if ( !(await tryRenameFile(filePath, options.force)) ) { // means renaming was interrupted + return; + } + + const spinner: ora.Ora = ora(`Writing fish completion in ${ filePath }...`).start(); + + try { + fs.mkdirsSync(path.dirname(filePath)); + + fs.writeFileSync(filePath, generateFishCompletion(getRoot(this.command))); + + spinner.succeed(`Fish completion successfully written in ${ filePath }.`); + if ( showInstructions ) { + const cpCommand = ` cp -i ${ filePath } ~/.config/fish/completions # interactive cp to avoid accidents `; + console.log(` +The easiest way to install the completion is to copy the ${ TextStyle.CODE(filePath) } into the ${ TextStyle.CODE('~/.config/fish/completions') } directory. + +${ TextStyle.CODE(cpCommand) }`); + } + } catch ( error ) { + spinner.fail(`Fish completion error: ${ error }.`); + } + } + + protected async commandAction(options: { shell: 'bash' | 'zsh' | 'fish', file: string, force: boolean }): Promise<void> { + switch ( options.shell ) { + case 'bash': + this.bash(); + break; + case 'zsh': + this.zsh(); + break; + case 'fish': + await this.fish(options); + break; + default: + console.error('Unsupported shell.'); + break; + } + } +} + + +export default new CompletionCreateUpdateCommand(); \ No newline at end of file diff --git a/NodeApp/src/commander/completion/subcommands/CompletionFishCommand.ts b/NodeApp/src/commander/completion/subcommands/CompletionFishCommand.ts deleted file mode 100644 index 2be5716ee17ae7347d146533f48c8594801b73e5..0000000000000000000000000000000000000000 --- a/NodeApp/src/commander/completion/subcommands/CompletionFishCommand.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { generateFishCompletion, getRoot, tryRenameFile } from '../../../helpers/AutoCompletionHelper'; -import CommanderCommand from '../../CommanderCommand'; -import ora from 'ora'; -import TextStyle from '../../../types/TextStyle'; -import path from 'path'; -import os from 'os'; -import fs from 'fs-extra'; -import GlobalHelper from '../../../helpers/GlobalHelper'; - - -class CompletionFishCommand extends CommanderCommand { - protected commandName: string = 'fish'; - - private readonly installPath = path.join(os.homedir(), '.config/fish/completions/dojo.fish'); - - protected defineCommand() { - GlobalHelper.completionCommandDefinition(this.command) - .description('generate fish completion') - .action(this.commandAction.bind(this)); - } - - private writeFile(filename: string, showInstructions: boolean) { - const spinner: ora.Ora = ora(`Writing fish completion in ${ filename }...`).start(); - - try { - fs.mkdirsSync(path.dirname(filename)); - - fs.writeFileSync(filename, generateFishCompletion(getRoot(this.command))); - - spinner.succeed(`Fish completion successfully written in ${ filename }.`); - if ( showInstructions ) { - const cpCommand = ` cp -i ${ filename } ~/.config/fish/completions # interactive cp to avoid accidents `; - console.log(` -The easiest way to install the completion is to copy the ${ TextStyle.CODE(filename) } into the ${ TextStyle.CODE('~/.config/fish/completions') } directory. - -${ TextStyle.CODE(cpCommand) }`); - } - } catch ( error ) { - spinner.fail(`Fish completion error: ${ error }.`); - } - } - - - /* The completion command must do the following: - - if a file is provided: - - if force is not enabled: - - check if the 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 - - 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 - */ - 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 CompletionFishCommand(); \ No newline at end of file diff --git a/NodeApp/src/commander/completion/subcommands/CompletionGetCommand.ts b/NodeApp/src/commander/completion/subcommands/CompletionGetCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..f57aa7059a1b1459f7d6c0cc9b4d560ed5a0cd44 --- /dev/null +++ b/NodeApp/src/commander/completion/subcommands/CompletionGetCommand.ts @@ -0,0 +1,69 @@ +import CommanderCommand from '../../CommanderCommand'; +import { Command, CommandOptions, Option } from 'commander'; +import * as AutoCompletionHelper from '../../../helpers/AutoCompletionHelper'; + + +type CompletionProposal = { name: string, description: string }; + + +class CompletionGetCommand extends CommanderCommand { + protected commandName: string = 'get'; + protected options: CommandOptions = { + hidden: true + }; + + protected defineCommand() { + this.command.description('generate completion options for a given command') + .addOption(new Option('-s, --shell <shell>', 'shell completion result format').choices([ 'bash', 'zsh' ])) + .argument('<commands...>', 'command chain to complete') + .action(this.commandAction.bind(this)); + } + + private completion(commandsChain: Array<string>, displayFunction: (completionProposals: Array<CompletionProposal>) => void) { + const command = AutoCompletionHelper.getCommandFromChain(AutoCompletionHelper.getRoot(this.command), commandsChain.slice(1)); + + if ( command ) { + const commands = command.commands.filter(cmd => !(cmd as Command & { _hidden: boolean })._hidden); + const options = command.options.filter(option => !option.hidden); + + displayFunction([ ...commands.flatMap(cmd => [ { + name : cmd.name(), + description: cmd.description() + }, ...cmd.aliases().map(alias => ({ + name : alias, + description: cmd.description() + })) ]), ...options.flatMap(option => [ { + name : option.long, + description: option.description + }, { + name : option.short, + description: option.description + } ]) ].filter(proposal => proposal.name) as Array<CompletionProposal>); + } + } + + private bashCompletion(commandsChain: Array<string>) { + this.completion(commandsChain, (completionProposals: Array<CompletionProposal>) => console.log(completionProposals.map(proposal => proposal.name).join(' '))); + } + + private zshCompletion(commandsChain: Array<string>) { + this.completion(commandsChain, (completionProposals: Array<CompletionProposal>) => completionProposals.forEach(proposal => console.log(`${ proposal.name }:${ proposal.description }`))); + } + + protected async commandAction(commandsChain: Array<string>, options: { shell: 'bash' | 'zsh' }): Promise<void> { + switch ( options.shell ) { + case 'bash': + this.bashCompletion(commandsChain); + break; + case 'zsh': + this.zshCompletion(commandsChain); + break; + default: + console.error('Unsupported shell completion format'); + break; + } + } +} + + +export default new CompletionGetCommand(); \ No newline at end of file diff --git a/NodeApp/src/commander/completion/subcommands/CompletionScriptCommand.ts b/NodeApp/src/commander/completion/subcommands/CompletionScriptCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..45c5f3e52a167b0eb76c370e5010d75959f2e56e --- /dev/null +++ b/NodeApp/src/commander/completion/subcommands/CompletionScriptCommand.ts @@ -0,0 +1,75 @@ +import CommanderCommand from '../../CommanderCommand'; +import { Argument } from 'commander'; + + +class CompletionScriptCommand extends CommanderCommand { + protected commandName: string = 'script'; + + protected defineCommand() { + this.command.description('generate script completion') + .addArgument(new Argument('<shell>', 'shell completion format').choices([ 'bash', 'zsh' ])) + .action(this.commandAction.bind(this)); + } + + private bashCompletionScript() { + console.log(` +#/usr/bin/env bash +###-begin-dojo-completions-### +# +# dojo command completion script for bash +# +# Installation: dojo completion bash +# +function _dojo_completions() +{ + latest="\${COMP_WORDS[$COMP_CWORD]}" + words=$(dojo completion get --shell bash \${COMP_WORDS[@]}) + + COMPREPLY=($(compgen -W "$words" -- $latest)) + return 0 +} +complete -F _dojo_completions dojo +###-end-dojo-completions-### + `); + } + + private zshCompletionScript() { + console.log(` +#compdef dojo +###-begin-dojo-completions-### +# +# dojo command completion script for zsh +# +# Installation: dojo completion zsh +# +_dojo_completions() +{ + local reply + local si=$IFS + IFS=$' +' reply=($(dojo completion get --shell zsh \${words[@]})) + IFS=$si + _describe 'values' reply +} +compdef _dojo_completions dojo +###-end-dojo-completions-### + `); + } + + protected async commandAction(shell: 'bash' | 'zsh'): Promise<void> { + switch ( shell ) { + case 'bash': + this.bashCompletionScript(); + break; + case 'zsh': + this.zshCompletionScript(); + break; + default: + console.error('Unsupported shell completion format'); + break; + } + } +} + + +export default new CompletionScriptCommand(); \ No newline at end of file diff --git a/NodeApp/src/commander/completion/subcommands/CompletionZshCommand.ts b/NodeApp/src/commander/completion/subcommands/CompletionZshCommand.ts deleted file mode 100644 index f9d279eb2e86b29f567db78797f98333b70c4ec2..0000000000000000000000000000000000000000 --- a/NodeApp/src/commander/completion/subcommands/CompletionZshCommand.ts +++ /dev/null @@ -1,125 +0,0 @@ -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'; - - -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 = ` -# Added by DojoCLI -autoload -U +X compinit && compinit -autoload -U +X bashcompinit && bashcompinit -source ${ this.bashCompletion } -`; - - - protected defineCommand() { - GlobalHelper.completionCommandDefinition(this.command) - .description('generate zsh completion, which is derived from the 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 ) { - 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); - } - } -} - - -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 56988ebdceb9f25b1ba53fff4a6021059ca138c9..04464f6205fd62b86cfe19631fc1d3e988ea694d 100644 --- a/NodeApp/src/helpers/AutoCompletionHelper.ts +++ b/NodeApp/src/helpers/AutoCompletionHelper.ts @@ -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. Please restart your shell session.` : `${ shellType } already up to date.`); + } else { + try { + fs.writeFileSync(filePath, completionCommand); + spinner.succeed(`${ shellType } written. Please restart your shell session.`); + } 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 diff --git a/NodeApp/src/helpers/GlobalHelper.ts b/NodeApp/src/helpers/GlobalHelper.ts index 7f0991cbd6ce17a58c1a7845057a2800f99cd7d4..c7dccb10389fda604b31052c01e9fe1e8e54b0cf 100644 --- a/NodeApp/src/helpers/GlobalHelper.ts +++ b/NodeApp/src/helpers/GlobalHelper.ts @@ -5,18 +5,10 @@ import Config from '../config/Config'; class GlobalHelper { public runCommandDefinition(command: Command) { command - .option('-p, --path <value>', 'assignment path', Config.folders.defaultLocalExercise) - .option('-v, --verbose', 'verbose mode - display principal container output in live') - .addOption(new Option('-w, --super-verbose', 'verbose mode - display all docker compose logs (build included) in live').conflicts('verbose')) - .addOption(new Option('--verbose-ssj2').hideHelp().implies({ superVerbose: true })); - - return command; - } - - public completionCommandDefinition(command: Command) { - command - .option('-f, --file <filename>') - .option('-y, --force', 'don\'t ask for file overwrite confirmation'); + .option('-p, --path <value>', 'assignment path', Config.folders.defaultLocalExercise) + .option('-v, --verbose', 'verbose mode - display principal container output in live') + .addOption(new Option('-w, --super-verbose', 'verbose mode - display all docker compose logs (build included) in live').conflicts('verbose')) + .addOption(new Option('--verbose-ssj2').hideHelp().implies({ superVerbose: true })); return command; }