diff --git a/app.json b/app.json
index 69ff15d..46c73e7 100644
--- a/app.json
+++ b/app.json
@@ -5,6 +5,8 @@
"eu.maveniverse.maven.mima.runtime:standalone-static": "2.4.15",
"eu.maveniverse.maven.mima:context": "2.4.15",
"info.picocli:picocli": "4.7.6",
+ "org.jline:jline-console-ui": "3.26.2",
+ "org.jline:jline-terminal-jni": "3.26.2",
"org.slf4j:slf4j-api": "2.0.13",
"org.slf4j:slf4j-log4j12": "2.0.13",
"org.slf4j:slf4j-simple": "2.0.13"
diff --git a/pom.xml b/pom.xml
index a1c9331..ac03def 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,6 +35,7 @@
2.4.15
4.7.6
2.11.0
+ 3.26.2
2.0.13
2.43.0
1.22.0
@@ -61,6 +62,16 @@
gson
${version.gson}
+
+ org.jline
+ jline-console-ui
+ ${version.jline}
+
+
+ org.jline
+ jline-terminal-jni
+ ${version.jline}
+
org.slf4j
slf4j-api
diff --git a/src/main/java/org/codejive/jpm/Main.java b/src/main/java/org/codejive/jpm/Main.java
index 4cc8177..9cef57f 100644
--- a/src/main/java/org/codejive/jpm/Main.java
+++ b/src/main/java/org/codejive/jpm/Main.java
@@ -2,6 +2,7 @@
//DEPS eu.maveniverse.maven.mima:context:2.4.15 eu.maveniverse.maven.mima.runtime:standalone-static:2.4.15
//DEPS info.picocli:picocli:4.7.6
//DEPS com.google.code.gson:gson:2.11.0
+//DEPS org.jline:jline-console-ui:3.26.2 org.jline:jline-terminal-jni:3.26.2
//DEPS org.slf4j:slf4j-api:2.0.13 org.slf4j:slf4j-simple:2.0.13
//SOURCES Jpm.java json/AppInfo.java util/FileUtils.java util/ResolverUtils.java util/SearchUtils.java
//SOURCES util/SearchResult.java util/SyncStats.java util/Version.java
@@ -16,6 +17,13 @@
import java.util.stream.Collectors;
import org.codejive.jpm.util.SyncStats;
import org.codejive.jpm.util.Version;
+import org.jline.consoleui.prompt.ConsolePrompt;
+import org.jline.consoleui.prompt.ListResult;
+import org.jline.consoleui.prompt.PromptResultItemIF;
+import org.jline.consoleui.prompt.builder.ListPromptBuilder;
+import org.jline.consoleui.prompt.builder.PromptBuilder;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.TerminalBuilder;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
@@ -74,6 +82,12 @@ static class Sync implements Callable {
@Mixin QuietMixin quietMixin;
@Mixin CopyMixin copyMixin;
+ @Option(
+ names = {"-i", "--interactive"},
+ description = "Interactively search and select artifacts to install",
+ defaultValue = "false")
+ private boolean interactive;
+
@Option(
names = {"-m", "--max"},
description = "Maximum number of results to return",
@@ -82,22 +96,135 @@ static class Sync implements Callable {
@Parameters(
paramLabel = "artifactPattern",
- description = "Partial or full artifact name to search for.")
+ description = "Partial or full artifact name to search for.",
+ defaultValue = "")
private String artifactPattern;
@Override
public Integer call() throws Exception {
- String[] artifactNames =
- Jpm.builder()
- .directory(copyMixin.directory)
- .noLinks(copyMixin.noLinks)
- .build()
- .search(artifactPattern, Math.min(max, 200));
- if (artifactNames.length > 0) {
- Arrays.stream(artifactNames).forEach(System.out::println);
+ if (interactive || artifactPattern == null || artifactPattern.isEmpty()) {
+ try (Terminal terminal = TerminalBuilder.builder().build()) {
+ while (true) {
+ ConsolePrompt prompt = new ConsolePrompt(terminal);
+ if (artifactPattern == null || artifactPattern.isEmpty()) {
+ artifactPattern = askString(prompt, "Search for:");
+ }
+ String[] artifactNames = search(artifactPattern);
+ PromptBuilder promptBuilder = prompt.getPromptBuilder();
+ addSelectItem(promptBuilder, "Select artifact:", artifactNames);
+ addSelectArtifactAction(promptBuilder);
+ Map result =
+ prompt.prompt(promptBuilder.build());
+ String selectedArtifact = getSelectedId(result, "item");
+ String artifactAction = getSelectedId(result, "action");
+ if ("install".equals(artifactAction)) {
+ SyncStats stats =
+ Jpm.builder()
+ .directory(copyMixin.directory)
+ .noLinks(copyMixin.noLinks)
+ .build()
+ .install(new String[] {selectedArtifact});
+ if (!quietMixin.quiet) {
+ printStats(stats);
+ }
+ } else if ("copy".equals(artifactAction)) {
+ SyncStats stats =
+ Jpm.builder()
+ .directory(copyMixin.directory)
+ .noLinks(copyMixin.noLinks)
+ .build()
+ .copy(new String[] {selectedArtifact}, false);
+ if (!quietMixin.quiet) {
+ printStats(stats);
+ }
+ } else if ("version".equals(artifactAction)) {
+ artifactPattern = selectedArtifact;
+ continue;
+ } else { // quit
+ break;
+ }
+ String finalAction = selectFinalAction(prompt);
+ if ("quit".equals(finalAction)) {
+ break;
+ }
+ artifactPattern = null;
+ }
+ }
+ } else {
+ String[] artifactNames = search(artifactPattern);
+ if (artifactNames.length > 0) {
+ Arrays.stream(artifactNames).forEach(System.out::println);
+ }
}
return 0;
}
+
+ String[] search(String artifactPattern) throws IOException {
+ return Jpm.builder()
+ .directory(copyMixin.directory)
+ .noLinks(copyMixin.noLinks)
+ .build()
+ .search(artifactPattern, Math.min(max, 200));
+ }
+
+ String askString(ConsolePrompt prompt, String message) throws IOException {
+ PromptBuilder promptBuilder = prompt.getPromptBuilder();
+ promptBuilder.createInputPrompt().name("input").message(message).addPrompt();
+ Map result = prompt.prompt(promptBuilder.build());
+ return result.get("input").getResult();
+ }
+
+ void addSelectItem(PromptBuilder promptBuilder, String message, String[] items)
+ throws IOException {
+ ListPromptBuilder artifactsList =
+ promptBuilder.createListPrompt().name("item").message(message).pageSize(10);
+ for (String artifactName : items) {
+ artifactsList.newItem(artifactName).text(artifactName).add();
+ }
+ artifactsList.addPrompt();
+ }
+
+ void addSelectArtifactAction(PromptBuilder promptBuilder) throws IOException {
+ promptBuilder
+ .createListPrompt()
+ .name("action")
+ .message("What to do:")
+ .newItem("install")
+ .text("Install artifact")
+ .add()
+ .newItem("copy")
+ .text("Copy artifact")
+ .add()
+ .newItem("version")
+ .text("Select different version")
+ .add()
+ .newItem("quit")
+ .text("Quit")
+ .add()
+ .addPrompt();
+ }
+
+ String selectFinalAction(ConsolePrompt prompt) throws IOException {
+ PromptBuilder promptBuilder = prompt.getPromptBuilder();
+ promptBuilder
+ .createListPrompt()
+ .name("action")
+ .message("What to do:")
+ .newItem("again")
+ .text("Search again")
+ .add()
+ .newItem("quit")
+ .text("Quit")
+ .add()
+ .addPrompt();
+ Map result = prompt.prompt(promptBuilder.build());
+ return getSelectedId(result, "action");
+ }
+
+ private static String getSelectedId(
+ Map result, String itemName) {
+ return ((ListResult) result.get(itemName)).getSelectedId();
+ }
}
@Command(
diff --git a/src/main/java/org/codejive/jpm/util/SearchUtils.java b/src/main/java/org/codejive/jpm/util/SearchUtils.java
index bd44d59..9e07a93 100644
--- a/src/main/java/org/codejive/jpm/util/SearchUtils.java
+++ b/src/main/java/org/codejive/jpm/util/SearchUtils.java
@@ -67,7 +67,7 @@ private static SearchResult select(String query, int start, int count) throws IO
}
String searchUrl =
String.format(
- "https://search.maven.org/solrsearch/select?start=%d&rows=%d&q=%s",
+ "https://search.maven.org/solrsearch/select?start=%d&rows=%d&q=p:jar+AND+%s",
start, count, URLEncoder.encode(finalQuery, "UTF-8"));
if (parts.length >= 3) {
searchUrl += "&core=gav";
@@ -90,23 +90,23 @@ private static SearchResult select(String query, int start, int count) throws IO
}
List artifacts =
result.response.docs.stream()
- .filter(
- d ->
- parts.length != 2
- || d.g.contains(parts[0])
- && d.a.contains(parts[1]))
- .map(
- d ->
- new DefaultArtifact(
- d.g,
- d.a,
- "",
- d.v != null ? d.v : d.latestVersion))
+ .filter(d -> acceptDoc(d, parts))
+ .map(SearchUtils::toArtifact)
.collect(Collectors.toList());
return new SearchResult(artifacts, query, start, count, result.response.numFound);
}
}
}
+
+ private static boolean acceptDoc(MsrDoc d, String[] parts) {
+ return d.ec != null
+ && d.ec.contains(".jar")
+ && (parts.length != 2 || d.g.contains(parts[0]) && d.a.contains(parts[1]));
+ }
+
+ private static DefaultArtifact toArtifact(MsrDoc d) {
+ return new DefaultArtifact(d.g, d.a, "", d.v != null ? d.v : d.latestVersion);
+ }
}
class MvnSearchResult {
@@ -129,4 +129,6 @@ class MsrDoc {
public String a;
public String v;
public String latestVersion;
+ public String p;
+ public List ec;
}