Skip to content
Merged
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
34 changes: 34 additions & 0 deletions docs/modules/ROOT/pages/running.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,40 @@ for `jbang build` or `jbang run`. Main classes set for `jbang build` are permane
using a `//MAIN` line in the code. But main classes set for `jbang run` are temporary and will
only be used for that execution run.

If no explicit class listed JBang will search through the classes in the jar and provide list of candidates to choose from.


[source, bash]
----
$ jbang run info.picocli:picocli-codegen:4.6.3
[jbang] No main class deduced, specified nor found in a manifest, but found these candidates:

(1) picocli.codegen.aot.graalvm.DynamicProxyConfigGenerator
(2) picocli.codegen.aot.graalvm.ReflectionConfigGenerator
(3) picocli.codegen.aot.graalvm.JniConfigGenerator
(4) picocli.codegen.aot.graalvm.ResourceConfigGenerator
(5) picocli.codegen.docgen.manpage.ManPageGenerator
(0) Cancel

[jbang] Type in your choice and hit enter. Will automatically select option (0) after 30 seconds.
----

The main class can also use a glob pattern using `?` and `*` and use it to limit the candidates to choose from. For example:

[source, bash]
----
just jbang run -m "*ManPageGenerator" info.picocli:picocli-codegen:4.6.3
[jbang] No main class deduced, specified nor found in a manifest, but found these candidates:

(1) picocli.codegen.docgen.manpage.ManPageGenerator
(0) Cancel

[jbang] Type in your choice and hit enter. Will automatically select option (0) after 30 seconds.
----

Note how even though there are a single candidate, JBang still asks for confirmation as you have not
*explicitly* set the main class.

== Adding entries to `MANIFEST.MF`

If you want to set custom entries in the `MANIFEST.MF` file of the Jar file generated by JBang then you can
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/dev/jbang/cli/BuildMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ void setJavaVersion(String javaVersion) {
}

@CommandLine.Option(names = { "-m",
"--main" }, description = "Main class to use when running. Used primarily for running jar's.")
"--main" }, description = "Main class to use when running. Used primarily for running jar's. Can be a glob pattern using ? and *.")
String main;

@CommandLine.Option(names = {
Expand Down
46 changes: 37 additions & 9 deletions src/main/java/dev/jbang/source/generators/JarCmdGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@
import java.net.ServerSocket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.Index;
Expand All @@ -16,9 +24,11 @@
import dev.jbang.cli.BaseCommand;
import dev.jbang.cli.ExitException;
import dev.jbang.devkitman.Jdk;
import dev.jbang.source.*;
import dev.jbang.source.BuildContext;
import dev.jbang.source.Project;
import dev.jbang.source.buildsteps.CompileBuildStep;
import dev.jbang.util.CommandBuffer;
import dev.jbang.util.Glob;
import dev.jbang.util.JavaUtil;
import dev.jbang.util.ModuleUtil;
import dev.jbang.util.Util;
Expand Down Expand Up @@ -219,7 +229,7 @@ protected List<String> generateCommandLineList() throws IOException {
fullArgs.addAll(optionalArgs);

String main = Optional.ofNullable(mainClass).orElse(project.getMainClass());
if (main != null) {
if (main != null && !Glob.isGlob(main)) {
if (moduleName != null && project.getModuleName().isPresent()) {
String modName = moduleName.isEmpty() ? ModuleUtil.getModuleName(project) : moduleName;
fullArgs.add("-m");
Expand Down Expand Up @@ -264,13 +274,30 @@ protected List<String> generateCommandLineList() throws IOException {
"No main class deduced, specified nor found in a manifest nor jar");
} else {

String mainClasses = mains.stream()
.map(m -> "\n - " + m.name().toString())
.collect(Collectors.joining());
Stream<ClassInfo> filteredMains = mains.stream();

throw new ExitException(BaseCommand.EXIT_INVALID_INPUT,
"No main class deduced, specified nor found in a manifest, but found these candidates:\n"
+ mainClasses + "\n\nUse -m <main class> to specify a main class.");
if (Glob.isGlob(main)) {
filteredMains = filteredMains
.filter(m -> Glob.matches(main, m.name().toString()));
}

String[] mainClassOptions = filteredMains.map(m -> m.name().toString()).toArray(String[]::new);
int result = Util.askInput(
"No main class deduced, specified nor found in a manifest, but found these candidates:", 30, 0,
mainClassOptions);

if (result <= 0) {
String mainClasses = mains.stream()
.map(m -> "\n - " + m)
.collect(Collectors.joining());
throw new ExitException(BaseCommand.EXIT_INVALID_INPUT,
"No main class deduced, specified nor found in a manifest, but found these candidates:\n"
+ mainClasses + "\n\nUse -m <main class> to specify a main class.");
} else {
mainClass = mainClassOptions[result - 1];
Util.verboseMsg("User chose main:" + mainClass);
fullArgs.add(mainClass);
}
}
}
fullArgs.addAll(arguments);
Expand All @@ -288,4 +315,5 @@ protected String generateCommandLineString(List<String> fullArgs) throws IOExcep
private static void addPropertyFlags(Map<String, String> properties, String def, List<String> result) {
properties.forEach((k, e) -> result.add(def + k + "=" + e));
}

}
67 changes: 67 additions & 0 deletions src/main/java/dev/jbang/util/Glob.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package dev.jbang.util;

import java.util.regex.Pattern;

/**
* Very simple glob to regex converter
*
* To use for glob pattern match strings, i.e. Demo* will match DemoApp,
* DemoTest etc.
*/
public final class Glob {

private Glob() {
}

public static Pattern toRegex(String glob) {
StringBuilder regex = new StringBuilder(glob.length() * 2);
regex.append('^');

for (int i = 0; i < glob.length(); i++) {
char c = glob.charAt(i);
switch (c) {
case '*':
regex.append(".*");
break;
case '?':
regex.append('.');
break;
// escape regex metacharacters
case '\\':
case '.':
case '+':
case '(':
case ')':
case '^':
case '$':
case '|':
case '{':
case '}':
case '[':
case ']':
regex.append('\\').append(c);
break;
default:
regex.append(c);
}
}

regex.append('$');
return Pattern.compile(regex.toString());
}

public static boolean matches(String glob, String value) {
boolean matches = toRegex(glob).matcher(value).matches();
if (matches) {
Util.verboseMsg("Glob " + glob + " matches " + value);
} else {
Util.verboseMsg("Glob " + glob + " does not match " + value);
}
return matches;
}

public static boolean isGlob(String main) {
return main != null && (main.contains("?") || main.contains("*"));
}

}
3 changes: 3 additions & 0 deletions src/main/java/dev/jbang/util/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -1308,6 +1308,9 @@ public static boolean isBlankString(String str) {
}

public static int askInput(String message, int timeout, int defaultValue, String... options) {
if (options.length == 0) {
return -1;
}
ConsoleInput con = ConsoleInput.get(1, timeout, TimeUnit.SECONDS);
if (con != null) {
StringBuilder msg = new StringBuilder(message + "\n\n");
Expand Down
20 changes: 20 additions & 0 deletions src/test/java/dev/jbang/util/GlobTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package dev.jbang.util;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;

import dev.jbang.BaseTest;

public class GlobTest extends BaseTest {

@Test
void testMainMatching() {
assertThat("Demo1").matches(Glob.toRegex("*"));
assertThat("Demo1").matches(Glob.toRegex("Demo*"));
assertThat("a.b.c.Demo$myapp").matches(Glob.toRegex("a.b.c.Demo$myapp"));

assertThat("a.b.c.Demo$myapp").doesNotMatch(Glob.toRegex("Demo$myapp"));
assertThat("a.b.c.Demo$myapp").matches(Glob.toRegex("*Demo$myapp"));
}
}