Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: app commands #868

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
37 changes: 37 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,7 +20,10 @@
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;
Expand Down Expand Up @@ -530,4 +535,36 @@ 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 url = new URL("https://developer.app.crowdin.net/items/Item?filter={\"slug\":{\"_eq\":\"" + id + "\"}}&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 apps
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
crowdin.app.list.usage.description=List apps
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 was uninstalled|@
message.application.install=@|green Application %s was installed|@
Comment on lines +795 to +796
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
message.application.uninstall=@|green Application %s was uninstalled|@
message.application.install=@|green Application %s was installed|@
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
Loading