From 5e601ba185ea2cd3397bea8f7362b33e70c35c47 Mon Sep 17 00:00:00 2001
From: Orestis <orestis.malaspinas@pm.me>
Date: Mon, 19 Feb 2024 22:06:28 +0100
Subject: [PATCH] added all 3 completion commands

---
 NodeApp/src/commander/CommanderApp.ts         |  6 +-
 .../commander/completion/CompletionCommand.ts | 25 +++++++
 .../subcommands/CompletionBashCommand.ts      | 61 ++++++++++++++++
 .../subcommands/CompletionFishCommand.ts      | 56 +++++++++++++++
 .../subcommands/CompletionZshCommand.ts       | 69 +++++++++++++++++++
 NodeApp/src/helpers/AutoCompletionHelper.ts   | 17 +++--
 6 files changed, 227 insertions(+), 7 deletions(-)
 create mode 100644 NodeApp/src/commander/completion/CompletionCommand.ts
 create mode 100644 NodeApp/src/commander/completion/subcommands/CompletionBashCommand.ts
 create mode 100644 NodeApp/src/commander/completion/subcommands/CompletionFishCommand.ts
 create mode 100644 NodeApp/src/commander/completion/subcommands/CompletionZshCommand.ts

diff --git a/NodeApp/src/commander/CommanderApp.ts b/NodeApp/src/commander/CommanderApp.ts
index e70ce93..b729c95 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 0000000..11df30f
--- /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 0000000..5dfde48
--- /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 0000000..b6ad117
--- /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 0000000..d162dc9
--- /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 2e1f42e..3af7148 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
 }
 
 
-- 
GitLab