Skip to content

Commit

Permalink
#101: #102: implement update & create commandlet (#150)
Browse files Browse the repository at this point in the history
  • Loading branch information
salimbouch authored Apr 8, 2024
1 parent ecc2e58 commit b6a41bd
Show file tree
Hide file tree
Showing 29 changed files with 646 additions and 60 deletions.
3 changes: 3 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/cli/Ideasy.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ public int runOrThrow(String... args) {

CliArguments arguments = new CliArguments(args);
this.context = initContext(arguments);
if (this.context.getIdeRoot() == null) {
return 1;
}
return this.context.run(arguments);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package com.devonfw.tools.ide.commandlet;

import com.devonfw.tools.ide.common.StepContainer;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.property.StringProperty;
import com.devonfw.tools.ide.repo.CustomTool;
import com.devonfw.tools.ide.tool.CustomToolCommandlet;
import com.devonfw.tools.ide.tool.ToolCommandlet;
import com.devonfw.tools.ide.variable.IdeVariables;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public abstract class AbstractUpdateCommandlet extends Commandlet {

protected final StringProperty settingsRepo;

/**
* The constructor.
*
* @param context the {@link IdeContext}.
*/
public AbstractUpdateCommandlet(IdeContext context) {

super(context);
settingsRepo = new StringProperty("", false, "settingsRepository");
}

@Override
public void run() {

updateSettings();
Path templatesFolder = this.context.getSettingsPath().resolve(IdeContext.FOLDER_TEMPLATES);

if (!Files.exists(templatesFolder)) {
Path legacyTemplatesFolder = this.context.getSettingsPath().resolve(IdeContext.FOLDER_LEGACY_TEMPLATES);
if (Files.exists(legacyTemplatesFolder)) {
templatesFolder = legacyTemplatesFolder;
} else {
this.context.warning("Templates folder is missing in settings repository.");
return;
}
}

setupConf(templatesFolder, this.context.getIdeHome());
updateSoftware();
}

private void setupConf(Path template, Path conf) {

List<Path> children = this.context.getFileAccess().listChildren(template, f -> true);
for (Path child : children) {

String basename = child.getFileName().toString();
Path confPath = conf.resolve(basename);

if (Files.isDirectory(child)) {
if (!Files.isDirectory(confPath)) {
this.context.getFileAccess().mkdirs(confPath);
}
setupConf(child, confPath);
} else if (Files.isRegularFile(child)) {
if (Files.isRegularFile(confPath)) {
this.context.debug("Configuration {} already exists - skipping to copy from {}", confPath, child);
} else {
if (!basename.equals("settings.xml")) {
this.context.info("Copying template {} to {}.", child, confPath);
this.context.getFileAccess().copy(child, confPath);
}
}
}
}
}

private void updateSettings() {

this.context.info("Updating settings repository ...");
Path settingsPath = this.context.getSettingsPath();
if (Files.isDirectory(settingsPath) && !this.context.getFileAccess().isEmptyDir(settingsPath)) {
// perform git pull on the settings repo
this.context.getGitContext().pull(settingsPath);
this.context.success("Successfully updated settings repository.");
} else {
// check if a settings repository is given then clone, otherwise prompt user for a repository.
String repository = settingsRepo.getValue();
if (repository == null) {
String message = "Missing your settings at " + settingsPath + " and no SETTINGS_URL is defined.\n" +
"Further details can be found here: https://github.com/devonfw/IDEasy/blob/main/documentation/settings.asciidoc\n" +
"Please contact the technical lead of your project to get the SETTINGS_URL for your project.\n" +
"In case you just want to test IDEasy you may simply hit return to install the default settings.\n" +
"Settings URL [" + IdeContext.DEFAULT_SETTINGS_REPO_URL + "]:";
repository = this.context.askForInput(message, IdeContext.DEFAULT_SETTINGS_REPO_URL);
}
this.context.getGitContext().pullOrClone(repository, settingsPath);
this.context.success("Successfully cloned settings repository.");
}
}

private void updateSoftware() {

Set<ToolCommandlet> toolCommandlets = new HashSet<>();

// installed tools in IDE_HOME/software
List<Path> softwares = this.context.getFileAccess().listChildren(this.context.getSoftwarePath(), Files::isDirectory);
for (Path software : softwares) {
String toolName = software.getFileName().toString();
ToolCommandlet toolCommandlet = this.context.getCommandletManager().getToolCommandletOrNull(toolName);
if (toolCommandlet != null) {
toolCommandlets.add(toolCommandlet);
}
}

// regular tools in $IDE_TOOLS
List<String> regularTools = IdeVariables.IDE_TOOLS.get(this.context);
if (regularTools != null) {
for (String regularTool : regularTools) {
toolCommandlets.add(this.context.getCommandletManager().getToolCommandlet(regularTool));
}
}

// custom tools in ide-custom-tools.json
for (CustomTool customTool : this.context.getCustomToolRepository().getTools()) {
CustomToolCommandlet customToolCommandlet = new CustomToolCommandlet(this.context, customTool);
toolCommandlets.add(customToolCommandlet);
}

// update/install the toolCommandlets
StepContainer container = new StepContainer(this.context);
for (ToolCommandlet toolCommandlet : toolCommandlets) {
try {
container.startStep(toolCommandlet.getName());
toolCommandlet.install(false);
container.endStep(toolCommandlet.getName(), true, null);
} catch (Exception e) {
container.endStep(toolCommandlet.getName(), false, e);
}
}
// summary
if (!toolCommandlets.isEmpty()) {
container.complete();
}
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public interface CommandletManager {

/**
* @param name the {@link Commandlet#getName() name} of the requested {@link ToolCommandlet}.
* @return the requested {@link ToolCommandlet} or {@code null} if not found.
* @return the requested {@link ToolCommandlet} if found.
* @throws IllegalArgumentException if the commandlet with the given name is not a {@link ToolCommandlet}
*/
default ToolCommandlet getToolCommandlet(String name) {

Expand All @@ -50,4 +51,16 @@ default ToolCommandlet getToolCommandlet(String name) {
throw new IllegalArgumentException("The commandlet " + name + " is not a ToolCommandlet!");
}

/**
* @param name the {@link Commandlet#getName() name} of the requested {@link ToolCommandlet}.
* @return the requested {@link ToolCommandlet} or {@code null} if not found.
*/
default ToolCommandlet getToolCommandletOrNull(String name) {

Commandlet commandlet = getCommandlet(name);
if (commandlet instanceof ToolCommandlet) {
return (ToolCommandlet) commandlet;
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public CommandletManagerImpl(IdeContext context) {
add(new EditionListCommandlet(context));
add(new VersionCommandlet(context));
add(new RepositoryCommandlet(context));
add(new UpdateCommandlet(context));
add(new CreateCommandlet(context));
add(new Gh(context));
add(new Helm(context));
add(new Java(context));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.devonfw.tools.ide.commandlet;

import com.devonfw.tools.ide.context.AbstractIdeContext;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.io.FileAccess;
import com.devonfw.tools.ide.property.StringProperty;

import java.nio.file.Path;

/**
* {@link Commandlet} to create a new IDEasy instance
*/
public class CreateCommandlet extends AbstractUpdateCommandlet {

public final StringProperty newProject;


/**
* The constructor.
*
* @param context the {@link IdeContext}.
*/
public CreateCommandlet(IdeContext context) {

super(context);
addKeyword(getName());
newProject = add(new StringProperty("", true, "project"));
add(this.settingsRepo);
}

@Override
public String getName() {

return "create";
}

@Override
public boolean isIdeHomeRequired() {

return false;
}

@Override
public void run() {

String newProjectName = newProject.getValue();
Path newProjectPath = this.context.getIdeRoot().resolve(newProjectName);

this.context.info("Creating new IDEasy project in {}", newProjectPath);
if (!this.context.getFileAccess().isEmptyDir(newProjectPath)) {
this.context.askToContinue("Directory " + newProjectPath + " already exists. Do you want to continue?");
} else {
this.context.getFileAccess().mkdirs(newProjectPath);
}

initializeProject(newProjectPath);
this.context.setIdeHome(newProjectPath);
super.run();
updateRepositories();
this.context.success("Successfully created new project '{}'.", newProjectName);
}

private void initializeProject(Path newInstancePath) {

FileAccess fileAccess = this.context.getFileAccess();
fileAccess.mkdirs(newInstancePath.resolve(IdeContext.FOLDER_SOFTWARE));
fileAccess.mkdirs(newInstancePath.resolve(IdeContext.FOLDER_PLUGINS));
fileAccess.mkdirs(newInstancePath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN));
}

private void updateRepositories() {

this.context.getCommandletManager().getCommandlet(RepositoryCommandlet.class).run();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.devonfw.tools.ide.commandlet;

import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.property.StringProperty;

/**
* {@link Commandlet} to update settings, software and repositories
*/
public class UpdateCommandlet extends AbstractUpdateCommandlet {

/**
* The constructor.
*
* @param context the {@link IdeContext}.
*/
public UpdateCommandlet(IdeContext context) {

super(context);
addKeyword(getName());
}

@Override
public String getName() {

return "update";
}

@Override
public void run() {

super.run();
}
}
89 changes: 89 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/common/StepContainer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.devonfw.tools.ide.common;

import com.devonfw.tools.ide.cli.CliException;
import com.devonfw.tools.ide.context.IdeContext;

import java.util.ArrayList;
import java.util.List;

/**
* A utility class to manage and log the progress of steps in a process.
* Each step can be started, ended with success or failure, and the overall completion
* status can be checked.
* @throws CliException if one or more steps fail.
*/
public class StepContainer {

private final IdeContext context;

/** List of steps that ended successfully. */
private List<String> successfulSteps;

/** List of steps that failed. */
private List<String> failedSteps;

/**
* The constructor.
*
* @param context the {@link IdeContext}.
*/
public StepContainer(IdeContext context) {

this.context = context;
successfulSteps = new ArrayList<>();
failedSteps = new ArrayList<>();
}

/**
* Logs the start of a step.
*
* @param stepName the name of the step.
*/
public void startStep(String stepName) {

this.context.step("Starting step: {}", stepName);
}

/**
* Logs the end of a step, indicating success or failure.
*
* @param stepName the name of the step.
* @param success {@code true} if the step succeeded, {@code false} otherwise.
* @param e the exception associated with the failure, or {@code null} if the step succeeded.
*/
public void endStep(String stepName, boolean success, Throwable e) {

if (success) {
successfulSteps.add(stepName);
this.context.success("Step '{}' succeeded.", stepName);
} else {
failedSteps.add(stepName);
this.context.warning("Step '{}' failed.", stepName);
if (e != null) {
this.context.error(e);
}
}
}

/**
* Checks the overall completion status of all steps.
*
* @throws CliException if one or more steps fail, providing a detailed summary.
*/
public void complete() {

if (failedSteps.isEmpty()) {
this.context.success("All {} steps ended successfully!", successfulSteps.size());
} else {
throw new CliException(String.format("%d step(s) failed (%d%%) and %d step(s) succeeded (%d%%) out of %d step(s)!",
failedSteps.size(), calculatePercentage(failedSteps.size()), successfulSteps.size(),
100 - calculatePercentage(failedSteps.size()), successfulSteps.size() + failedSteps.size()));
}
}

private int calculatePercentage(int count) {

return (count * 100) / (successfulSteps.size() + failedSteps.size());
}

}
Loading

0 comments on commit b6a41bd

Please sign in to comment.