Skip to content
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ allure-results
.DS_Store
.env
assets/
.cachebro/
CLAUDE.md
PLAN-*.md
27 changes: 24 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ dependencies {
implementation 'org.jspecify:jspecify:1.0.0'
implementation 'org.apache.commons:commons-text:1.15.0'
implementation 'org.apache.commons:commons-compress:1.27.1'
implementation 'info.picocli:picocli:4.7.7'
annotationProcessor 'info.picocli:picocli-codegen:4.7.7'
implementation 'org.aesh:aesh:3.6.1'
annotationProcessor 'org.aesh:aesh-processor:3.6.1'
implementation 'io.quarkus.qute:qute-core:1.13.7.Final'
implementation 'org.codehaus.plexus:plexus-java:1.2.0'
implementation 'com.google.code.gson:gson:2.13.2'
Expand Down Expand Up @@ -354,7 +354,9 @@ shadowJar {
}

test {
useJUnitPlatform()
useJUnitPlatform() {
excludeTags 'benchmark'
}
javaLauncher = testExecutionToolchain
def jvmArgsList = ["-javaagent:${configurations.agent.singleFile}"] // for allure reporting
if (testJavaVersion.asInt() >= 9) {
Expand All @@ -380,6 +382,25 @@ test {
systemProperty('jbang.test.wiremock.enable', project.findProperty('disableWiremock') == 'true' ? 'false' : 'true')
}

task benchmark(type: Test) {
description = 'Run startup benchmark tests'
group = 'verification'
useJUnitPlatform() {
includeTags 'benchmark'
}
javaLauncher = testExecutionToolchain
def jvmArgsList = []
if (testJavaVersion.asInt() >= 9) {
jvmArgsList.addAll([
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
"--add-opens", "java.base/java.util=ALL-UNNAMED"
])
}
jvmArgs = jvmArgsList
testLogging.showStandardStreams = true
systemProperty('jbang.test.wiremock.enable', project.findProperty('disableWiremock') == 'true' ? 'false' : 'true')
}

jacoco {
toolVersion = '0.8.14' // 0.8.14 supports java 25
}
Expand Down
2 changes: 1 addition & 1 deletion src/it/java/dev/jbang/it/AbstractHelpBaseIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public abstract class AbstractHelpBaseIT extends BaseIT {
public void shouldPrintHelp() {
assertThat(shell("jbang " + commandName() + " --help"))
.succeeded()
.errContains("Use 'jbang <command> -h' for detailed");
.outContains("--help");
}

// Scenario: check help command is printed when -h is requested
Expand Down
125 changes: 113 additions & 12 deletions src/main/java/dev/jbang/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,120 @@

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.logging.LogManager;
import java.util.stream.Collectors;

import org.aesh.AeshRuntimeRunner;
import org.aesh.command.CommandDefinition;
import org.aesh.command.CommandResult;
import org.aesh.command.GroupCommandDefinition;

import dev.jbang.catalog.Alias;
import dev.jbang.catalog.Catalog;
import dev.jbang.cli.BaseCommand;
import dev.jbang.cli.ExitException;
import dev.jbang.cli.JBang;
import dev.jbang.util.Util;

import picocli.CommandLine;
import dev.jbang.util.VersionChecker;

public class Main {
public static void main(String... args) {
// Set up JUL logging so the output looks like JBang output
try {
// Set up JUL logging so the output looks like JBang output
LogManager.getLogManager().readConfiguration(Main.class.getResourceAsStream("/logging.properties"));
} catch (IOException e) {
// Ignore
}

CommandLine cli = JBang.getCommandLine();
String[] new_args = handleDefaultRun(cli.getCommandSpec(), args);
int exitcode = cli.execute(new_args);
System.exit(exitcode);
if (AeshRuntimeRunner.handleDynamicCompletion(args, JBang.class)) {
return;
}

String[] newArgs = handleDefaultRun(args);

Util.verboseMsg("jbang version " + Util.getJBangVersion());
Future<String> versionCheckResult = VersionChecker.newerVersionAsync();
int exitCode = 0;
try {
CommandResult result = AeshRuntimeRunner.builder()
.command(JBang.class)
.args(newArgs)
.execute();
if (result != null) {
exitCode = result.getResultValue();
}
} catch (ExitException e) {
if (e.getStatus() != 0) {
Util.errorMsg(null, e);
}
VersionChecker.informOrCancel(versionCheckResult);
System.exit(e.getStatus());
} catch (Exception e) {
Util.errorMsg(null, e);
if (Util.isVerbose()) {
Util.infoMsg(
"If you believe this a bug in jbang, open an issue at https://github.com/jbangdev/jbang/issues");
}
VersionChecker.informOrCancel(versionCheckResult);
System.exit(BaseCommand.EXIT_INTERNAL_ERROR);
}
VersionChecker.informOrCancel(versionCheckResult);
if (exitCode != 0) {
System.exit(exitCode);
}
}

public static String[] handleDefaultRun(CommandLine.Model.CommandSpec spec, String[] args) {
private static volatile Set<String> subcommandNames;

static Set<String> getSubcommandNames() {
if (subcommandNames == null) {
Set<String> names = new LinkedHashSet<>();
GroupCommandDefinition gcd = JBang.class.getAnnotation(GroupCommandDefinition.class);
if (gcd != null) {
for (Class<?> cmd : gcd.groupCommands()) {
CommandDefinition cd = cmd.getAnnotation(CommandDefinition.class);
if (cd != null) {
names.add(cd.name());
} else {
GroupCommandDefinition gc = cmd.getAnnotation(GroupCommandDefinition.class);
if (gc != null) {
names.add(gc.name());
}
}
}
}
subcommandNames = names;
}
return subcommandNames;
}

public static String[] handleDefaultRun(String[] args) {
if (args != null) {
List<String> filtered = new ArrayList<>();
for (String a : args) {
if (a != null) {
filtered.add(a);
}
}
args = filtered.toArray(new String[0]);
}
// Check for deprecated flags before parsing
for (String arg : args) {
String key = arg.contains("=") ? arg.substring(0, arg.indexOf("=")) : arg;
String replacement = getDeprecatedFlagReplacement(key);
if (replacement != null) {
System.err.printf(
"%s is a deprecated and now removed flag. See %s for more details on its replacement.%n",
key, replacement);
throw new ExitException(BaseCommand.EXIT_INVALID_INPUT,
key + " is a deprecated and now removed flag");
}
}

List<String> leadingOpts = new ArrayList<>();
List<String> remainingArgs = new ArrayList<>();
boolean foundParam = false;
Expand All @@ -54,12 +140,12 @@ public static String[] handleDefaultRun(CommandLine.Model.CommandSpec spec, Stri
result.addAll(leadingOpts);
result.addAll(remainingArgs);
args = result.toArray(args);
} else if (!spec.subcommands().containsKey(cmd)) {
} else if (!getSubcommandNames().contains(cmd)) {
if (Catalog.isValidName("jbang-" + cmd) && Alias.get("jbang-" + cmd) != null) {
// We found a matching "jbang-xxx" alias
remainingArgs.set(0, "jbang-" + cmd);
} else if (Catalog.isValidName(cmd) && Alias.get(cmd) != null) {
// We found an exactly matching alias
// We found an exactly matching alias.
// We do this test because we want aliases to have a higher
// priority than the next case, which is to look up commands
// in the user's PATH which might be slow-ish
Expand All @@ -74,9 +160,8 @@ public static String[] handleDefaultRun(CommandLine.Model.CommandSpec spec, Stri
Util.verboseMsg("run plugin: " + cmdLine);
System.out.println(cmdLine);
System.exit(BaseCommand.EXIT_EXECUTE);
} else {
// In all other cases assume it's an implicit "run" (no need to do anything)
}
// In all other cases assume it's an implicit "run"
List<String> jbangOpts = stripNonInheritedJBangOpts(leadingOpts);
List<String> result = new ArrayList<>(jbangOpts);
result.add("run");
Expand All @@ -97,6 +182,22 @@ private static boolean hasRunOpts(List<String> opts) {
return res;
}

private static String getDeprecatedFlagReplacement(String flag) {
switch (flag) {
case "--init":
return "jbang init --help";
case "--edit":
case "--edit-live":
return "jbang edit --help";
case "--trust":
return "jbang trust --help";
case "--alias":
return "jbang alias --help";
default:
return null;
}
}

private static List<String> stripNonInheritedJBangOpts(List<String> opts) {
List<String> jbangOpts = opts.stream()
.filter(o -> "--preview".equals(o) || o.startsWith("--preview="))
Expand Down
20 changes: 0 additions & 20 deletions src/main/java/dev/jbang/cli/AIOptions.java

This file was deleted.

Loading