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

Sonar => Resolve issues

parent d50b7418
Branches
Tags
1 merge request!10Resolve "Add sonar integration"
Pipeline #30053 failed
Showing
with 231 additions and 218 deletions
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
"commander": "^11.1.0", "commander": "^11.1.0",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"dotenv-expand": "^10.0.0", "dotenv-expand": "^10.0.0",
"form-data": "^4.0.0",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"http-status-codes": "^2.3.0", "http-status-codes": "^2.3.0",
"inquirer": "^8.2.6", "inquirer": "^8.2.6",
......
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
"commander" : "^11.1.0", "commander" : "^11.1.0",
"dotenv" : "^16.3.1", "dotenv" : "^16.3.1",
"dotenv-expand" : "^10.0.0", "dotenv-expand" : "^10.0.0",
"form-data" : "^4.0.0",
"fs-extra" : "^11.2.0", "fs-extra" : "^11.2.0",
"http-status-codes" : "^2.3.0", "http-status-codes" : "^2.3.0",
"inquirer" : "^8.2.6", "inquirer" : "^8.2.6",
...@@ -51,6 +52,7 @@ ...@@ -51,6 +52,7 @@
"semver" : "^7.5.4", "semver" : "^7.5.4",
"tar-stream" : "^3.1.6", "tar-stream" : "^3.1.6",
"winston" : "^3.11.0", "winston" : "^3.11.0",
"winston-transport" : "^4.7.0",
"yaml" : "^2.3.4", "yaml" : "^2.3.4",
"zod" : "^3.22.4", "zod" : "^3.22.4",
"zod-validation-error": "^3.0.0" "zod-validation-error": "^3.0.0"
......
...@@ -20,4 +20,4 @@ import HttpManager from './managers/HttpManager'; ...@@ -20,4 +20,4 @@ import HttpManager from './managers/HttpManager';
HttpManager.registerAxiosInterceptor(); HttpManager.registerAxiosInterceptor();
new CommanderApp(); (new CommanderApp()).parse();
\ No newline at end of file \ No newline at end of file
...@@ -53,6 +53,10 @@ class CommanderApp { ...@@ -53,6 +53,10 @@ class CommanderApp {
this.program.parse(); this.program.parse();
} }
public parse() {
this.program.parse();
}
private warnDevelopmentVersion() { private warnDevelopmentVersion() {
if ( !SharedConfig.production ) { if ( !SharedConfig.production ) {
console.log(boxen(`This is a development (unstable) version of the DojoCLI. console.log(boxen(`This is a development (unstable) version of the DojoCLI.
...@@ -73,8 +77,7 @@ https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, { ...@@ -73,8 +77,7 @@ https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, {
if ( SharedConfig.production ) { if ( SharedConfig.production ) {
const latestDojoCliVersion = stateConfigFile.getParam('latestDojoCliVersion') as string | null || '0.0.0'; const latestDojoCliVersion = stateConfigFile.getParam('latestDojoCliVersion') as string | null || '0.0.0';
const latestDojoCliVersionNotification = stateConfigFile.getParam('latestDojoCliVersionNotification') as number | null || 0; const latestDojoCliVersionNotification = stateConfigFile.getParam('latestDojoCliVersionNotification') as number | null || 0;
if ( semver.lt(version, latestDojoCliVersion) ) { if ( semver.lt(version, latestDojoCliVersion) && (new Date()).getTime() - latestDojoCliVersionNotification >= Config.versionUpdateInformationPeriodHours * 60 * 60 * 1000 ) {
if ( (new Date()).getTime() - latestDojoCliVersionNotification >= Config.versionUpdateInformationPeriodHours * 60 * 60 * 1000 ) {
console.log(boxen(`The ${ latestDojoCliVersion } version of the DojoCLI is available: console.log(boxen(`The ${ latestDojoCliVersion } version of the DojoCLI is available:
https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, { https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, {
title : 'Information', title : 'Information',
...@@ -89,7 +92,6 @@ https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, { ...@@ -89,7 +92,6 @@ https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, {
} }
} }
} }
}
private registerCommands() { private registerCommands() {
new AuthCommand().registerOnCommand(this.program); new AuthCommand().registerOnCommand(this.program);
......
...@@ -18,7 +18,12 @@ abstract class CommanderCommand { ...@@ -18,7 +18,12 @@ abstract class CommanderCommand {
protected abstract defineCommand(): void; protected abstract defineCommand(): void;
protected defineSubCommands() {} protected defineSubCommands() {
/*
* No action
* Override this method only if you need to define subcommands
* */
}
protected abstract commandAction(...args: Array<unknown>): Promise<void>; protected abstract commandAction(...args: Array<unknown>): Promise<void>;
} }
......
...@@ -24,7 +24,9 @@ class AssignmentCommand extends CommanderCommand { ...@@ -24,7 +24,9 @@ class AssignmentCommand extends CommanderCommand {
AssignmentCorrectionCommand.registerOnCommand(this.command); AssignmentCorrectionCommand.registerOnCommand(this.command);
} }
protected async commandAction(): Promise<void> { } protected async commandAction(): Promise<void> {
// No action
}
} }
......
...@@ -10,6 +10,10 @@ import GlobalHelper from '../../../helpers/GlobalHelper'; ...@@ -10,6 +10,10 @@ import GlobalHelper from '../../../helpers/GlobalHelper';
class AssignmentCheckCommand extends CommanderCommand { class AssignmentCheckCommand extends CommanderCommand {
protected commandName: string = 'check'; protected commandName: string = 'check';
protected currentSpinner: ora.Ora = ora();
private verbose: boolean = false;
private superVerbose: boolean = false;
private buildPhase: boolean | undefined = undefined;
protected defineCommand() { protected defineCommand() {
GlobalHelper.runCommandDefinition(this.command) GlobalHelper.runCommandDefinition(this.command)
...@@ -17,78 +21,78 @@ class AssignmentCheckCommand extends CommanderCommand { ...@@ -17,78 +21,78 @@ class AssignmentCheckCommand extends CommanderCommand {
.action(this.commandAction.bind(this)); .action(this.commandAction.bind(this));
} }
protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> { private logsEvent(log: string, _error: boolean, displayable: boolean, _currentStep: string, currentSubStep: string) {
const verbose: boolean = options.verbose || options.superVerbose || SharedConfig.debug;
const localExercisePath: string = options.path ?? Config.folders.defaultLocalExercise;
const assignmentValidator = new AssignmentValidator(localExercisePath);
try {
await new Promise<void>((resolve, reject) => {
let spinner: ora.Ora;
if ( verbose ) {
let buildPhase: boolean | undefined = undefined;
assignmentValidator.events.on('logs', (log: string, error: boolean, displayable: boolean, _currentStep: string, currentSubStep: string) => {
for ( const line of log.split('\n') ) { for ( const line of log.split('\n') ) {
if ( displayable && buildPhase == undefined && line.startsWith('#') ) { if ( displayable && this.buildPhase === undefined && line.startsWith('#') ) {
buildPhase = true; this.buildPhase = true;
} }
if ( currentSubStep == 'COMPOSE_RUN' && buildPhase === true && line != '' && !line.startsWith('#') ) { if ( currentSubStep === 'COMPOSE_RUN' && this.buildPhase === true && line !== '' && !line.startsWith('#') ) {
buildPhase = false; this.buildPhase = false;
} }
if ( SharedConfig.debug || (displayable && (options.superVerbose || buildPhase === false)) ) { if ( SharedConfig.debug || (displayable && (this.superVerbose || this.buildPhase === false)) ) {
console.log(line); console.log(line);
} }
} }
});
} }
assignmentValidator.events.on('step', (name: string, message: string) => { private subStepEvent(name: string, message: string) {
console.log(chalk.cyan(message)); this.currentSpinner = ora({
});
assignmentValidator.events.on('subStep', (name: string, message: string) => {
spinner = ora({
text : message, text : message,
indent: 4 indent: 4
}).start(); }).start();
if ( verbose && name == 'COMPOSE_RUN' ) { if ( this.verbose && name === 'COMPOSE_RUN' ) {
spinner.info(); this.currentSpinner.info();
}
} }
});
assignmentValidator.events.on('endSubStep', (stepName: string, message: string, error: boolean) => { private endSubStepEvent(stepName: string, message: string, error: boolean) {
if ( error ) { if ( error ) {
if ( verbose && stepName == 'COMPOSE_RUN' ) { if ( this.verbose && stepName === 'COMPOSE_RUN' ) {
ora({ ora({
text : message, text : message,
indent: 4 indent: 4
}).start().fail(); }).start().fail();
} else { } else {
spinner.fail(message); this.currentSpinner.fail(message);
} }
} else { } else {
if ( verbose && stepName == 'COMPOSE_RUN' ) { if ( this.verbose && stepName === 'COMPOSE_RUN' ) {
ora({ ora({
text : message, text : message,
indent: 4 indent: 4
}).start().succeed(); }).start().succeed();
} else { } else {
spinner.succeed(message); this.currentSpinner.succeed(message);
}
} }
} }
});
assignmentValidator.events.on('finished', (success: boolean) => { protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> {
success ? resolve() : reject(); this.superVerbose = options.superVerbose;
}); this.verbose = options.verbose || options.superVerbose || SharedConfig.debug;
const localExercisePath: string = options.path ?? Config.folders.defaultLocalExercise;
const assignmentValidator = new AssignmentValidator(localExercisePath);
try {
await new Promise<void>((resolve, reject) => {
if ( this.verbose ) {
assignmentValidator.events.on('logs', this.logsEvent.bind(this));
}
assignmentValidator.events.on('step', (_name: string, message: string) => console.log(chalk.cyan(message)));
assignmentValidator.events.on('subStep', this.subStepEvent.bind(this));
assignmentValidator.events.on('endSubStep', this.endSubStepEvent.bind(this));
assignmentValidator.events.on('finished', (success: boolean) => success ? resolve() : reject());
assignmentValidator.run(true); assignmentValidator.run();
}); });
} catch ( error ) { /* empty */ } } catch ( error ) { /* empty */ }
......
...@@ -52,10 +52,10 @@ class AssignmentCreateCommand extends CommanderCommand { ...@@ -52,10 +52,10 @@ class AssignmentCreateCommand extends CommanderCommand {
templateIdOrNamespace = options.template; templateIdOrNamespace = options.template;
if ( Number.isNaN(Number(templateIdOrNamespace)) ) { if ( Number.isNaN(Number(templateIdOrNamespace)) ) {
templateIdOrNamespace = Toolbox.urlToPath(templateIdOrNamespace as string); templateIdOrNamespace = Toolbox.urlToPath(templateIdOrNamespace);
} }
if ( !await DojoBackendManager.checkTemplateAccess(encodeURIComponent(templateIdOrNamespace as string)) ) { if ( !await DojoBackendManager.checkTemplateAccess(encodeURIComponent(templateIdOrNamespace)) ) {
return; return;
} }
} }
......
...@@ -13,7 +13,7 @@ class AssignmentRunCommand extends CommanderCommand { ...@@ -13,7 +13,7 @@ class AssignmentRunCommand extends CommanderCommand {
} }
protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> { protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> {
await ExerciseRunHelper.run(options); await (new ExerciseRunHelper(options)).run();
} }
} }
......
...@@ -16,7 +16,9 @@ class AssignmentCorrectionCommand extends CommanderCommand { ...@@ -16,7 +16,9 @@ class AssignmentCorrectionCommand extends CommanderCommand {
AssignmentCorrectionUpdateCommand.registerOnCommand(this.command); AssignmentCorrectionUpdateCommand.registerOnCommand(this.command);
} }
protected async commandAction(): Promise<void> { } protected async commandAction(): Promise<void> {
// No action
}
} }
......
...@@ -18,7 +18,9 @@ class AuthCommand extends CommanderCommand { ...@@ -18,7 +18,9 @@ class AuthCommand extends CommanderCommand {
SessionTestCommand.registerOnCommand(this.command); SessionTestCommand.registerOnCommand(this.command);
} }
protected async commandAction(): Promise<void> { } protected async commandAction(): Promise<void> {
// No action
}
} }
......
...@@ -18,7 +18,9 @@ class CompletionCommand extends CommanderCommand { ...@@ -18,7 +18,9 @@ class CompletionCommand extends CommanderCommand {
CompletionZshCommand.registerOnCommand(this.command); CompletionZshCommand.registerOnCommand(this.command);
} }
protected async commandAction(): Promise<void> { } protected async commandAction(): Promise<void> {
// No action
}
} }
......
...@@ -11,7 +11,7 @@ import GlobalHelper from '../../../helpers ...@@ -11,7 +11,7 @@ import GlobalHelper from '../../../helpers
class CompletionFishCommand extends CommanderCommand { class CompletionFishCommand extends CommanderCommand {
protected commandName: string = 'fish'; protected commandName: string = 'fish';
private installPath = path.join(os.homedir(), '.config/fish/completions/dojo.fish'); private readonly installPath = path.join(os.homedir(), '.config/fish/completions/dojo.fish');
protected defineCommand() { protected defineCommand() {
GlobalHelper.completionCommandDefinition(this.command) GlobalHelper.completionCommandDefinition(this.command)
...@@ -29,10 +29,11 @@ class CompletionFishCommand extends CommanderCommand { ...@@ -29,10 +29,11 @@ class CompletionFishCommand extends CommanderCommand {
spinner.succeed(`Fish completion successfully written in ${ filename }.`); spinner.succeed(`Fish completion successfully written in ${ filename }.`);
if ( showInstructions ) { if ( showInstructions ) {
const cpCommand = ` cp -i ${ filename } ~/.config/fish/completions # interactive cp to avoid accidents `;
console.log(` console.log(`
The easiest way to install the completion is to copy the ${ TextStyle.CODE(filename) } into the ${ TextStyle.CODE('~/.config/fish/completions') } directory. 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 `) }`); ${ TextStyle.CODE(cpCommand) }`);
} }
} catch ( error ) { } catch ( error ) {
spinner.fail(`Fish completion error: ${ error }.`); spinner.fail(`Fish completion error: ${ error }.`);
......
...@@ -37,6 +37,11 @@ source ${ this.bashCompletion } ...@@ -37,6 +37,11 @@ source ${ this.bashCompletion }
spinner.succeed(`Bash completion successfully written in ${ TextStyle.CODE(filename) }`); spinner.succeed(`Bash completion successfully written in ${ TextStyle.CODE(filename) }`);
if ( showInstructions ) { if ( showInstructions ) {
const zprofileContent = TextStyle.CODE(`
autoload -U +X compinit && compinit
autoload -U +X bashcompinit && bashcompinit
source ${ filename }
`);
console.log(` 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. 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.
...@@ -45,54 +50,40 @@ cat ${ filename } > ~/.bash_completion # overwrites .bash_completion ...@@ -45,54 +50,40 @@ cat ${ filename } > ~/.bash_completion # overwrites .bash_completion
cat ${ filename } >> ~/.bash_completion # appends to .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') } 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(` Next add the following lines to your ${ TextStyle.CODE('~/.zprofile') } file: ${ zprofileContent } `);
autoload -U +X compinit && compinit
autoload -U +X bashcompinit && bashcompinit
source ${ filename }
`) } `);
} }
} catch ( error ) { } catch ( error ) {
spinner.fail(`Bash completion writing error: ${ error }`); spinner.fail(`Bash completion writing error: ${ error }`);
} }
} }
protected addToZprofile(path: string, bash_path: string) { protected addToZprofile(zprofilePath: string, bashPath: string) {
const spinner: ora.Ora = ora(`Modifying ${ path } ...`).start(); const spinner: ora.Ora = ora(`Modifying ${ zprofilePath } ...`).start();
if ( fs.existsSync(path) ) { if ( fs.existsSync(zprofilePath) ) {
const data = fs.readFileSync(path); const data = fs.readFileSync(zprofilePath);
let updated = false; let updated = false;
if ( !data.includes('autoload -U +X compinit && compinit') ) {
try { try {
fs.appendFileSync(path, '\nautoload -U +X compinit && compinit'); if ( !data.includes('autoload -U +X compinit && compinit') ) {
fs.appendFileSync(zprofilePath, '\nautoload -U +X compinit && compinit');
updated = true; updated = true;
} catch {
spinner.fail(`Error appending in ${ this.zprofile }`);
}
} }
if ( !data.includes('autoload -U +X bashcompinit && bashcompinit') ) { if ( !data.includes('autoload -U +X bashcompinit && bashcompinit') ) {
try { fs.appendFileSync(zprofilePath, '\nautoload -U +X bashcompinit && bashcompinit');
fs.appendFileSync(path, '\nautoload -U +X bashcompinit && bashcompinit');
updated = true; updated = true;
} catch {
spinner.fail(`Error appending in ${ this.zprofile }`);
} }
} if ( !data.includes(`source ${ bashPath }`) ) {
if ( !data.includes(`source ${ bash_path }`) ) { fs.appendFileSync(zprofilePath, `\nsource ${ bashPath }`);
try {
fs.appendFileSync(path, `\nsource ${ bash_path }`);
updated = true; updated = true;
}
} catch { } catch {
spinner.fail(`Error appending in ${ this.zprofile }`); spinner.fail(`Error appending in ${ this.zprofile }`);
return;
} }
}
if ( updated ) { spinner.succeed(updated ? `Zsh profile updated.` : `Zsh profile already up to date.`);
spinner.succeed(`Zsh profile updated.`);
} else {
spinner.succeed(`Zsh profile already up to date.`);
}
} else { } else {
try { try {
fs.writeFileSync(path, this.loadBashCompletion); fs.writeFileSync(zprofilePath, this.loadBashCompletion);
spinner.succeed(`Zsh profile written.`); spinner.succeed(`Zsh profile written.`);
} catch ( error ) { } catch ( error ) {
spinner.fail(`Error writing in ${ this.zprofile }`); spinner.fail(`Error writing in ${ this.zprofile }`);
......
...@@ -18,7 +18,9 @@ class ExerciseCommand extends CommanderCommand { ...@@ -18,7 +18,9 @@ class ExerciseCommand extends CommanderCommand {
ExerciseCorrectionCommand.registerOnCommand(this.command); ExerciseCorrectionCommand.registerOnCommand(this.command);
} }
protected async commandAction(): Promise<void> { } protected async commandAction(): Promise<void> {
// No action
}
} }
......
...@@ -34,7 +34,6 @@ class ExerciseCorrectionCommand extends CommanderCommand { ...@@ -34,7 +34,6 @@ class ExerciseCorrectionCommand extends CommanderCommand {
Config.interactiveMode ? await this.showCorrectionsInteractive(assignment, assignmentGetSpinner) : this.showCorrections(assignment, assignmentGetSpinner); Config.interactiveMode ? await this.showCorrectionsInteractive(assignment, assignmentGetSpinner) : this.showCorrections(assignment, assignmentGetSpinner);
} else { } else {
assignmentGetSpinner.fail(`The assignment doesn't have any corrections yet`); assignmentGetSpinner.fail(`The assignment doesn't have any corrections yet`);
return;
} }
} }
......
...@@ -68,7 +68,7 @@ class ExerciseCreateCommand extends CommanderCommand { ...@@ -68,7 +68,7 @@ class ExerciseCreateCommand extends CommanderCommand {
console.log(TextStyle.BLOCK('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.name, members);
const oraInfo = (message: string) => { const oraInfo = (message: string) => {
ora({ ora({
......
...@@ -13,7 +13,7 @@ class ExerciseRunCommand extends CommanderCommand { ...@@ -13,7 +13,7 @@ class ExerciseRunCommand extends CommanderCommand {
} }
protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> { protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> {
await ExerciseRunHelper.run(options); await (new ExerciseRunHelper(options)).run();
} }
} }
......
...@@ -4,7 +4,11 @@ import JSON5 from 'json5'; ...@@ -4,7 +4,11 @@ import JSON5 from 'json5';
class LocalConfigFile { class LocalConfigFile {
constructor(private filename: string) { private readonly filename: string;
constructor(filename: string) {
this.filename = filename;
this.loadConfig(); this.loadConfig();
} }
......
...@@ -4,15 +4,16 @@ import ora from 'ora'; ...@@ -4,15 +4,16 @@ import ora from 'ora';
import TextStyle from '../types/TextStyle'; import TextStyle from '../types/TextStyle';
import inquirer from 'inquirer'; import inquirer from 'inquirer';
function renameFile(filename: string, showWarning: boolean) { function renameFile(filename: string, showWarning: boolean) {
const old_filename = `${filename}.old` const oldFilename = `${ filename }.old`;
const spinner: ora.Ora = ora(`Renaming ${TextStyle.CODE(filename)} in ${TextStyle.CODE(old_filename)} ...`).start(); const spinner: ora.Ora = ora(`Renaming ${ TextStyle.CODE(filename) } in ${ TextStyle.CODE(oldFilename) } ...`).start();
try { try {
renameSync(filename, old_filename); renameSync(filename, oldFilename);
spinner.succeed(`Renaming success: ${TextStyle.CODE(filename)} in ${TextStyle.CODE(old_filename)}`); spinner.succeed(`Renaming success: ${ TextStyle.CODE(filename) } in ${ TextStyle.CODE(oldFilename) }`);
if ( showWarning ) { 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.`); console.log(`${ TextStyle.WARNING('Warning:') } Your ${ TextStyle.CODE(filename) } was renamed ${ TextStyle.CODE(oldFilename) }. If this was not intended please revert this change.`);
} }
} catch ( error ) { } catch ( error ) {
spinner.fail(`Renaming failed: ${ error }.`); spinner.fail(`Renaming failed: ${ error }.`);
...@@ -25,24 +26,24 @@ async function askConfirmation(msg: string): Promise<boolean> { ...@@ -25,24 +26,24 @@ async function askConfirmation(msg: string): Promise<boolean> {
message: msg, message: msg,
type : 'confirm', type : 'confirm',
default: false default: false
})).confirm })).confirm;
} }
// Returns false, when the renaming is interrupted // Returns false, when the renaming is interrupted
export async function tryRenameFile(path: string, force: boolean): Promise<boolean> { export async function tryRenameFile(path: string, force: boolean): Promise<boolean> {
const fileExists = existsSync(path) const fileExists = existsSync(path);
if ( fileExists && force ) { if ( fileExists && force ) {
renameFile(path, false) renameFile(path, false);
} else if ( fileExists ) { } else if ( fileExists ) {
const confirm = (await askConfirmation(`${TextStyle.CODE(path)} in ${TextStyle.CODE(path + '.old')}. Are you sure?`)) const confirm = (await askConfirmation(`${ TextStyle.CODE(path) } in ${ TextStyle.CODE(path + '.old') }. Are you sure?`));
if ( confirm ) { if ( confirm ) {
renameFile(path, true) renameFile(path, true);
} else { } else {
console.log(`${TextStyle.BLOCK('Completion generation interrupted.')}`) console.log(`${ TextStyle.BLOCK('Completion generation interrupted.') }`);
return false return false;
} }
} }
return true return true;
} }
const fishFunction = ` const fishFunction = `
...@@ -64,12 +65,12 @@ complete -f -c dojo ...@@ -64,12 +65,12 @@ complete -f -c dojo
`; `;
function isHidden(cmd: Command): boolean { function isHidden(cmd: Command): boolean {
return (cmd as Command & { _hidden: boolean })._hidden return (cmd as Command & { _hidden: boolean })._hidden;
} }
function isLeaf(cmd: Command): boolean { function isLeaf(cmd: Command): boolean {
return cmd.commands.length == 0; return cmd.commands.length === 0;
} }
function flatten(cmd: Command): Array<Command> { function flatten(cmd: Command): Array<Command> {
...@@ -79,7 +80,7 @@ function flatten(cmd: Command): Array<Command> { ...@@ -79,7 +80,7 @@ function flatten(cmd: Command): Array<Command> {
return cmd.commands return cmd.commands
.filter(c => !isHidden(c)) .filter(c => !isHidden(c))
.map(child => flatten(child)) .map(child => flatten(child))
.reduce((acc, cmd) => acc.concat(cmd), [cmd]); .reduce((acc, subCmd) => acc.concat(subCmd), [ cmd ]);
} }
} }
...@@ -114,7 +115,7 @@ export function getRoot(cmd: Command): Command { ...@@ -114,7 +115,7 @@ export function getRoot(cmd: Command): Command {
function getOptions(cmd: Command): string { function getOptions(cmd: Command): string {
// we remove <args>, [command], and , from option lines // we remove <args>, [command], and , from option lines
return cmd.options.filter(opt => !opt.hidden).map(opt => opt.flags.replace(/<.*?>/, '').replace(/\[.*?\]/, '').replace(',', '').trimEnd()).join(' '); return cmd.options.filter(opt => !opt.hidden).map(opt => opt.flags.replace(/<.*?>/, '').replace(/\[.*?]/, '').replace(',', '').trimEnd()).join(' ');
} }
function commandsAndOptionsToString(cmd: Command): string { function commandsAndOptionsToString(cmd: Command): string {
...@@ -126,9 +127,8 @@ function addLine(identLevel: number, pattern: string): string { ...@@ -126,9 +127,8 @@ function addLine(identLevel: number, pattern: string): string {
} }
function generateBashSubCommands(cmd: Command, current: number, maxDepth: number, ident: number): string { function generateBashSubCommands(cmd: Command, current: number, maxDepth: number, ident: number): string {
if (current == maxDepth) { 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'); 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 { } else {
let data = addLine(ident, `case "\${COMP_WORDS[$COMP_CWORD - ${ maxDepth - current + 1 }]}" in`) + addLine(ident + 1, `${ cmd.name() })`); 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 => { cmd.commands.filter(c => !isHidden(c)).forEach(subCmd => {
...@@ -143,7 +143,7 @@ export function generateBashCompletion(root: Command): string { ...@@ -143,7 +143,7 @@ export function generateBashCompletion(root: Command): string {
const depth = computeDepth(root); const depth = computeDepth(root);
let data = addLine(0, '#/usr/bin/env bash\nfunction _dojo_completions()') + addLine(0, '{') + addLine(1, 'latest="${COMP_WORDS[$COMP_CWORD]}"'); 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++ ) { for ( let i = 1 ; i <= depth ; i++ ) {
data += addLine(1, `${i == 1 ? 'if' : 'elif'} [ $COMP_CWORD -eq ${depth - i + 1} ]`) + addLine(1, 'then'); data += addLine(1, `${ i === 1 ? 'if' : 'elif' } [ $COMP_CWORD -eq ${ depth - i + 1 } ]`) + addLine(1, 'then');
data += generateBashSubCommands(root, i, depth, 2); 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'); data += addLine(1, 'fi') + addLine(1, 'COMPREPLY=($(compgen -W "$words" -- $latest))') + addLine(1, 'return 0') + addLine(0, '}') + addLine(0, 'complete -F _dojo_completions dojo');
...@@ -167,21 +167,15 @@ function hasOptions(cmd: Command): boolean { ...@@ -167,21 +167,15 @@ function hasOptions(cmd: Command): boolean {
} }
function optionsToString(cmd: Command): string { function optionsToString(cmd: Command): string {
return cmd.options.filter(opt => !opt.hidden).map(opt => { return cmd.options.filter(opt => !opt.hidden).map(opt => `${ prefix } ${ computeHeight(cmd) } ${ generateCommandChain(cmd) }' -a "${ opt.short ?? '' } ${ opt.long ?? '' }" -d "${ opt.description }"`).join('\n').concat('\n');
return `${prefix} ${computeHeight(cmd)} ${generateCommandChain(cmd)}' -a "${opt.short ?? ''} ${opt.long ?? ''}" -d "${opt.description}"`;
}).join('\n').concat('\n');
} }
export function generateFishCompletion(root: Command): string { export function generateFishCompletion(root: Command): string {
const commands = flatten(root); const commands = flatten(root);
const data = fishFunction.concat(// add completions for options return 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 => 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 => { 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(''));
return `${prefix} ${computeHeight(cmd)} ${generateCommandChain(cmd)}' -a ${subCmd.name()} -d "${subCmd.description()}"`;
}).join('\n').concat('\n')).join(''));
return data;
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment