Skip to content
Snippets Groups Projects
Commit 960f83a4 authored by orestis.malaspin's avatar orestis.malaspin Committed by michael.minelli
Browse files

Resolve "Add zsh, fish, and bash shell completion helper function generation...

Resolve "Add zsh, fish, and bash shell completion helper function generation as well as the related command"
parent c9d77dbb
Branches
Tags
2 merge requests!10Resolve "Add sonar integration",!9Resolve "Add zsh, fish, and bash shell completion helper function generation as well as the related command"
This commit is part of merge request !10. Comments created here will be created in the context of that merge request.
Showing
with 694 additions and 33 deletions
...@@ -5,6 +5,9 @@ Wiki/.idea ...@@ -5,6 +5,9 @@ Wiki/.idea
NodeApp/src/config/Version.ts NodeApp/src/config/Version.ts
dojo_bash_completion.sh
dojo.fish
############################ MacOS ############################ MacOS
# General # General
.DS_Store .DS_Store
......
import { Command, Option } from 'commander'; import { Command, Option } from 'commander';
import AuthCommand from './auth/AuthCommand';
import ClientsSharedConfig from '../sharedByClients/config/ClientsSharedConfig'; import ClientsSharedConfig from '../sharedByClients/config/ClientsSharedConfig';
import AssignmentCommand from './assignment/AssignmentCommand'; import AssignmentCommand from './assignment/AssignmentCommand';
import ExerciseCommand from './exercise/ExerciseCommand'; import ExerciseCommand from './exercise/ExerciseCommand';
...@@ -9,6 +8,8 @@ import { stateConfigFile } from '../config/ConfigFiles'; ...@@ -9,6 +8,8 @@ import { stateConfigFile } from '../config/ConfigFiles';
import semver from 'semver/preload'; import semver from 'semver/preload';
import { version } from '../config/Version'; import { version } from '../config/Version';
import Config from '../config/Config'; import Config from '../config/Config';
import CompletionCommand from './completion/CompletionCommand';
import AuthCommand from './auth/AuthCommand';
import SessionCommand from './auth/SessionCommand'; import SessionCommand from './auth/SessionCommand';
...@@ -95,6 +96,7 @@ https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, { ...@@ -95,6 +96,7 @@ https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, {
SessionCommand.registerOnCommand(this.program); SessionCommand.registerOnCommand(this.program);
AssignmentCommand.registerOnCommand(this.program); AssignmentCommand.registerOnCommand(this.program);
ExerciseCommand.registerOnCommand(this.program); ExerciseCommand.registerOnCommand(this.program);
CompletionCommand.registerOnCommand(this.program);
} }
} }
......
import CommanderCommand from '../../CommanderCommand'; import CommanderCommand from '../../CommanderCommand';
import chalk from 'chalk';
import ora from 'ora'; import ora from 'ora';
import AccessesHelper from '../../../helpers/AccessesHelper'; import AccessesHelper from '../../../helpers/AccessesHelper';
import Assignment from '../../../sharedByClients/models/Assignment'; import Assignment from '../../../sharedByClients/models/Assignment';
...@@ -7,6 +6,7 @@ import GitlabUser from '../../../shared/types/Gitlab/GitlabUser'; ...@@ -7,6 +6,7 @@ import GitlabUser from '../../../shared/types/Gitlab/GitlabUser';
import GitlabManager from '../../../managers/GitlabManager'; import GitlabManager from '../../../managers/GitlabManager';
import DojoBackendManager from '../../../managers/DojoBackendManager'; import DojoBackendManager from '../../../managers/DojoBackendManager';
import Toolbox from '../../../shared/helpers/Toolbox'; import Toolbox from '../../../shared/helpers/Toolbox';
import TextStyle from '../../../types/TextStyle';
class AssignmentCreateCommand extends CommanderCommand { class AssignmentCreateCommand extends CommanderCommand {
...@@ -30,7 +30,7 @@ class AssignmentCreateCommand extends CommanderCommand { ...@@ -30,7 +30,7 @@ class AssignmentCreateCommand extends CommanderCommand {
// Check access and retrieve data // Check access and retrieve data
{ {
console.log(chalk.cyan('Please wait while we verify and retrieve data...')); console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...'));
if ( !await AccessesHelper.checkTeachingStaff() ) { if ( !await AccessesHelper.checkTeachingStaff() ) {
return; return;
...@@ -63,7 +63,7 @@ class AssignmentCreateCommand extends CommanderCommand { ...@@ -63,7 +63,7 @@ class AssignmentCreateCommand extends CommanderCommand {
// Create the assignment // Create the assignment
{ {
console.log(chalk.cyan('Please wait while we are creating the assignment (approximately 10 seconds)...')); console.log(TextStyle.BLOCK('Please wait while we are creating the assignment (approximately 10 seconds)...'));
try { try {
assignment = await DojoBackendManager.createAssignment(options.name, members, templateIdOrNamespace); assignment = await DojoBackendManager.createAssignment(options.name, members, templateIdOrNamespace);
...@@ -75,10 +75,10 @@ class AssignmentCreateCommand extends CommanderCommand { ...@@ -75,10 +75,10 @@ class AssignmentCreateCommand extends CommanderCommand {
}).start().info(); }).start().info();
}; };
oraInfo(`${ chalk.magenta('Name:') } ${ assignment.name }`); oraInfo(`${ TextStyle.LIST_ITEM_NAME('Name:') } ${ assignment.name }`);
oraInfo(`${ chalk.magenta('Web URL:') } ${ assignment.gitlabCreationInfo.web_url }`); oraInfo(`${ TextStyle.LIST_ITEM_NAME('Web URL:') } ${ assignment.gitlabCreationInfo.web_url }`);
oraInfo(`${ chalk.magenta('HTTP Repo:') } ${ assignment.gitlabCreationInfo.http_url_to_repo }`); oraInfo(`${ TextStyle.LIST_ITEM_NAME('HTTP Repo:') } ${ assignment.gitlabCreationInfo.http_url_to_repo }`);
oraInfo(`${ chalk.magenta('SSH Repo:') } ${ assignment.gitlabCreationInfo.ssh_url_to_repo }`); oraInfo(`${ TextStyle.LIST_ITEM_NAME('SSH Repo:') } ${ assignment.gitlabCreationInfo.ssh_url_to_repo }`);
} catch ( error ) { } catch ( error ) {
return; return;
} }
...@@ -87,7 +87,7 @@ class AssignmentCreateCommand extends CommanderCommand { ...@@ -87,7 +87,7 @@ class AssignmentCreateCommand extends CommanderCommand {
// Clone the repository // Clone the repository
{ {
if ( options.clone ) { if ( options.clone ) {
console.log(chalk.cyan('Please wait while we are cloning the repository...')); console.log(TextStyle.BLOCK('Please wait while we are cloning the repository...'));
await GitlabManager.cloneRepository(options.clone, assignment.gitlabCreationInfo.ssh_url_to_repo, undefined, true, 0); await GitlabManager.cloneRepository(options.clone, assignment.gitlabCreationInfo.ssh_url_to_repo, undefined, true, 0);
} }
......
import CommanderCommand from '../../CommanderCommand'; import CommanderCommand from '../../CommanderCommand';
import inquirer from 'inquirer'; import inquirer from 'inquirer';
import chalk from 'chalk';
import SessionManager from '../../../managers/SessionManager'; import SessionManager from '../../../managers/SessionManager';
import ora from 'ora'; import ora from 'ora';
import DojoBackendManager from '../../../managers/DojoBackendManager'; import DojoBackendManager from '../../../managers/DojoBackendManager';
import Assignment from '../../../sharedByClients/models/Assignment'; import Assignment from '../../../sharedByClients/models/Assignment';
import SharedAssignmentHelper from '../../../shared/helpers/Dojo/SharedAssignmentHelper'; import SharedAssignmentHelper from '../../../shared/helpers/Dojo/SharedAssignmentHelper';
import TextStyle from '../../../types/TextStyle';
abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand { abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand {
...@@ -35,7 +35,7 @@ abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand { ...@@ -35,7 +35,7 @@ abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand {
let assignment!: Assignment | undefined; let assignment!: Assignment | undefined;
{ {
console.log(chalk.cyan('Please wait while we verify and retrieve data...')); console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...'));
if ( !await SessionManager.testSession(true, null) ) { if ( !await SessionManager.testSession(true, null) ) {
return; return;
...@@ -82,7 +82,7 @@ abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand { ...@@ -82,7 +82,7 @@ abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand {
} }
{ {
console.log(chalk.cyan(`Please wait while we ${ this.publish ? 'publish' : 'unpublish' } the assignment...`)); console.log(TextStyle.BLOCK(`Please wait while we ${ this.publish ? 'publish' : 'unpublish' } the assignment...`));
try { try {
await DojoBackendManager.changeAssignmentPublishedStatus(assignment, this.publish); await DojoBackendManager.changeAssignmentPublishedStatus(assignment, this.publish);
......
import CommanderCommand from '../../../../CommanderCommand'; import CommanderCommand from '../../../../CommanderCommand';
import chalk from 'chalk';
import ora from 'ora'; import ora from 'ora';
import DojoBackendManager from '../../../../../managers/DojoBackendManager'; import DojoBackendManager from '../../../../../managers/DojoBackendManager';
import SessionManager from '../../../../../managers/SessionManager'; import SessionManager from '../../../../../managers/SessionManager';
import Assignment from '../../../../../sharedByClients/models/Assignment'; import Assignment from '../../../../../sharedByClients/models/Assignment';
import TextStyle from '../../../../../types/TextStyle';
abstract class AssignmentCorrectionLinkUpdateCommand extends CommanderCommand { abstract class AssignmentCorrectionLinkUpdateCommand extends CommanderCommand {
...@@ -22,7 +22,7 @@ abstract class AssignmentCorrectionLinkUpdateCommand extends CommanderCommand { ...@@ -22,7 +22,7 @@ abstract class AssignmentCorrectionLinkUpdateCommand extends CommanderCommand {
// Check access // Check access
{ {
console.log(chalk.cyan('Please wait while we check access...')); console.log(TextStyle.BLOCK('Please wait while we check access...'));
const assignmentGetSpinner: ora.Ora = ora('Checking if assignment exists').start(); const assignmentGetSpinner: ora.Ora = ora('Checking if assignment exists').start();
assignment = await DojoBackendManager.getAssignment(options.assignment); assignment = await DojoBackendManager.getAssignment(options.assignment);
...@@ -51,7 +51,7 @@ abstract class AssignmentCorrectionLinkUpdateCommand extends CommanderCommand { ...@@ -51,7 +51,7 @@ abstract class AssignmentCorrectionLinkUpdateCommand extends CommanderCommand {
// Link the exercise // Link the exercise
{ {
console.log(chalk.cyan('Please wait while we link the exercise...')); console.log(TextStyle.BLOCK('Please wait while we link the exercise...'));
await DojoBackendManager.linkUpdateCorrection(exerciseIdOrUrl, assignment, this.isUpdate); await DojoBackendManager.linkUpdateCorrection(exerciseIdOrUrl, assignment, this.isUpdate);
} }
......
import chalk from 'chalk';
import CommanderCommand from '../../CommanderCommand'; import CommanderCommand from '../../CommanderCommand';
import SessionManager from '../../../managers/SessionManager'; import SessionManager from '../../../managers/SessionManager';
import TextStyle from '../../../types/TextStyle';
class AuthLoginCommand extends CommanderCommand { class AuthLoginCommand extends CommanderCommand {
...@@ -15,7 +15,7 @@ class AuthLoginCommand extends CommanderCommand { ...@@ -15,7 +15,7 @@ class AuthLoginCommand extends CommanderCommand {
protected async commandAction(options: { cli: boolean }): Promise<void> { protected async commandAction(options: { cli: boolean }): Promise<void> {
try { try {
console.log(chalk.cyan('Please wait while we login you into Dojo...')); console.log(TextStyle.BLOCK('Please wait while we login you into Dojo...'));
await SessionManager.login(options.cli); await SessionManager.login(options.cli);
} catch ( error ) { /* empty */ } } catch ( error ) { /* empty */ }
} }
......
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
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';
class CompletionBashCommand extends CommanderCommand {
protected commandName: string = 'bash';
private installPath = path.join(os.homedir(), '.bash_completion');
protected defineCommand() {
this.command
.description('generate bash completion')
.option('-f, --file <filename>')
.option('-y, --force', 'don\'t ask for file overwrite confirmation')
.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 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 CompletionBashCommand();
\ No newline at end of file
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';
class CompletionFishCommand extends CommanderCommand {
protected commandName: string = 'fish';
private installPath = path.join(os.homedir(), '.config/fish/completions/dojo.fish');
protected defineCommand() {
this.command
.description('generate fish completion')
.option('-f, --file <filename>', `filename where the bash completion will be stored`)
.option('-y, --force', 'don\'t ask for file overwrite confirmation')
.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 ) {
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(` cp -i ${ filename } ~/.config/fish/completions # interactive cp to avoid accidents `) }`);
}
} 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
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';
class CompletionZshCommand extends CommanderCommand {
protected commandName: string = 'zsh';
private zprofile: string = path.join(homedir(), '.zprofile');
private bash_completion = path.join(homedir(), '.bash_completion');
private load_bash_completion = `
# Added by DojoCLI
autoload -U +X compinit && compinit
autoload -U +X bashcompinit && bashcompinit
source ${ this.bash_completion }
`;
protected defineCommand() {
this.command
.description('generate zsh completion, which is derived from the bash completion')
.option('-f, --file <filename>', 'bash completion filename')
.option('-y, --force', 'don\'t ask for file overwrite confirmation')
.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(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: ${ TextStyle.CODE(`
autoload -U +X compinit && compinit
autoload -U +X bashcompinit && bashcompinit
source ${ filename }
`) } `);
}
} catch ( error ) {
spinner.fail(`Bash completion writing error: ${ error }`);
}
}
protected addToZprofile(path: string, bash_path: string) {
const spinner: ora.Ora = ora(`Modifying ${ path } ...`).start();
if ( fs.existsSync(path) ) {
const data = fs.readFileSync(path);
let updated = false;
if ( !data.includes('autoload -U +X compinit && compinit') ) {
try {
fs.appendFileSync(path, '\nautoload -U +X compinit && compinit');
updated = true;
} catch {
spinner.fail(`Error appending in ${ this.zprofile }`);
}
}
if ( !data.includes('autoload -U +X bashcompinit && bashcompinit') ) {
try {
fs.appendFileSync(path, '\nautoload -U +X bashcompinit && bashcompinit');
updated = true;
} catch {
spinner.fail(`Error appending in ${ this.zprofile }`);
}
}
if ( !data.includes(`source ${ bash_path }`) ) {
try {
fs.appendFileSync(path, `\nsource ${ bash_path }`);
updated = true;
} catch {
spinner.fail(`Error appending in ${ this.zprofile }`);
}
}
if ( updated ) {
spinner.succeed(`Zsh profile updated.`);
} else {
spinner.succeed(`Zsh profile already up to date.`);
}
} else {
try {
fs.writeFileSync(path, this.load_bash_completion);
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.bash_completion); // 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
...@@ -6,6 +6,7 @@ import Assignment from '../../../sharedByClients/models/Assignment'; ...@@ -6,6 +6,7 @@ import Assignment from '../../../sharedByClients/models/Assignment';
import inquirer from 'inquirer'; import inquirer from 'inquirer';
import open from 'open'; import open from 'open';
import chalk from 'chalk'; import chalk from 'chalk';
import TextStyle from '../../../types/TextStyle';
type CorrectionResume = { name: string, value: string } type CorrectionResume = { name: string, value: string }
...@@ -66,7 +67,7 @@ class ExerciseCorrectionCommand extends CommanderCommand { ...@@ -66,7 +67,7 @@ class ExerciseCorrectionCommand extends CommanderCommand {
default: false default: false
})).correctionUrl; })).correctionUrl;
console.log(chalk.green(correctionUrl)); console.log(TextStyle.URL(correctionUrl));
open(correctionUrl).then(); open(correctionUrl).then();
} }
......
import CommanderCommand from '../../CommanderCommand'; import CommanderCommand from '../../CommanderCommand';
import chalk from 'chalk';
import GitlabManager from '../../../managers/GitlabManager'; import GitlabManager from '../../../managers/GitlabManager';
import GitlabUser from '../../../shared/types/Gitlab/GitlabUser'; import GitlabUser from '../../../shared/types/Gitlab/GitlabUser';
import ora from 'ora'; import ora from 'ora';
...@@ -7,6 +6,7 @@ import DojoBackendManager from '../../../managers/DojoBackendManager'; ...@@ -7,6 +6,7 @@ import DojoBackendManager from '../../../managers/DojoBackendManager';
import AccessesHelper from '../../../helpers/AccessesHelper'; import AccessesHelper from '../../../helpers/AccessesHelper';
import Assignment from '../../../sharedByClients/models/Assignment'; import Assignment from '../../../sharedByClients/models/Assignment';
import Exercise from '../../../sharedByClients/models/Exercise'; import Exercise from '../../../sharedByClients/models/Exercise';
import TextStyle from '../../../types/TextStyle';
class ExerciseCreateCommand extends CommanderCommand { class ExerciseCreateCommand extends CommanderCommand {
...@@ -29,7 +29,7 @@ class ExerciseCreateCommand extends CommanderCommand { ...@@ -29,7 +29,7 @@ class ExerciseCreateCommand extends CommanderCommand {
// Check access and retrieve data // Check access and retrieve data
{ {
console.log(chalk.cyan('Please wait while we verify and retrieve data...')); console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...'));
if ( !await AccessesHelper.checkStudent() ) { if ( !await AccessesHelper.checkStudent() ) {
return; return;
...@@ -65,7 +65,7 @@ class ExerciseCreateCommand extends CommanderCommand { ...@@ -65,7 +65,7 @@ class ExerciseCreateCommand extends CommanderCommand {
//Create the exercise //Create the exercise
{ {
console.log(chalk.cyan('Please wait while we are creating the exercise (approximately 10 seconds)...')); console.log(TextStyle.BLOCK('Please wait while we are creating the exercise (approximately 10 seconds)...'));
try { try {
exercise = await DojoBackendManager.createExercise((assignment as Assignment).name, members); exercise = await DojoBackendManager.createExercise((assignment as Assignment).name, members);
...@@ -77,11 +77,11 @@ class ExerciseCreateCommand extends CommanderCommand { ...@@ -77,11 +77,11 @@ class ExerciseCreateCommand extends CommanderCommand {
}).start().info(); }).start().info();
}; };
oraInfo(`${ chalk.magenta('Id:') } ${ exercise.id }`); oraInfo(`${ TextStyle.LIST_ITEM_NAME('Id:') } ${ exercise.id }`);
oraInfo(`${ chalk.magenta('Name:') } ${ exercise.name }`); oraInfo(`${ TextStyle.LIST_ITEM_NAME('Name:') } ${ exercise.name }`);
oraInfo(`${ chalk.magenta('Web URL:') } ${ exercise.gitlabCreationInfo.web_url }`); oraInfo(`${ TextStyle.LIST_ITEM_NAME('Web URL:') } ${ exercise.gitlabCreationInfo.web_url }`);
oraInfo(`${ chalk.magenta('HTTP Repo:') } ${ exercise.gitlabCreationInfo.http_url_to_repo }`); oraInfo(`${ TextStyle.LIST_ITEM_NAME('HTTP Repo:') } ${ exercise.gitlabCreationInfo.http_url_to_repo }`);
oraInfo(`${ chalk.magenta('SSH Repo:') } ${ exercise.gitlabCreationInfo.ssh_url_to_repo }`); oraInfo(`${ TextStyle.LIST_ITEM_NAME('SSH Repo:') } ${ exercise.gitlabCreationInfo.ssh_url_to_repo }`);
} catch ( error ) { } catch ( error ) {
return; return;
} }
...@@ -90,7 +90,7 @@ class ExerciseCreateCommand extends CommanderCommand { ...@@ -90,7 +90,7 @@ class ExerciseCreateCommand extends CommanderCommand {
// Clone the repository // Clone the repository
{ {
if ( options.clone ) { if ( options.clone ) {
console.log(chalk.cyan('Please wait while we are cloning the repository...')); console.log(TextStyle.BLOCK('Please wait while we are cloning the repository...'));
await GitlabManager.cloneRepository(options.clone, exercise.gitlabCreationInfo.ssh_url_to_repo, `DojoExercise - ${ exercise.assignmentName }`, true, 0); await GitlabManager.cloneRepository(options.clone, exercise.gitlabCreationInfo.ssh_url_to_repo, `DojoExercise - ${ exercise.assignmentName }`, true, 0);
} }
......
import { Command } from 'commander';
import { existsSync, renameSync } from 'fs';
import ora from 'ora';
import TextStyle from '../types/TextStyle';
import inquirer from 'inquirer';
function renameFile(filename: string, showWarning: boolean) {
const old_filename = `${filename}.old`
const spinner: ora.Ora = ora(`Renaming ${TextStyle.CODE(filename)} in ${TextStyle.CODE(old_filename)} ...`).start();
try {
renameSync(filename, old_filename);
spinner.succeed(`Renaming success: ${TextStyle.CODE(filename)} in ${TextStyle.CODE(old_filename)}`);
if (showWarning) {
console.log(`${TextStyle.WARNING('Warning:')} Your ${TextStyle.CODE(filename)} was renamed ${TextStyle.CODE(old_filename)}. If this was not intended please revert this change.`);
}
} catch (error) {
spinner.fail(`Renaming failed: ${error}.`);
}
}
async function askConfirmation(msg: string): Promise<boolean> {
return (await inquirer.prompt({
name: 'confirm',
message: msg,
type: 'confirm',
default: false
})).confirm
}
// Returns false, when the renaming is interrupted
export async function tryRenameFile(path: string, force: boolean): Promise<boolean> {
const fileExists = existsSync(path)
if (fileExists && force) {
renameFile(path, false)
} else if (fileExists) {
const confirm = (await askConfirmation(`${TextStyle.CODE(path)} in ${TextStyle.CODE(path + '.old')}. Are you sure?`))
if (confirm) {
renameFile(path, true)
} else {
console.log(`${TextStyle.BLOCK('Completion generation interrupted.')}`)
return false
}
}
return true
}
const fishFunction = `
function __fish_dojo_using_commands
set cmd (commandline -opc)
set num_cmd (count $cmd)
if [ $num_cmd -eq $argv[1] ]
for i in (seq 1 (math $num_cmd))
if [ $argv[(math $i+1)] != $cmd[$i] ]
return 1
end
end
return 0
end
return 1
end
complete -f -c dojo
`;
function isHidden(cmd: Command): boolean {
return (cmd as Command & { _hidden: boolean })._hidden
}
function isLeaf(cmd: Command): boolean {
return cmd.commands.length == 0;
}
function flatten(cmd: Command): Array<Command> {
if (isLeaf(cmd)) {
return [cmd];
} else {
return cmd.commands
.filter(c => !isHidden(c))
.map(child => flatten(child))
.reduce((acc, cmd) => acc.concat(cmd), [cmd]);
}
}
// Computes the maximum number of commands until a leaf is reached
function computeDepth(cmd: Command | undefined): number {
if (cmd === undefined) {
return 0;
} else {
return 1 + cmd.commands.filter(c => !isHidden(c)).map(subCmd => computeDepth(subCmd)).reduce((acc, depth) => depth > acc ? depth : acc, 0);
}
}
// Computes the maximum number of commands until the root is reached
function computeHeight(cmd: Command | null): number {
let height = 0;
let tmp = cmd;
while (tmp !== null) {
tmp = tmp.parent;
height += 1;
}
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 => opt.flags.replace(/<.*?>/, '').replace(/\[.*?\]/, '').replace(',', '').trimEnd()).join(' ');
}
function commandsAndOptionsToString(cmd: Command): string {
return cmd.commands.filter(c => !isHidden(c)).map(c => c.name()).join(' ').concat(' ' + getOptions(cmd)).trim().concat(' --help -h').trim();
}
function addLine(identLevel: number, pattern: string): string {
return `${' '.repeat(identLevel)}${pattern}\n`;
}
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');
} else {
let data = addLine(ident, `case "\${COMP_WORDS[$COMP_CWORD - ${maxDepth - current + 1}]}" in`) + addLine(ident + 1, `${cmd.name()})`);
cmd.commands.filter(c => !isHidden(c)).forEach(subCmd => {
data += generateBashSubCommands(subCmd, current + 1, maxDepth, ident + 2);
});
data += addLine(ident + 1, ';;') + addLine(ident + 1, '*)') + addLine(ident + 1, ';;') + addLine(ident, 'esac');
return data;
}
}
export function generateBashCompletion(root: Command): string {
const depth = computeDepth(root);
let data = addLine(0, '#/usr/bin/env bash\nfunction _dojo_completions()') + addLine(0, '{') + addLine(1, 'latest="${COMP_WORDS[$COMP_CWORD]}"');
for (let i = 1; i <= depth; i++) {
data += addLine(1, `${i == 1 ? 'if' : 'elif'} [ $COMP_CWORD -eq ${depth - i + 1} ]`) + addLine(1, 'then');
data += generateBashSubCommands(root, i, depth, 2);
}
data += addLine(1, 'fi') + addLine(1, 'COMPREPLY=($(compgen -W "$words" -- $latest))') + addLine(1, 'return 0') + addLine(0, '}') + addLine(0, 'complete -F _dojo_completions dojo');
return data;
}
const prefix = 'complete -f -c dojo -n \'__fish_dojo_using_commands';
function generateCommandChain(cmd: Command | null): string {
let data = '';
while (cmd !== null) {
data = cmd.name().concat(` ${data}`);
cmd = cmd.parent;
}
return data.trimEnd();
}
function hasOptions(cmd: Command): boolean {
return cmd.options.length > 0;
}
function optionsToString(cmd: Command): string {
return cmd.options.filter(opt => !opt.hidden).map(opt => {
return `${prefix} ${computeHeight(cmd)} ${generateCommandChain(cmd)}' -a "${opt.short ?? ''} ${opt.long ?? ''}" -d "${opt.description}"`;
}).join('\n').concat('\n');
}
export function generateFishCompletion(root: Command): string {
const commands = flatten(root);
const data = fishFunction.concat(// add completions for options
commands.filter(c => !isHidden(c)).filter(cmd => hasOptions(cmd)).map(cmd => optionsToString(cmd)).filter(str => str != '').join('')).concat(// add completions for commands and subcommands
commands.filter(c => !isHidden(c)).filter(cmd => !isLeaf(cmd)).map(cmd => cmd.commands.filter(c => !isHidden(c)).map(subCmd => {
return `${prefix} ${computeHeight(cmd)} ${generateCommandChain(cmd)}' -a ${subCmd.name()} -d "${subCmd.description()}"`;
}).join('\n').concat('\n')).join(''));
return data;
}
// 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
// this comment is written).
// #/usr/bin/env bash
// function _dojo_completions()
// {
// latest="${COMP_WORDS[$COMP_CWORD]}"
// if [ $COMP_CWORD -eq 3 ]
// then
// case "${COMP_WORDS[$COMP_CWORD - 3]}" in
// dojo)
// case "${COMP_WORDS[$COMP_CWORD - 2]}" in
// session)
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// login)
// words="-c --cli --help -h"
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// logout)
// words="-f --force --help -h"
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// test)
// words="--help -h"
// ;;
// *)
// ;;
// esac
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 2]}" in
// assignment)
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// create)
// words="-n --name -i --members_id -u --members_username -t --template -c --clone --help -h"
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// check)
// words="-p --path -v --verbose -w --super-verbose --help -h"
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// run)
// words="-p --path -v --verbose -w --super-verbose --help -h"
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// publish)
// words="-f --force --help -h"
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// unpublish)
// words="-f --force --help -h"
// ;;
// *)
// ;;
// esac
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 2]}" in
// exercise)
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// create)
// words="-a --assignment -i --members_id -u --members_username -c --clone --help -h"
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// run)
// words="-p --path -v --verbose -w --super-verbose --help -h"
// ;;
// *)
// ;;
// esac
// ;;
// *)
// ;;
// esac
// ;;
// *)
// ;;
// esac
// elif [ $COMP_CWORD -eq 2 ]
// then
// case "${COMP_WORDS[$COMP_CWORD - 2]}" in
// dojo)
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// session)
// words="login logout test --help -h"
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// assignment)
// words="create check run publish unpublish --help -h"
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// exercise)
// words="create run --help -h"
// ;;
// *)
// ;;
// esac
// ;;
// *)
// ;;
// esac
// elif [ $COMP_CWORD -eq 1 ]
// then
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// dojo)
// words="session assignment exercise -V --version -H --host --help -h"
// ;;
// *)
// ;;
// esac
// fi
// COMPREPLY=($(compgen -W "$words" -- $latest))
// return 0
// }
// complete -F _dojo_completions dojo
...@@ -14,6 +14,7 @@ import os from 'os'; ...@@ -14,6 +14,7 @@ import os from 'os';
import util from 'util'; import util from 'util';
import { exec } from 'child_process'; import { exec } from 'child_process';
import SharedConfig from '../../shared/config/SharedConfig'; import SharedConfig from '../../shared/config/SharedConfig';
import TextStyle from '../../types/TextStyle';
const execAsync = util.promisify(exec); const execAsync = util.promisify(exec);
...@@ -50,7 +51,7 @@ class ExerciseRunHelper { ...@@ -50,7 +51,7 @@ class ExerciseRunHelper {
// Step 1: Check requirements (if it's an exercise folder and if Docker daemon is running) // Step 1: Check requirements (if it's an exercise folder and if Docker daemon is running)
{ {
console.log(chalk.cyan('Please wait while we are checking and creating dependencies...')); console.log(TextStyle.BLOCK('Please wait while we are checking and creating dependencies...'));
// Create result temp folder // Create result temp folder
fs.mkdirSync(this.folderResultsVolume, { recursive: true }); fs.mkdirSync(this.folderResultsVolume, { recursive: true });
...@@ -122,7 +123,7 @@ class ExerciseRunHelper { ...@@ -122,7 +123,7 @@ class ExerciseRunHelper {
// Step 2: Run docker-compose file // Step 2: Run docker-compose file
{ {
console.log(chalk.cyan('Please wait while we are running the exercise...')); console.log(TextStyle.BLOCK('Please wait while we are running the exercise...'));
let composeFileOverride: string[] = []; let composeFileOverride: string[] = [];
const composeOverridePath: string = path.join(localExercisePath, 'docker-compose-override.yml'); const composeOverridePath: string = path.join(localExercisePath, 'docker-compose-override.yml');
...@@ -211,7 +212,7 @@ class ExerciseRunHelper { ...@@ -211,7 +212,7 @@ class ExerciseRunHelper {
// Step 3: Get results // Step 3: Get results
{ {
console.log(chalk.cyan('Please wait while we are checking the results...')); console.log(TextStyle.BLOCK('Please wait while we are checking the results...'));
exerciseResultsValidation = new ExerciseResultsSanitizerAndValidator(this.folderResultsDojo, this.folderResultsExercise, exerciseDockerCompose.exitCode); exerciseResultsValidation = new ExerciseResultsSanitizerAndValidator(this.folderResultsDojo, this.folderResultsExercise, exerciseDockerCompose.exitCode);
......
...@@ -20,6 +20,7 @@ import GitlabManager from './GitlabManager'; ...@@ -20,6 +20,7 @@ import GitlabManager from './GitlabManager';
import GitlabToken from '../shared/types/Gitlab/GitlabToken'; import GitlabToken from '../shared/types/Gitlab/GitlabToken';
import open from 'open'; import open from 'open';
import { sessionConfigFile } from '../config/ConfigFiles'; import { sessionConfigFile } from '../config/ConfigFiles';
import TextStyle from '../types/TextStyle';
class LoginServer { class LoginServer {
...@@ -124,9 +125,9 @@ class SessionManager { ...@@ -124,9 +125,9 @@ class SessionManager {
private async getGitlabCodeFromHeadlessEnvironment(): Promise<string> { private async getGitlabCodeFromHeadlessEnvironment(): Promise<string> {
const indent: string = ' '; const indent: string = ' ';
console.log(`${ indent }Please open the following URL in your web browser and accept to give the requested permissions to Dojo:`); console.log(`${ indent }Please open the following URL in your web browser and accept to give the requested permissions to Dojo:`);
console.log(chalk.blue(`${ indent }${ Config.login.gitlab.url.code }`)); console.log(TextStyle.URL(`${ indent }${ Config.login.gitlab.url.code }`));
console.log(`${ indent }Then, copy the code at the end of the redirected url and paste it bellow.`); console.log(`${ indent }Then, copy the code at the end of the redirected url and paste it bellow.`);
console.log(`${ indent }Example of url (here the code is 123456): ${ chalk.blue(`${ SharedConfig.login.gitlab.url.redirect }?code=`) }${ chalk.green('123456') }`); console.log(`${ indent }Example of url (here the code is 123456): ${ TextStyle.URL(`${ SharedConfig.login.gitlab.url.redirect }?code=`) }${ chalk.green('123456') }`);
return (await inquirer.prompt({ return (await inquirer.prompt({
type : 'password', type : 'password',
name : 'code', name : 'code',
......
import chalk from 'chalk';
class TextStyle {
public readonly BLOCK = chalk.cyan;
public readonly CODE = chalk.bgHex('F7F7F7').grey;
public readonly LIST_ITEM_NAME = chalk.magenta;
public readonly URL = chalk.blue.underline;
public readonly WARNING = chalk.red;
}
export default new TextStyle();
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment