Skip to content

Commit

Permalink
feat: app commands (#868)
Browse files Browse the repository at this point in the history
  • Loading branch information
yevheniyJ authored Nov 20, 2024
1 parent 7b93ceb commit e07c17a
Show file tree
Hide file tree
Showing 27 changed files with 544 additions and 2 deletions.
7 changes: 7 additions & 0 deletions src/main/java/com/crowdin/cli/client/CrowdinClientCore.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ protected static <T> T executeRequest(Supplier<T> r) {
return executeRequest(new HashMap<BiPredicate<String, String>, RuntimeException>(), r);
}

protected static void executeRequest(Runnable r) {
executeRequest(() -> {
r.run();
return null;
});
}

protected static <T, R extends Exception> T executeRequest(Map<BiPredicate<String, String>, R> errorHandlers, Supplier<T> r) throws R {
try {
return r.get();
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/com/crowdin/cli/client/CrowdinProjectClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.crowdin.cli.commands.picocli.ExitCodeExceptionMapper;
import com.crowdin.cli.utils.Utils;
import com.crowdin.client.applications.installations.model.ApplicationInstallation;
import com.crowdin.client.applications.installations.model.InstallApplicationRequest;
import com.crowdin.client.branches.model.*;
import com.crowdin.client.core.model.PatchRequest;
import com.crowdin.client.labels.model.AddLabelRequest;
Expand All @@ -18,10 +20,15 @@
import com.crowdin.client.stringcomments.model.StringComment;
import com.crowdin.client.translations.model.*;
import com.crowdin.client.translationstatus.model.LanguageProgress;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONObject;

import java.io.InputStream;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.BiPredicate;

Expand Down Expand Up @@ -530,4 +537,37 @@ public Project addProject(AddProjectRequest request) {
.addProject(request)
.getData());
}

@Override
public List<ApplicationInstallation> listApplications() {
return executeRequestFullList((limit, offset) ->
this.client.getApplicationsApi().listApplicationInstallations(limit, offset)
);
}

@Override
public void uninstallApplication(String id, boolean force) {
executeRequest(() -> this.client.getApplicationsApi().deleteApplicationInstallation(id, force));
}

@Override
public void installApplication(String url) {
var req = new InstallApplicationRequest();
req.setUrl(url);
executeRequest(() -> this.client.getApplicationsApi().installApplication(req));
}

@Override
@SneakyThrows
public Optional<String> findManifestUrl(String id) {
var query = URLEncoder.encode( "{\"slug\":{\"_eq\":\"" + id + "\"}}", StandardCharsets.UTF_8);
var url = new URL("https://developer.app.crowdin.net/items/Item?filter=" + query + "&fields=manifest");
var res = new String(url.openStream().readAllBytes());
JSONObject json = new JSONObject(res);
var apps = (JSONArray) json.get("data");
if (apps.isEmpty()) {
return Optional.empty();
}
return Optional.ofNullable(JSONObject.class.cast(apps.get(0)).get("manifest").toString());
}
}
11 changes: 11 additions & 0 deletions src/main/java/com/crowdin/cli/client/ProjectClient.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.crowdin.cli.client;

import com.crowdin.client.applications.installations.model.ApplicationInstallation;
import com.crowdin.client.applications.model.ApplicationDataResponseObject;
import com.crowdin.client.branches.model.*;
import com.crowdin.client.core.model.PatchRequest;
import com.crowdin.client.labels.model.AddLabelRequest;
Expand All @@ -17,6 +19,7 @@
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.Optional;

public interface ProjectClient extends Client {

Expand Down Expand Up @@ -129,4 +132,12 @@ default CrowdinProjectFull downloadFullProject() {
List<? extends Project> listProjects();

Project addProject(AddProjectRequest request);

List<ApplicationInstallation> listApplications();

void uninstallApplication(String id, boolean force);

void installApplication(String url);

Optional<String> findManifestUrl(String id);
}
6 changes: 6 additions & 0 deletions src/main/java/com/crowdin/cli/commands/Actions.java
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,10 @@ NewAction<PropertiesWithFiles, ProjectClient> preTranslate(
NewAction<ProjectProperties, ProjectClient> projectList(boolean isVerbose);

NewAction<ProjectProperties, ProjectClient> projectAdd(String name, boolean isStringBased, String sourceLanguage, List<String> languages, boolean isPublic, boolean plainView);

NewAction<ProjectProperties, ProjectClient> listApps(boolean plainView);

NewAction<ProjectProperties, ProjectClient> uninstallApp(String id, Boolean force);

NewAction<ProjectProperties, ProjectClient> installApp(String identifier);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.crowdin.cli.commands.actions;

import com.crowdin.cli.client.ProjectClient;
import com.crowdin.cli.commands.NewAction;
import com.crowdin.cli.commands.Outputter;
import com.crowdin.cli.commands.picocli.ExitCodeExceptionMapper;
import com.crowdin.cli.properties.ProjectProperties;
import com.crowdin.cli.utils.console.ExecutionStatus;
import lombok.RequiredArgsConstructor;

import static com.crowdin.cli.BaseCli.RESOURCE_BUNDLE;

@RequiredArgsConstructor
class AppInstallAction implements NewAction<ProjectProperties, ProjectClient> {

private final String id;

@Override
public void act(Outputter out, ProjectProperties pb, ProjectClient client) {
var manifestUrl = client.findManifestUrl(id);
if (manifestUrl.isEmpty()) {
throw new ExitCodeExceptionMapper.NotFoundException(String.format(RESOURCE_BUNDLE.getString("error.application_not_found"), this.id));
}
client.installApplication(manifestUrl.get());
out.println(ExecutionStatus.OK.withIcon(String.format(RESOURCE_BUNDLE.getString("message.application.install"), id)));
}
}
29 changes: 29 additions & 0 deletions src/main/java/com/crowdin/cli/commands/actions/AppListAction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.crowdin.cli.commands.actions;

import com.crowdin.cli.client.ProjectClient;
import com.crowdin.cli.commands.NewAction;
import com.crowdin.cli.commands.Outputter;
import com.crowdin.cli.properties.ProjectProperties;

import static com.crowdin.cli.BaseCli.RESOURCE_BUNDLE;

class AppListAction implements NewAction<ProjectProperties, ProjectClient> {
private final boolean plainView;

public AppListAction(boolean plainView) {
this.plainView = plainView;
}

@Override
public void act(Outputter out, ProjectProperties pb, ProjectClient client) {
client
.listApplications()
.forEach(app -> {
if (!plainView) {
out.println(String.format(RESOURCE_BUNDLE.getString("message.application.list"), app.getIdentifier(), app.getName()));
} else {
out.println(app.getIdentifier());
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.crowdin.cli.commands.actions;

import com.crowdin.cli.client.ProjectClient;
import com.crowdin.cli.commands.NewAction;
import com.crowdin.cli.commands.Outputter;
import com.crowdin.cli.properties.ProjectProperties;
import com.crowdin.cli.utils.console.ExecutionStatus;
import lombok.RequiredArgsConstructor;

import static com.crowdin.cli.BaseCli.RESOURCE_BUNDLE;

@RequiredArgsConstructor
class AppUninstallAction implements NewAction<ProjectProperties, ProjectClient> {

private final String id;
private final boolean force;

@Override
public void act(Outputter out, ProjectProperties pb, ProjectClient client) {
client.uninstallApplication(id, force);
out.println(ExecutionStatus.OK.withIcon(String.format(RESOURCE_BUNDLE.getString("message.application.uninstall"), id)));
}
}
15 changes: 15 additions & 0 deletions src/main/java/com/crowdin/cli/commands/actions/CliActions.java
Original file line number Diff line number Diff line change
Expand Up @@ -323,4 +323,19 @@ public NewAction<ProjectProperties, ProjectClient> projectList(boolean isVerbose
public NewAction<ProjectProperties, ProjectClient> projectAdd(String name, boolean isStringBased, String sourceLanguage, List<String> languages, boolean isPublic, boolean plainView) {
return new ProjectAddAction(name, isStringBased, sourceLanguage, languages, isPublic, plainView);
}

@Override
public NewAction<ProjectProperties, ProjectClient> listApps(boolean plainView) {
return new AppListAction(plainView);
}

@Override
public NewAction<ProjectProperties, ProjectClient> uninstallApp(String id, Boolean force) {
return new AppUninstallAction(id, force);
}

@Override
public NewAction<ProjectProperties, ProjectClient> installApp(String identifier) {
return new AppInstallAction(identifier);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.crowdin.cli.commands.picocli;

import com.crowdin.cli.client.ProjectClient;
import com.crowdin.cli.commands.Actions;
import com.crowdin.cli.commands.NewAction;
import com.crowdin.cli.properties.ProjectProperties;
import picocli.CommandLine;

@CommandLine.Command(
sortOptions = false,
name = CommandNames.INSTALL
)
class AppInstallSubcommand extends ActCommandProject {

@CommandLine.Parameters(descriptionKey = "crowdin.app.install.identifier")
protected String identifier;

@Override
protected NewAction<ProjectProperties, ProjectClient> getAction(Actions actions) {
return actions.installApp(identifier);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.crowdin.cli.commands.picocli;

import com.crowdin.cli.client.ProjectClient;
import com.crowdin.cli.commands.Actions;
import com.crowdin.cli.commands.NewAction;
import com.crowdin.cli.properties.ProjectProperties;
import picocli.CommandLine;

@CommandLine.Command(
name = CommandNames.LIST
)
class AppListSubcommand extends ActCommandProject {

@Override
protected NewAction<ProjectProperties, ProjectClient> getAction(Actions actions) {
return actions.listApps(this.plainView);
}

@CommandLine.Option(names = {"--plain"}, descriptionKey = "crowdin.list.usage.plain")
protected boolean plainView;

@Override
protected final boolean isAnsi() {
return super.isAnsi() && !plainView;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.crowdin.cli.commands.picocli;

import com.crowdin.cli.client.ProjectClient;
import com.crowdin.cli.commands.Actions;
import com.crowdin.cli.commands.NewAction;
import com.crowdin.cli.properties.ProjectProperties;
import picocli.CommandLine;

@CommandLine.Command(
sortOptions = false,
name = CommandNames.UNINSTALL
)
class AppUninstallSubcommand extends ActCommandProject {

@CommandLine.Parameters(descriptionKey = "crowdin.app.uninstall.identifier")
protected String identifier;

@CommandLine.Option(names = {"--force"}, descriptionKey = "crowdin.app.uninstall.force", order = -2)
protected boolean force;

@Override
protected NewAction<ProjectProperties, ProjectClient> getAction(Actions actions) {
return actions.uninstallApp(identifier, force);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.crowdin.cli.commands.picocli;

import picocli.CommandLine;

@CommandLine.Command(
name = CommandNames.APP,
subcommands = {
AppListSubcommand.class,
AppInstallSubcommand.class,
AppUninstallSubcommand.class
}
)
class ApplicationSubcommand extends HelpCommand {

@Override
protected CommandLine getCommand(CommandLine rootCommand) {
return rootCommand.getSubcommands().get(CommandNames.APP);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,7 @@ public final class CommandNames {

public static final String PROJECT = "project";
public static final String BROWSE = "browse";
public static final String APP = "app";
public static final String UNINSTALL = "uninstall";
public static final String INSTALL = "install";
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
LanguageSubcommand.class,
ConfigSubcommand.class,
ProjectSubcommand.class,
CompletionSubCommand.class
CompletionSubCommand.class,
ApplicationSubcommand.class
})
class RootCommand extends HelpCommand {
@Override
Expand Down
24 changes: 24 additions & 0 deletions src/main/resources/messages/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,25 @@ crowdin.project.add.source-language=Defines the source language. English by defa
crowdin.project.add.public=Defines whether the project is public. Private by default
crowdin.project.add.string-based=Defines whether the project is string-based

# CROWDIN APP COMMAND
crowdin.app.usage.description=Manage apps
crowdin.app.usage.customSynopsis=@|fg(green) crowdin app|@ [SUBCOMMAND] [CONFIG OPTIONS] [OPTIONS]

# CROWDIN APP LIST
crowdin.app.list.usage.description=List installed apps
crowdin.app.list.usage.customSynopsis=@|fg(green) crowdin app list|@ [CONFIG OPTIONS] [OPTIONS]

# CROWDIN APP INSTALL
crowdin.app.install.usage.description=Install the application
crowdin.app.install.usage.customSynopsis=@|fg(green) crowdin app install|@ <identifier> [CONFIG OPTIONS] [OPTIONS]
crowdin.app.install.identifier=Application identifier. You can find it on Crowdin Store

# CROWDIN APP UNINSTALL
crowdin.app.uninstall.usage.description=Uninstall the application
crowdin.app.uninstall.usage.customSynopsis=@|fg(green) crowdin app uninstall|@ <identifier> [CONFIG OPTIONS] [OPTIONS]
crowdin.app.uninstall.identifier=Application identifier
crowdin.app.uninstall.force=Force to delete application installation

error.collect_project_info=Failed to collect project info. Please contact our support team for help
error.no_sources=No sources found for '%s' pattern. Check the source paths in your configuration file
error.only_enterprise=Operation is available only for Crowdin Enterprise
Expand Down Expand Up @@ -521,6 +540,7 @@ error.file_not_exists=Project doesn't contain the '%s' file
error.file_required=The '--file' parameter is required for this type of project
error.dir_not_exists=Project doesn't contain the '%s' directory
error.branch_not_exists=Project doesn't contain the '%s' branch
error.application_not_found=Application with identifier '%s' doesn't exist in Crowdin Store
error.source_string_no_edit=Specify some parameters to edit the string
error.branch_no_edit=Specify some parameters to edit the branch
error.unexpected_response=Unexpected response from %s: %s
Expand Down Expand Up @@ -771,6 +791,10 @@ message.label.list_empty=No labels found
message.label.already_exists=Label '%s' already exists in the project
message.label.deleted=@|green Label '%s' deleted successfully|@

message.application.list=@|yellow %s|@ @|green %s|@
message.application.uninstall=@|green Application %s has been uninstalled|@
message.application.install=@|green Application %s has been installed|@

message.delete_obsolete.obsolete_file_delete='%s' file was deleted
message.delete_obsolete.obsolete_directory_delete=No obsolete files were found
message.delete_obsolete.no_obsolete_files_found='%s' directory was deleted
Expand Down
Loading

0 comments on commit e07c17a

Please sign in to comment.